本地HTML美国身份生成器

Setang 40 阅读 信息分享

<!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">州 &amp; 地区</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">年龄 &amp; 其他</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>