<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>US Address Generator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<style>
:root {
--bg: #0a0e1a;
--surface: #111827;
--surface2: #1a2235;
--border: #1e2d45;
--accent: #00d4ff;
--accent2: #0066ff;
--accent-glow: rgba(0,212,255,0.15);
--text: #e8edf5;
--text-muted: #6b7fa3;
--success: #00e5a0;
--mono: 'Space Mono', monospace;
--sans: 'DM Sans', sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: var(--sans);
min-height: 100vh;
overflow-x: hidden;
}
/* Grid background */
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
linear-gradient(rgba(0,212,255,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0,212,255,0.03) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
z-index: 0;
}
.container {
max-width: 1100px;
margin: 0 auto;
padding: 40px 24px;
position: relative;
z-index: 1;
}
/* Header */
header {
text-align: center;
margin-bottom: 48px;
}
.header-badge {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--accent-glow);
border: 1px solid rgba(0,212,255,0.3);
border-radius: 100px;
padding: 6px 16px;
font-family: var(--mono);
font-size: 11px;
color: var(--accent);
letter-spacing: 2px;
margin-bottom: 20px;
text-transform: uppercase;
}
.header-badge::before { content: '◆'; font-size: 8px; }
h1 {
font-family: var(--mono);
font-size: clamp(28px, 5vw, 48px);
font-weight: 700;
letter-spacing: -1px;
line-height: 1.1;
background: linear-gradient(135deg, #fff 30%, var(--accent) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 12px;
}
header p {
color: var(--text-muted);
font-size: 15px;
font-weight: 300;
letter-spacing: 0.3px;
}
/* Main grid */
.main-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 700px) {
.main-grid { grid-template-columns: 1fr; }
}
/* Card */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
position: relative;
overflow: hidden;
transition: border-color 0.2s;
}
.card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(0,212,255,0.4), transparent);
}
.card:hover { border-color: rgba(0,212,255,0.2); }
.card-label {
font-family: var(--mono);
font-size: 10px;
letter-spacing: 2px;
color: var(--accent);
text-transform: uppercase;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.card-label::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
/* Controls */
label {
display: block;
font-size: 12px;
font-weight: 500;
color: var(--text-muted);
margin-bottom: 6px;
letter-spacing: 0.5px;
text-transform: uppercase;
}
select, input[type="number"] {
width: 100%;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text);
font-family: var(--sans);
font-size: 14px;
padding: 10px 14px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
appearance: none;
-webkit-appearance: none;
cursor: pointer;
margin-bottom: 14px;
}
select:focus, input[type="number"]:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-glow);
}
select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236b7fa3' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; padding-right: 36px; }
.age-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
/* Gender / count */
.toggle-group {
display: flex;
gap: 8px;
margin-bottom: 14px;
}
.toggle-btn {
flex: 1;
padding: 9px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-muted);
font-family: var(--sans);
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.toggle-btn.active {
background: var(--accent-glow);
border-color: var(--accent);
color: var(--accent);
}
/* Generate button */
.btn-generate {
width: 100%;
padding: 16px;
background: linear-gradient(135deg, var(--accent2), var(--accent));
border: none;
border-radius: 12px;
color: #fff;
font-family: var(--mono);
font-size: 13px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
cursor: pointer;
transition: all 0.2s;
position: relative;
overflow: hidden;
margin-top: 4px;
}
.btn-generate::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1), transparent);
opacity: 0;
transition: opacity 0.2s;
}
.btn-generate:hover { transform: translateY(-1px); box-shadow: 0 8px 30px rgba(0,102,255,0.4); }
.btn-generate:hover::after { opacity: 1; }
.btn-generate:active { transform: translateY(0); }
/* Result card */
.result-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 16px;
padding: 28px;
margin-bottom: 20px;
position: relative;
overflow: hidden;
display: none;
animation: fadeSlideIn 0.35s ease;
}
.result-card.show { display: block; }
.result-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
background: linear-gradient(90deg, var(--accent2), var(--accent));
}
@keyframes fadeSlideIn {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.result-title {
font-family: var(--mono);
font-size: 11px;
letter-spacing: 2px;
color: var(--accent);
text-transform: uppercase;
}
.copy-all-btn {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-muted);
font-family: var(--mono);
font-size: 11px;
padding: 7px 14px;
cursor: pointer;
letter-spacing: 1px;
transition: all 0.2s;
}
.copy-all-btn:hover { border-color: var(--accent); color: var(--accent); }
.fields-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 14px;
}
.field-item {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 10px;
padding: 12px 14px;
cursor: pointer;
transition: all 0.2s;
position: relative;
}
.field-item:hover { border-color: rgba(0,212,255,0.3); }
.field-item.copied { border-color: var(--success); }
.field-key {
font-size: 10px;
color: var(--text-muted);
letter-spacing: 1px;
text-transform: uppercase;
margin-bottom: 5px;
font-family: var(--mono);
}
.field-value {
font-family: var(--mono);
font-size: 14px;
color: var(--text);
font-weight: 400;
word-break: break-all;
}
.field-item.full-width { grid-column: 1 / -1; }
.copy-hint {
position: absolute;
top: 8px; right: 10px;
font-size: 10px;
color: var(--text-muted);
opacity: 0;
transition: opacity 0.2s;
font-family: var(--mono);
}
.field-item:hover .copy-hint { opacity: 1; }
.field-item.copied .copy-hint { opacity: 1; color: var(--success); }
/* Map */
.map-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 16px;
overflow: hidden;
display: none;
animation: fadeSlideIn 0.4s ease 0.1s both;
}
.map-card.show { display: block; }
.map-header {
padding: 16px 24px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
}
.map-header span {
font-family: var(--mono);
font-size: 11px;
letter-spacing: 2px;
color: var(--accent);
text-transform: uppercase;
}
.map-dot {
width: 8px; height: 8px;
background: var(--success);
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%,100% { box-shadow: 0 0 0 0 rgba(0,229,160,0.4); }
50% { box-shadow: 0 0 0 6px rgba(0,229,160,0); }
}
#map { height: 320px; }
/* History */
.history-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
margin-top: 20px;
display: none;
}
.history-card.show { display: block; }
.history-list { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; }
.history-item {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 14px;
font-family: var(--mono);
font-size: 12px;
color: var(--text-muted);
cursor: pointer;
transition: all 0.15s;
display: flex;
justify-content: space-between;
align-items: center;
}
.history-item:hover { border-color: rgba(0,212,255,0.3); color: var(--text); }
.history-state-tag {
background: var(--accent-glow);
border: 1px solid rgba(0,212,255,0.2);
border-radius: 4px;
padding: 2px 7px;
font-size: 10px;
color: var(--accent);
}
footer {
text-align: center;
margin-top: 40px;
color: var(--text-muted);
font-size: 12px;
font-family: var(--mono);
opacity: 0.5;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="header-badge">US Address Generator v2.0</div>
<h1>ADDRESS<br>GENERATOR</h1>
<p>生成真实感美国地址 · 全州覆盖 · 可视化地图定位</p>
</header>
<div class="main-grid">
<!-- Controls Left -->
<div class="card">
<div class="card-label">州 & 地区</div>
<label>选择州 (State)</label>
<select id="stateSelect">
<option value="random">🎲 随机州</option>
</select>
<label>性别</label>
<div class="toggle-group" id="genderToggle">
<div class="toggle-btn active" data-val="male">♂ 男性</div>
<div class="toggle-btn" data-val="female">♀ 女性</div>
<div class="toggle-btn" data-val="random">⚡ 随机</div>
</div>
<label>生成数量</label>
<select id="countSelect">
<option value="1">1 条</option>
<option value="3">3 条</option>
<option value="5">5 条</option>
<option value="10">10 条</option>
</select>
</div>
<!-- Controls Right -->
<div class="card">
<div class="card-label">年龄 & 其他</div>
<label>年龄范围</label>
<div class="age-row">
<div>
<label>最小年龄</label>
<input type="number" id="ageMin" value="18" min="1" max="99">
</div>
<div>
<label>最大年龄</label>
<input type="number" id="ageMax" value="45" min="1" max="99">
</div>
</div>
<label>地址类型</label>
<select id="addrType">
<option value="residential">住宅(Residential)</option>
<option value="apartment">公寓(Apartment)</option>
<option value="mixed">随机混合(Mixed)</option>
</select>
<button class="btn-generate" onclick="generateAddress()">⚡ 生成地址</button>
</div>
</div>
<!-- Result -->
<div class="result-card" id="resultCard">
<div class="result-header">
<div class="result-title">◆ 生成结果</div>
<button class="copy-all-btn" onclick="copyAll()">一键复制全部</button>
</div>
<div class="fields-grid" id="fieldsGrid"></div>
</div>
<!-- Map -->
<div class="map-card" id="mapCard">
<div class="map-header">
<div class="map-dot"></div>
<span>地图预览 · OpenStreetMap</span>
</div>
<div id="map"></div>
</div>
<!-- History -->
<div class="history-card" id="historyCard">
<div class="card-label">最近生成记录</div>
<div class="history-list" id="historyList"></div>
</div>
<footer>纯本地运行 · 无服务器 · 不收集任何数据</footer>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// ===== DATA =====
const STATES = [
{name:"Alabama",zh:"阿拉巴马州",abbr:"AL",zip:[35004,36925],area:["205","251","334","938"],lat:32.806671,lng:-86.791130},
{name:"Alaska",zh:"阿拉斯加州",abbr:"AK",zip:[99501,99950],area:["907"],lat:61.370716,lng:-152.404419},
{name:"Arizona",zh:"亚利桑那州",abbr:"AZ",zip:[85001,86556],area:["480","520","602","623","928"],lat:33.729759,lng:-111.431221},
{name:"Arkansas",zh:"阿肯色州",abbr:"AR",zip:[71601,72959],area:["479","501","870"],lat:34.969704,lng:-92.373123},
{name:"California",zh:"加利福尼亚州",abbr:"CA",zip:[90001,96162],area:["209","213","310","323","408","415","424","442","510","530","559","562","619","626","650","657","661","669","707","714","747","760","805","818","831","858","909","916","925","949","951"],lat:36.116203,lng:-119.681564},
{name:"Colorado",zh:"科罗拉多州",abbr:"CO",zip:[80001,81658],area:["303","719","720","970"],lat:39.059811,lng:-105.311104},
{name:"Connecticut",zh:"康涅狄格州",abbr:"CT",zip:[6001,6928],area:["203","475","860","959"],lat:41.597782,lng:-72.755371},
{name:"Delaware",zh:"特拉华州",abbr:"DE",zip:[19701,19980],area:["302"],lat:39.318523,lng:-75.507141},
{name:"Florida",zh:"佛罗里达州",abbr:"FL",zip:[32004,34997],area:["239","305","321","352","386","407","561","727","754","772","786","813","850","863","904","941","954"],lat:27.766279,lng:-81.686783},
{name:"Georgia",zh:"乔治亚州",abbr:"GA",zip:[30001,31999],area:["229","404","470","478","678","706","762","770","912"],lat:33.040619,lng:-83.643074},
{name:"Hawaii",zh:"夏威夷州",abbr:"HI",zip:[96701,96898],area:["808"],lat:21.094318,lng:-157.498337},
{name:"Idaho",zh:"爱达荷州",abbr:"ID",zip:[83201,83876],area:["208","986"],lat:44.240459,lng:-114.478828},
{name:"Illinois",zh:"伊利诺伊州",abbr:"IL",zip:[60001,62999],area:["217","224","309","312","331","447","464","618","630","708","773","779","815","847","872"],lat:40.349457,lng:-88.986137},
{name:"Indiana",zh:"印第安纳州",abbr:"IN",zip:[46001,47997],area:["219","260","317","463","574","765","812","930"],lat:39.849426,lng:-86.258278},
{name:"Iowa",zh:"爱荷华州",abbr:"IA",zip:[50001,52809],area:["319","515","563","641","712"],lat:42.011539,lng:-93.210526},
{name:"Kansas",zh:"堪萨斯州",abbr:"KS",zip:[66002,67954],area:["316","620","785","913"],lat:38.526600,lng:-96.726486},
{name:"Kentucky",zh:"肯塔基州",abbr:"KY",zip:[40003,42788],area:["270","364","502","606","859"],lat:37.668140,lng:-84.670067},
{name:"Louisiana",zh:"路易斯安那州",abbr:"LA",zip:[70001,71497],area:["225","318","337","504","985"],lat:31.169960,lng:-91.867805},
{name:"Maine",zh:"缅因州",abbr:"ME",zip:[3901,4992],area:["207"],lat:44.693947,lng:-69.381927},
{name:"Maryland",zh:"马里兰州",abbr:"MD",zip:[20601,21930],area:["240","301","410","443","667"],lat:39.063946,lng:-76.802101},
{name:"Massachusetts",zh:"马萨诸塞州",abbr:"MA",zip:[1001,2791],area:["339","351","413","508","617","774","781","857","978"],lat:42.230171,lng:-71.530106},
{name:"Michigan",zh:"密歇根州",abbr:"MI",zip:[48001,49971],area:["231","248","269","313","517","586","616","734","810","906","947","989"],lat:43.326618,lng:-84.536095},
{name:"Minnesota",zh:"明尼苏达州",abbr:"MN",zip:[55001,56763],area:["218","320","507","612","651","763","952"],lat:45.694454,lng:-93.900192},
{name:"Mississippi",zh:"密西西比州",abbr:"MS",zip:[38601,39776],area:["228","601","662","769"],lat:32.741646,lng:-89.678696},
{name:"Missouri",zh:"密苏里州",abbr:"MO",zip:[63001,65899],area:["314","417","573","636","660","816"],lat:38.456085,lng:-92.288368},
{name:"Montana",zh:"蒙大拿州",abbr:"MT",zip:[59001,59937],area:["406"],lat:46.921925,lng:-110.454353},
{name:"Nebraska",zh:"内布拉斯加州",abbr:"NE",zip:[68001,69367],area:["308","402","531"],lat:41.125370,lng:-98.268082},
{name:"Nevada",zh:"内华达州",abbr:"NV",zip:[88901,89883],area:["702","725","775"],lat:38.313515,lng:-117.055374},
{name:"New Hampshire",zh:"新罕布什尔州",abbr:"NH",zip:[3031,3897],area:["603"],lat:43.452492,lng:-71.563896},
{name:"New Jersey",zh:"新泽西州",abbr:"NJ",zip:[7001,8989],area:["201","551","609","640","732","848","856","862","908","973"],lat:40.298904,lng:-74.521011},
{name:"New Mexico",zh:"新墨西哥州",abbr:"NM",zip:[87001,88441],area:["505","575"],lat:34.840515,lng:-106.248482},
{name:"New York",zh:"纽约州",abbr:"NY",zip:[10001,14975],area:["212","315","347","516","518","585","607","631","646","680","716","718","838","845","914","917","929","934"],lat:42.165726,lng:-74.948051},
{name:"North Carolina",zh:"北卡罗来纳州",abbr:"NC",zip:[27006,28909],area:["252","336","704","743","828","910","919","980","984"],lat:35.630066,lng:-79.806419},
{name:"North Dakota",zh:"北达科他州",abbr:"ND",zip:[58001,58856],area:["701"],lat:47.528912,lng:-99.784012},
{name:"Ohio",zh:"俄亥俄州",abbr:"OH",zip:[43001,45999],area:["216","234","283","330","380","419","440","513","567","614","740","937"],lat:40.388783,lng:-82.764915},
{name:"Oklahoma",zh:"俄克拉荷马州",abbr:"OK",zip:[73001,74966],area:["405","539","580","918"],lat:35.565342,lng:-96.928917},
{name:"Oregon",zh:"俄勒冈州",abbr:"OR",zip:[97001,97920],area:["458","503","541","971"],lat:44.572021,lng:-122.070938},
{name:"Pennsylvania",zh:"宾夕法尼亚州",abbr:"PA",zip:[15001,19640],area:["215","223","267","272","412","445","484","570","582","610","717","724","814","835","878"],lat:40.590752,lng:-77.209755},
{name:"Rhode Island",zh:"罗德岛州",abbr:"RI",zip:[2801,2940],area:["401"],lat:41.680893,lng:-71.511780},
{name:"South Carolina",zh:"南卡罗来纳州",abbr:"SC",zip:[29001,29948],area:["803","839","843","854","864"],lat:33.856892,lng:-80.945007},
{name:"South Dakota",zh:"南达科他州",abbr:"SD",zip:[57001,57799],area:["605"],lat:44.299782,lng:-99.438828},
{name:"Tennessee",zh:"田纳西州",abbr:"TN",zip:[37010,38589],area:["423","615","629","731","865","901","931"],lat:35.747845,lng:-86.692345},
{name:"Texas",zh:"德克萨斯州",abbr:"TX",zip:[75001,79999],area:["210","214","254","281","325","346","361","409","430","432","469","512","682","713","726","737","806","817","830","832","903","915","936","940","956","972","979"],lat:31.054487,lng:-97.563461},
{name:"Utah",zh:"犹他州",abbr:"UT",zip:[84001,84784],area:["385","435","801"],lat:40.150032,lng:-111.862434},
{name:"Vermont",zh:"佛蒙特州",abbr:"VT",zip:[5001,5907],area:["802"],lat:44.045876,lng:-72.710686},
{name:"Virginia",zh:"弗吉尼亚州",abbr:"VA",zip:[20101,24658],area:["276","434","540","571","703","757","804"],lat:37.769337,lng:-78.169968},
{name:"Washington",zh:"华盛顿州",abbr:"WA",zip:[98001,99403],area:["206","253","360","425","509","564"],lat:47.400902,lng:-121.490494},
{name:"West Virginia",zh:"西弗吉尼亚州",abbr:"WV",zip:[24701,26886],area:["304","681"],lat:38.491226,lng:-80.954453},
{name:"Wisconsin",zh:"威斯康星州",abbr:"WI",zip:[53001,54990],area:["262","414","534","608","715","920"],lat:44.268543,lng:-89.616508},
{name:"Wyoming",zh:"怀俄明州",abbr:"WY",zip:[82001,83128],area:["307"],lat:42.755966,lng:-107.302490}
];
const MALE_FIRST = ["James","John","Robert","Michael","William","David","Richard","Joseph","Thomas","Charles","Christopher","Daniel","Matthew","Anthony","Mark","Donald","Steven","Paul","Andrew","Joshua","Kevin","Brian","George","Timothy","Ronald","Edward","Jason","Jeffrey","Ryan","Jacob","Gary","Nicholas","Eric","Jonathan","Stephen","Larry","Justin","Scott","Brandon","Benjamin","Samuel","Raymond","Gregory","Frank","Alexander","Patrick","Raymond","Jack","Dennis","Jerry"];
const FEMALE_FIRST = ["Mary","Patricia","Jennifer","Linda","Barbara","Elizabeth","Susan","Jessica","Sarah","Karen","Lisa","Nancy","Betty","Margaret","Sandra","Ashley","Dorothy","Kimberly","Emily","Donna","Michelle","Carol","Amanda","Melissa","Deborah","Stephanie","Rebecca","Sharon","Laura","Cynthia","Kathleen","Amy","Angela","Shirley","Anna","Brenda","Pamela","Emma","Nicole","Helen","Samantha","Katherine","Christine","Debra","Rachel","Carolyn","Janet","Catherine","Maria","Heather"];
const LAST_NAMES = ["Smith","Johnson","Williams","Brown","Jones","Garcia","Miller","Davis","Rodriguez","Martinez","Hernandez","Lopez","Gonzalez","Wilson","Anderson","Thomas","Taylor","Moore","Jackson","Martin","Lee","Perez","Thompson","White","Harris","Sanchez","Clark","Ramirez","Lewis","Robinson","Walker","Young","Allen","King","Wright","Scott","Torres","Nguyen","Hill","Flores","Green","Adams","Nelson","Baker","Hall","Rivera","Campbell","Mitchell","Carter","Roberts"];
const STREET_TYPES = {
residential: ["Oak","Maple","Cedar","Pine","Elm","Willow","Cherry","Birch","Walnut","Hickory","Magnolia","Dogwood","Spruce","Aspen","Sycamore","Cypress","Chestnut","Poplar","Redwood","Sequoia"],
apartment: ["Grand","Park","Lake","River","Mountain","Valley","Sunset","Sunrise","Garden","Forest","Meadow","Hillside","Lakeview","Riverside","Oceanview","Bayview","Harbor","Parkside","Greenwood","Westwood"],
suffix_residential: ["St","Ave","Rd","Ln","Dr","Blvd","Way","Ct","Pl","Ter","Path","Trail","Cir","Run","Pass"],
suffix_apartment: ["Blvd","Ave","Dr","Pkwy","Way","Plaza","Square"]
};
const UNIT_PREFIXES = ["Apt","Suite","Unit","#"];
// ===== INIT =====
const stateSelect = document.getElementById('stateSelect');
STATES.forEach(s => {
const opt = document.createElement('option');
opt.value = s.abbr;
opt.textContent = `${s.name}(${s.zh})`;
stateSelect.appendChild(opt);
});
// Gender toggle
let selectedGender = 'male';
document.getElementById('genderToggle').addEventListener('click', e => {
const btn = e.target.closest('.toggle-btn');
if (!btn) return;
document.querySelectorAll('.toggle-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
selectedGender = btn.dataset.val;
});
// ===== MAP =====
let map, marker;
function initMap(lat, lng, popupText) {
if (!map) {
map = L.map('map').setView([lat, lng], 13);
L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://carto.com/">CARTO</a>',
subdomains: 'abcd',
maxZoom: 19
}).addTo(map);
} else {
map.setView([lat, lng], 13);
}
if (marker) map.removeLayer(marker);
marker = L.marker([lat, lng]).addTo(map);
if (popupText) {
marker.bindPopup(`
<div style="font-family:'DM Sans',sans-serif;font-size:13px;line-height:1.7;min-width:160px;">
<div style="font-weight:700;color:#0066ff;margin-bottom:4px;font-size:11px;letter-spacing:1px;text-transform:uppercase;">📍 地址位置</div>
${popupText}
</div>
`, { maxWidth: 220 }).openPopup();
}
}
// ===== GENERATE =====
const rand = (a, b) => Math.floor(Math.random() * (b - a + 1)) + a;
const pick = arr => arr[rand(0, arr.length - 1)];
const randFloat = (a, b) => Math.random() * (b - a) + a;
function generateOne(state, gender, ageMin, ageMax, addrType) {
// Name
const g = gender === 'random' ? (Math.random() > 0.5 ? 'male' : 'female') : gender;
const firstName = pick(g === 'male' ? MALE_FIRST : FEMALE_FIRST);
const lastName = pick(LAST_NAMES);
const fullName = `${firstName} ${lastName}`;
// Age & DOB
const age = rand(ageMin, ageMax);
const now = new Date();
const birthYear = now.getFullYear() - age;
const birthMonth = rand(1, 12);
const birthDay = rand(1, 28);
const dob = `${birthYear}-${String(birthMonth).padStart(2,'0')}-${String(birthDay).padStart(2,'0')}`;
// Address
const type = addrType === 'mixed' ? (Math.random() > 0.5 ? 'residential' : 'apartment') : addrType;
const streetNum = rand(100, 9999);
const streetName = pick(type === 'residential' ? STREET_TYPES.residential : STREET_TYPES.apartment);
const suffix = pick(type === 'residential' ? STREET_TYPES.suffix_residential : STREET_TYPES.suffix_apartment);
let address1 = `${streetNum} ${streetName} ${suffix}`;
let address2 = '';
if (type === 'apartment') {
address2 = `${pick(UNIT_PREFIXES)} ${rand(100, 999)}`;
}
// Zip
const zipNum = rand(state.zip[0], state.zip[1]);
const zip = String(zipNum).padStart(5, '0');
// City (simplified - use state-based city names)
const cities = getCities(state.abbr);
const city = pick(cities);
// Phone
const area = pick(state.area);
const phone = `(${area}) ${rand(200,999)}-${rand(1000,9999)}`;
// Email
const emailProviders = ['gmail.com','yahoo.com','outlook.com','hotmail.com','icloud.com'];
const emailUser = `${firstName.toLowerCase()}.${lastName.toLowerCase()}${rand(10,99)}`;
const email = `${emailUser}@${pick(emailProviders)}`;
// Coords with random offset
const lat = state.lat + randFloat(-0.8, 0.8);
const lng = state.lng + randFloat(-0.8, 0.8);
return {
fullName, firstName, lastName,
address1, address2,
city, state: state.name, stateAbbr: state.abbr,
zip, phone, email,
age: String(age), dob,
lat, lng
};
}
function getCities(abbr) {
const cityMap = {
AL:["Birmingham","Montgomery","Huntsville","Mobile","Tuscaloosa"],
AK:["Anchorage","Fairbanks","Juneau","Sitka","Ketchikan"],
AZ:["Phoenix","Tucson","Mesa","Chandler","Scottsdale","Glendale","Tempe"],
AR:["Little Rock","Fort Smith","Fayetteville","Springdale","Jonesboro"],
CA:["Los Angeles","San Diego","San Jose","San Francisco","Fresno","Sacramento","Long Beach","Oakland","Bakersfield","Anaheim"],
CO:["Denver","Colorado Springs","Aurora","Fort Collins","Lakewood","Thornton","Arvada","Westminster"],
CT:["Bridgeport","New Haven","Hartford","Stamford","Waterbury","Norwalk"],
DE:["Wilmington","Dover","Newark","Middletown","Smyrna"],
FL:["Jacksonville","Miami","Tampa","Orlando","St. Petersburg","Hialeah","Tallahassee","Fort Lauderdale"],
GA:["Atlanta","Columbus","Augusta","Macon","Savannah","Athens","Sandy Springs"],
HI:["Honolulu","Pearl City","Hilo","Kailua","Waipahu"],
ID:["Boise","Meridian","Nampa","Idaho Falls","Pocatello"],
IL:["Chicago","Aurora","Joliet","Rockford","Springfield","Peoria","Elgin"],
IN:["Indianapolis","Fort Wayne","Evansville","South Bend","Carmel","Fishers"],
IA:["Des Moines","Cedar Rapids","Davenport","Sioux City","Iowa City"],
KS:["Wichita","Overland Park","Kansas City","Topeka","Olathe"],
KY:["Louisville","Lexington","Bowling Green","Owensboro","Covington"],
LA:["New Orleans","Baton Rouge","Shreveport","Metairie","Lafayette"],
ME:["Portland","Lewiston","Bangor","South Portland","Auburn"],
MD:["Baltimore","Columbia","Germantown","Silver Spring","Waldorf","Glen Burnie"],
MA:["Boston","Worcester","Springfield","Lowell","Cambridge","New Bedford"],
MI:["Detroit","Grand Rapids","Warren","Sterling Heights","Ann Arbor","Lansing"],
MN:["Minneapolis","Saint Paul","Rochester","Duluth","Bloomington","Brooklyn Park"],
MS:["Jackson","Gulfport","Southaven","Hattiesburg","Biloxi"],
MO:["Kansas City","Saint Louis","Springfield","Columbia","Independence"],
MT:["Billings","Missoula","Great Falls","Bozeman","Butte"],
NE:["Omaha","Lincoln","Bellevue","Grand Island","Kearney"],
NV:["Las Vegas","Henderson","Reno","North Las Vegas","Sparks"],
NH:["Manchester","Nashua","Concord","Derry","Dover"],
NJ:["Newark","Jersey City","Paterson","Elizabeth","Edison","Woodbridge"],
NM:["Albuquerque","Las Cruces","Rio Rancho","Santa Fe","Roswell"],
NY:["New York City","Buffalo","Rochester","Yonkers","Syracuse","Albany"],
NC:["Charlotte","Raleigh","Greensboro","Durham","Winston-Salem","Fayetteville"],
ND:["Fargo","Bismarck","Grand Forks","Minot","West Fargo"],
OH:["Columbus","Cleveland","Cincinnati","Toledo","Akron","Dayton"],
OK:["Oklahoma City","Tulsa","Norman","Broken Arrow","Lawton"],
OR:["Portland","Eugene","Salem","Gresham","Hillsboro","Beaverton"],
PA:["Philadelphia","Pittsburgh","Allentown","Erie","Reading","Scranton"],
RI:["Providence","Cranston","Warwick","Pawtucket","East Providence"],
SC:["Columbia","Charleston","North Charleston","Mount Pleasant","Rock Hill"],
SD:["Sioux Falls","Rapid City","Aberdeen","Brookings","Watertown"],
TN:["Memphis","Nashville","Knoxville","Chattanooga","Clarksville"],
TX:["Houston","San Antonio","Dallas","Austin","Fort Worth","El Paso","Arlington","Corpus Christi"],
UT:["Salt Lake City","West Valley City","Provo","West Jordan","Orem","Sandy"],
VT:["Burlington","South Burlington","Rutland","Barre","Montpelier"],
VA:["Virginia Beach","Norfolk","Chesapeake","Richmond","Newport News","Alexandria"],
WA:["Seattle","Spokane","Tacoma","Vancouver","Bellevue","Kent","Everett"],
WV:["Charleston","Huntington","Morgantown","Parkersburg","Wheeling"],
WI:["Milwaukee","Madison","Green Bay","Kenosha","Racine","Appleton"],
WY:["Cheyenne","Casper","Laramie","Gillette","Rock Springs"]
};
return cityMap[abbr] || ["Springfield","Riverside","Franklin","Clinton","Salem"];
}
// History
const history = [];
function generateAddress() {
const stateVal = document.getElementById('stateSelect').value;
const state = stateVal === 'random' ? pick(STATES) : STATES.find(s => s.abbr === stateVal);
const gender = selectedGender;
const ageMin = parseInt(document.getElementById('ageMin').value) || 18;
const ageMax = parseInt(document.getElementById('ageMax').value) || 45;
const addrType = document.getElementById('addrType').value;
const count = parseInt(document.getElementById('countSelect').value);
const data = generateOne(state, gender, Math.min(ageMin,ageMax), Math.max(ageMin,ageMax), addrType);
// Render result
const grid = document.getElementById('fieldsGrid');
grid.innerHTML = '';
const stateObj = STATES.find(s => s.abbr === data.stateAbbr);
const stateZh = stateObj ? stateObj.zh : '';
const fields = [
{key:'姓名(Full Name)', val: data.fullName},
{key:'名字(First Name)', val: data.firstName},
{key:'姓氏(Last Name)', val: data.lastName},
{key:'出生日期(Date of Birth)', val: data.dob},
{key:'年龄(Age)', val: data.age + ' 岁'},
{key:'电话号码(Phone)', val: data.phone},
{key:'电子邮箱(Email)', val: data.email},
{key:'街道地址(Street Address)', val: data.address1, full: true},
...(data.address2 ? [{key:'门牌单元(Unit/Apt)', val: data.address2}] : []),
{key:'城市(City)', val: data.city},
{key:'州(State)', val: `${data.state}(${stateZh})`},
{key:'邮政编码(ZIP Code)', val: data.zip},
{key:'国家(Country)', val: 'United States'},
{key:'地理坐标(Coordinates)', val: `${data.lat.toFixed(5)}, ${data.lng.toFixed(5)}`, full: true}
];
fields.forEach(f => {
const div = document.createElement('div');
div.className = 'field-item' + (f.full ? ' full-width' : '');
div.innerHTML = `<div class="field-key">${f.key}</div><div class="field-value">${f.val}</div><span class="copy-hint">点击复制</span>`;
div.addEventListener('click', () => {
navigator.clipboard.writeText(f.val).then(() => {
div.classList.add('copied');
div.querySelector('.copy-hint').textContent = '✓ 已复制!';
setTimeout(() => {
div.classList.remove('copied');
div.querySelector('.copy-hint').textContent = '点击复制';
}, 1500);
});
});
grid.appendChild(div);
});
document.getElementById('resultCard').classList.add('show');
document.getElementById('mapCard').classList.add('show');
setTimeout(() => {
const popupText = `${data.address1}${data.address2 ? ', ' + data.address2 : ''}<br>${data.city}, ${data.stateAbbr} ${data.zip}`;
initMap(data.lat, data.lng, popupText);
map.invalidateSize();
}, 100);
// History
history.unshift(data);
if (history.length > 5) history.pop();
renderHistory();
// Window scroll
document.getElementById('resultCard').scrollIntoView({behavior:'smooth', block:'nearest'});
}
function renderHistory() {
const card = document.getElementById('historyCard');
const list = document.getElementById('historyList');
card.classList.add('show');
list.innerHTML = '';
history.forEach((d, i) => {
const item = document.createElement('div');
item.className = 'history-item';
item.innerHTML = `<span>${d.fullName} · ${d.address1}, ${d.city}</span><span class="history-state-tag">${d.stateAbbr}</span>`;
list.appendChild(item);
});
}
function copyAll() {
const data = history[0];
if (!data) return;
const text = [
`Name: ${data.fullName}`,
`DOB: ${data.dob} (Age ${data.age})`,
`Phone: ${data.phone}`,
`Email: ${data.email}`,
`Address: ${data.address1}${data.address2 ? ', ' + data.address2 : ''}`,
`City: ${data.city}`,
`State: ${data.state} (${data.stateAbbr})`,
`ZIP: ${data.zip}`,
`Country: United States`
].join('\n');
navigator.clipboard.writeText(text).then(() => {
const btn = document.querySelector('.copy-all-btn');
btn.textContent = '✓ 已复制!';
setTimeout(() => btn.textContent = '一键复制全部', 2000);
});
}
</script>
</body>
</html>