{"id":16217,"date":"2026-05-13T03:00:21","date_gmt":"2026-05-12T21:30:21","guid":{"rendered":"https:\/\/himtrails.in\/?page_id=16217"},"modified":"2026-07-18T20:57:26","modified_gmt":"2026-07-18T15:27:26","slug":"custom-itinerary-builder","status":"publish","type":"page","link":"https:\/\/himtrails.in\/hi\/custom-itinerary-builder\/","title":{"rendered":"HimTrails &#8211; Custom Itinerary Builder"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"16217\" class=\"elementor elementor-16217\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2cb13be e-con-full e-flex e-con e-parent\" data-id=\"2cb13be\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-c4e39b9 elementor-widget elementor-widget-html\" data-id=\"c4e39b9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n<meta charset=\"UTF-8\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n<title>HimTrails \u2014 Custom Itinerary Builder<\/title>\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jspdf\/2.5.1\/jspdf.umd.min.js\"><\/script>\r\n<style>\r\n  *{box-sizing:border-box;margin:0;padding:0}\r\n  html,body{overflow-x:hidden;max-width:100%}\r\n  body{\r\n    font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,sans-serif;\r\n    background:#f5f7f6;color:#283746;line-height:1.5;\r\n    padding:24px;min-height:100vh;\r\n    -webkit-text-size-adjust:100%;\r\n  }\r\n  .container{max-width:1100px;margin:0 auto;width:100%}\r\n  header{\r\n    background:linear-gradient(135deg,#0f4c3a 0%,#1a7a5e 100%);\r\n    color:#fff;padding:24px 28px;border-radius:14px;margin-bottom:24px;\r\n    box-shadow:0 6px 20px rgba(15,76,58,.18);\r\n    display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:16px;\r\n  }\r\n  header h1{font-size:22px;font-weight:700;letter-spacing:-.3px;margin:0;color:#ffffff;line-height:1.25}\r\n  header .sub{font-size:13px;color:#c8e6d5;margin-top:4px}\r\n  header .actions{display:flex;gap:8px;flex-wrap:wrap}\r\n  header button{\r\n    background:rgba(255,255,255,.12);color:#fff;border:1.5px solid rgba(255,255,255,.5);\r\n    padding:8px 14px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;\r\n    font-family:inherit;transition:background .2s;\r\n  }\r\n  header button:hover{background:rgba(255,255,255,.22)}\r\n  header button.primary{background:#fff;color:#0f4c3a;border-color:#fff}\r\n  header button.primary:hover{background:#f0f9f5}\r\n  .form-card{\r\n    background:#fff;border-radius:14px;padding:24px 28px;margin-bottom:18px;\r\n    border:1px solid #e5ecea;\r\n  }\r\n  .form-card h2{\r\n    color:#0f4c3a;font-size:15px;font-weight:700;letter-spacing:.4px;\r\n    text-transform:uppercase;margin-bottom:14px;\r\n    display:flex;align-items:center;gap:10px;\r\n  }\r\n  .form-card h2::before{\r\n    content:\"\";display:block;width:4px;height:18px;\r\n    background:linear-gradient(135deg,#0f4c3a,#1a7a5e);border-radius:2px;\r\n    flex-shrink:0;\r\n  }\r\n  .grid{display:grid;gap:14px}\r\n  .g2{grid-template-columns:1fr 1fr}\r\n  .g3{grid-template-columns:1fr 1fr 1fr}\r\n  .g4{grid-template-columns:1fr 1fr 1fr 1fr}\r\n  .field{display:flex;flex-direction:column;gap:5px;min-width:0}\r\n  .field label{\r\n    font-size:11px;font-weight:600;color:#5a6b65;\r\n    text-transform:uppercase;letter-spacing:.4px;\r\n  }\r\n  .field label .req{color:#c0392b}\r\n  .field input,.field select,.field textarea{\r\n    padding:9px 12px;border:1.5px solid #d6dfdb;border-radius:8px;\r\n    font-size:14px;font-family:inherit;color:#283746;background:#fff;\r\n    transition:border-color .15s;\r\n    width:100%;max-width:100%;\r\n  }\r\n  .field input:focus,.field select:focus,.field textarea:focus{\r\n    outline:none;border-color:#1a7a5e;\r\n  }\r\n  .field textarea{resize:vertical;min-height:60px;font-family:inherit}\r\n  .field .hint{font-size:11px;color:#8a9a93}\r\n  .day-card,.hotel-row,.addon-row{\r\n    background:#f8faf9;border:1.5px solid #d6dfdb;border-radius:10px;\r\n    padding:14px 16px;margin-bottom:10px;\r\n    position:relative;\r\n  }\r\n  .day-card .day-num{\r\n    position:absolute;top:14px;left:16px;\r\n    background:#0f4c3a;color:#fff;font-weight:700;font-size:12px;\r\n    padding:4px 10px;border-radius:6px;letter-spacing:.4px;\r\n  }\r\n  .day-card .day-fields{margin-top:32px}\r\n  .remove-btn{\r\n    position:absolute;top:10px;right:10px;\r\n    background:#fee;border:1.5px solid #fcc;color:#c0392b;\r\n    padding:5px 10px;border-radius:6px;font-size:11px;font-weight:600;\r\n    cursor:pointer;font-family:inherit;\r\n  }\r\n  .remove-btn:hover{background:#fdd}\r\n  .add-btn{\r\n    background:#0f4c3a;color:#fff;border:none;\r\n    padding:10px 16px;border-radius:8px;font-size:13px;font-weight:600;\r\n    cursor:pointer;font-family:inherit;margin-top:6px;\r\n    transition:background .2s;\r\n  }\r\n  .add-btn:hover{background:#1a7a5e}\r\n  .add-btn::before{content:\"+ \";font-weight:700}\r\n  .footer-actions{\r\n    background:#fff;border-radius:14px;padding:20px 24px;\r\n    border:1px solid #e5ecea;\r\n    display:flex;gap:12px;justify-content:flex-end;flex-wrap:wrap;\r\n  }\r\n  .footer-actions button{\r\n    padding:12px 22px;border-radius:10px;font-size:14px;font-weight:700;\r\n    cursor:pointer;font-family:inherit;border:1.5px solid;\r\n  }\r\n  .btn-primary{background:#0f4c3a;color:#fff;border-color:#0f4c3a}\r\n  .btn-primary:hover{background:#1a7a5e;border-color:#1a7a5e}\r\n  .btn-secondary{background:#fff;color:#0f4c3a;border-color:#c8e6d5}\r\n  .btn-secondary:hover{background:#f0f9f5}\r\n  .toast{\r\n    position:fixed;bottom:24px;right:24px;left:24px;max-width:420px;margin-left:auto;\r\n    background:#0f4c3a;color:#fff;padding:12px 18px;border-radius:8px;\r\n    box-shadow:0 6px 20px rgba(0,0,0,.2);font-size:14px;font-weight:600;\r\n    transform:translateY(160%);transition:transform .3s;z-index:1000;\r\n  }\r\n  .toast.show{transform:translateY(0)}\r\n  .toast.error{background:#c0392b}\r\n  .empty-msg{color:#8a9a93;font-size:13px;font-style:italic;padding:8px 0}\r\n  input[type=\"file\"]{display:none}\r\n  .helper{\r\n    background:#f0f9f5;border-left:3px solid #1a7a5e;\r\n    padding:10px 14px;border-radius:0 8px 8px 0;\r\n    font-size:13px;color:#3a5a4f;margin-bottom:14px;\r\n  }\r\n  .quick-plan-card{\r\n    background:linear-gradient(135deg,#f0f9f5 0%,#e8f5ee 100%);\r\n    border:1.5px dashed #1a7a5e;\r\n  }\r\n  .quick-plan-card h2{color:#0f4c3a}\r\n  .day-input-row{\r\n    display:grid;grid-template-columns:60px 1fr;gap:10px;align-items:center;\r\n    margin-bottom:8px;\r\n  }\r\n  .day-input-label{\r\n    background:#0f4c3a;color:#fff;font-weight:700;font-size:12px;\r\n    padding:8px 0;border-radius:6px;text-align:center;letter-spacing:.4px;\r\n  }\r\n  .day-input-row input{\r\n    padding:9px 12px;border:1.5px solid #d6dfdb;border-radius:8px;\r\n    font-size:14px;font-family:inherit;width:100%;min-width:0;\r\n  }\r\n  .day-input-row input:focus{outline:none;border-color:#1a7a5e}\r\n  .quick-plan-actions{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}\r\n  .btn-generate{\r\n    background:#0f4c3a;color:#fff;border:none;\r\n    padding:10px 18px;border-radius:8px;font-size:13px;font-weight:700;\r\n    cursor:pointer;font-family:inherit;\r\n  }\r\n  .btn-generate:hover{background:#1a7a5e}\r\n  .btn-add-day{\r\n    background:#fff;color:#0f4c3a;border:1.5px solid #1a7a5e;\r\n    padding:10px 14px;border-radius:8px;font-size:13px;font-weight:600;\r\n    cursor:pointer;font-family:inherit;\r\n  }\r\n  .quick-plan-hint{\r\n    font-size:12px;color:#3a5a4f;margin-top:6px;font-style:italic;\r\n    line-height:1.5;\r\n  }\r\n\r\n  \/* ====== TABLET ====== *\/\r\n  @media(max-width:900px){\r\n    .g4{grid-template-columns:1fr 1fr}\r\n  }\r\n\r\n  \/* ====== MOBILE ====== *\/\r\n  @media(max-width:680px){\r\n    body{padding:12px}\r\n    .container{padding:0}\r\n    header{\r\n      padding:18px 18px;\r\n      border-radius:12px;\r\n      margin-bottom:16px;\r\n      gap:12px;\r\n    }\r\n    header > div:first-child{width:100%}\r\n    header h1{font-size:18px;line-height:1.3}\r\n    header .sub{font-size:12px}\r\n    header .actions{width:100%;gap:6px}\r\n    header button{\r\n      flex:1 1 auto;min-width:0;\r\n      padding:8px 10px;font-size:12px;\r\n      text-align:center;\r\n    }\r\n    .helper{\r\n      font-size:12px;padding:10px 12px;\r\n      border-radius:0 6px 6px 0;\r\n    }\r\n    .form-card{\r\n      padding:16px 16px;\r\n      margin-bottom:12px;\r\n      border-radius:12px;\r\n    }\r\n    .form-card h2{\r\n      font-size:13px;\r\n      margin-bottom:12px;\r\n    }\r\n    .g2,.g3,.g4{grid-template-columns:1fr;gap:12px}\r\n    .grid{gap:12px}\r\n    .field input,.field select,.field textarea{\r\n      font-size:16px;\r\n      padding:10px 12px;\r\n    }\r\n    .day-card,.hotel-row,.addon-row{\r\n      padding:12px 12px;\r\n      border-radius:10px;\r\n    }\r\n    .day-card .day-num{\r\n      top:12px;left:12px;\r\n      font-size:11px;padding:3px 8px;\r\n    }\r\n    .day-card .day-fields{margin-top:30px}\r\n    .remove-btn{\r\n      top:8px;right:8px;\r\n      padding:4px 8px;font-size:10px;\r\n    }\r\n    .addon-row .grid{padding-right:0 !important}\r\n    .addon-row .remove-btn{position:relative;top:auto;right:auto;float:right;margin-bottom:8px}\r\n    .footer-actions{\r\n      padding:14px 14px;\r\n      flex-direction:column;\r\n      gap:10px;\r\n    }\r\n    .footer-actions button{\r\n      width:100%;\r\n      padding:13px 18px;\r\n      font-size:14px;\r\n    }\r\n    .quick-plan-card{padding:16px}\r\n    .quick-plan-hint{font-size:11px;line-height:1.5}\r\n    .day-input-row{grid-template-columns:50px 1fr;gap:8px}\r\n    .day-input-label{font-size:11px;padding:9px 0}\r\n    .day-input-row input{font-size:16px;padding:10px 12px}\r\n    .quick-plan-actions{flex-direction:column;gap:8px}\r\n    .quick-plan-actions button{width:100%}\r\n    .add-btn{width:100%;padding:11px 16px}\r\n    .toast{\r\n      right:12px;left:12px;bottom:12px;\r\n      font-size:13px;padding:11px 14px;\r\n    }\r\n  }\r\n\r\n  \/* ====== SMALL MOBILE (<400px) ====== *\/\r\n  @media(max-width:400px){\r\n    body{padding:10px}\r\n    header{padding:16px 14px}\r\n    header h1{font-size:17px}\r\n    .form-card{padding:14px 14px}\r\n    .day-card,.hotel-row,.addon-row{padding:10px 10px}\r\n  }\r\n<\/style>\r\n<\/head>\r\n<body>\r\n<div class=\"container\">\r\n\r\n<header>\r\n  <div>\r\n    <h1>HimTrails \u2014 Custom Itinerary Builder<\/h1>\r\n    <div class=\"sub\">Generate brand-grade PDF quotes for confirmed customers<\/div>\r\n  <\/div>\r\n  <div class=\"actions\">\r\n    <button onclick=\"loadDraft()\">Load Draft<\/button>\r\n    <input type=\"file\" id=\"draft-file\" accept=\".json\" onchange=\"handleDraftFile(event)\">\r\n    <button onclick=\"saveDraft()\">Save Draft<\/button>\r\n    <button onclick=\"resetForm()\">Reset<\/button>\r\n  <\/div>\r\n<\/header>\r\n\r\n<div class=\"helper\">\r\n  Fill the form below, then click <strong>Generate PDF<\/strong> at the bottom. Save your work as a draft (.json file) any time \u2014 reload it later to edit. Covers all Himachal destinations <strong>+ Leh Ladakh circuit<\/strong>.\r\n<\/div>\r\n\r\n<!-- Customer Block -->\r\n<div class=\"form-card\">\r\n  <h2>Customer Details<\/h2>\r\n  <div class=\"grid g2\">\r\n    <div class=\"field\">\r\n      <label>Customer Name <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"cust_name\" type=\"text\" placeholder=\"e.g. Praveen Chauhan\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Phone <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"cust_phone\" type=\"text\" placeholder=\"+91 9876543210\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Email<\/label>\r\n      <input id=\"cust_email\" type=\"email\" placeholder=\"Optional\">\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>City \/ From<\/label>\r\n      <input id=\"cust_from\" type=\"text\" placeholder=\"e.g. Delhi\">\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Adults <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"pax_adults\" type=\"number\" min=\"1\" value=\"2\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Children<\/label>\r\n      <input id=\"pax_children\" type=\"number\" min=\"0\" value=\"0\">\r\n    <\/div>\r\n  <\/div>\r\n<\/div>\r\n\r\n<!-- Trip Block -->\r\n<div class=\"form-card\">\r\n  <h2>Trip Overview<\/h2>\r\n  <div class=\"grid g3\">\r\n    <div class=\"field\">\r\n      <label>Trip Title <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"trip_title\" type=\"text\" placeholder=\"e.g. Shimla + Kinnaur + Spiti\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Trip Subtitle \/ Short Pitch<\/label>\r\n      <input id=\"trip_subtitle\" type=\"text\" placeholder=\"e.g. The full Tribal Circuit - Shimla, Sangla, Chitkul...\">\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Trip Tags (e.g. Family | From Delhi)<\/label>\r\n      <input id=\"trip_tags\" type=\"text\" placeholder=\"Family | From Delhi\">\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Trip Start Date <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"trip_start\" type=\"date\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Total Cost (\u20b9) <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"trip_cost\" type=\"number\" min=\"0\" placeholder=\"60000\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Vehicle <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"trip_vehicle\" type=\"text\" placeholder=\"Suzuki Dzire\" required>\r\n    <\/div>\r\n  <\/div>\r\n<\/div>\r\n\r\n<!-- Pickup & Drop -->\r\n<div class=\"form-card\">\r\n  <h2>Pickup & Drop<\/h2>\r\n  <div class=\"grid g3\">\r\n    <div class=\"field\">\r\n      <label>Pickup Location <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"pickup_loc\" type=\"text\" placeholder=\"Delhi Airport\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Pickup Date <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"pickup_date\" type=\"date\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Pickup Time <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"pickup_time\" type=\"time\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Drop Location <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"drop_loc\" type=\"text\" placeholder=\"Chandigarh Airport\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Drop Date <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"drop_date\" type=\"date\" required>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Drop Time <span class=\"req\">*<\/span><\/label>\r\n      <input id=\"drop_time\" type=\"time\" required>\r\n    <\/div>\r\n  <\/div>\r\n<\/div>\r\n\r\n<!-- Quick Plan Generator -->\r\n<div class=\"form-card quick-plan-card\">\r\n  <h2>\u26a1 Quick Plan \u2014 Auto-Generate Itinerary<\/h2>\r\n  <div class=\"quick-plan-hint\">\r\n    Type each day as comma-separated <strong>destinations + activities<\/strong>. Example: <em>Chandigarh, Shimla, Shimla Sightseeing<\/em> or <em>Leh, Nubra, Camel Ride<\/em> \u2014 and we auto-fill route, hotel, distance, drive time, activities &amp; meals for every day. Works for all of Himachal + the full Leh Ladakh circuit.\r\n  <\/div>\r\n  <div id=\"quick-plan-rows\" style=\"margin-top:14px\"><\/div>\r\n  <div class=\"quick-plan-actions\">\r\n    <button class=\"btn-add-day\" onclick=\"addQuickPlanRow()\">+ Add Day<\/button>\r\n    <button class=\"btn-generate\" onclick=\"generateFromQuickPlan()\">\u26a1 Generate Day-by-Day Plan<\/button>\r\n  <\/div>\r\n<\/div>\r\n\r\n<!-- Day-by-Day Plan -->\r\n<div class=\"form-card\">\r\n  <h2>Day-by-Day Plan<\/h2>\r\n  <div id=\"days-container\"><\/div>\r\n  <button class=\"add-btn\" onclick=\"addDay()\">Add Day Manually<\/button>\r\n<\/div>\r\n\r\n<!-- Inclusions \/ Exclusions -->\r\n<div class=\"form-card\">\r\n  <h2>Inclusions &amp; Exclusions<\/h2>\r\n  <div class=\"grid g2\">\r\n    <div class=\"field\">\r\n      <label>Inclusions (one per line)<\/label>\r\n      <textarea id=\"inclusions\" rows=\"6\">Accommodation\r\nTransportation\r\nMeals (as per itinerary)\r\n24x7 Local Support<\/textarea>\r\n    <\/div>\r\n    <div class=\"field\">\r\n      <label>Exclusions (one per line)<\/label>\r\n      <textarea id=\"exclusions\" rows=\"6\">Personal Expenses\r\nTravel Insurance\r\nActivities \/ Guides\r\nAll Permits & Passes<\/textarea>\r\n    <\/div>\r\n  <\/div>\r\n<\/div>\r\n\r\n<!-- Documents & Local Tip Overrides -->\r\n<div class=\"form-card\">\r\n  <h2>Auto-filled Sections (override only if needed)<\/h2>\r\n  <div class=\"quick-plan-hint\" style=\"margin-bottom:14px\">\r\n    <strong>At-a-Glance, Documents &amp; Permits, and Local Tips<\/strong> are automatically filled in the PDF based on the destinations in your day-by-day plan (Himachal + Ladakh aware). Only fill the boxes below if you want to <em>override<\/em> the auto-generated content for this specific quote.\r\n  <\/div>\r\n  <div class=\"field\" style=\"margin-bottom:14px\">\r\n    <label>Documents \/ Permits Override (one per line \u2014 leave blank for auto-fill)<\/label>\r\n    <textarea id=\"documents\" rows=\"4\" placeholder=\"Leave blank to auto-fill based on destinations\"><\/textarea>\r\n  <\/div>\r\n  <div class=\"field\">\r\n    <label>Local Tip Override (leave blank for auto-fill)<\/label>\r\n    <textarea id=\"local_tip\" rows=\"3\" placeholder=\"Leave blank to auto-fill based on destinations\"><\/textarea>\r\n  <\/div>\r\n<\/div>\r\n\r\n<!-- Optional Add-ons -->\r\n<div class=\"form-card\">\r\n  <h2>Optional Add-ons (skip if none)<\/h2>\r\n  <div id=\"addons-container\"><\/div>\r\n  <button class=\"add-btn\" onclick=\"addAddon()\">Add Optional Add-on<\/button>\r\n<\/div>\r\n\r\n<!-- Footer Actions -->\r\n<div class=\"footer-actions\">\r\n  <button class=\"btn-secondary\" onclick=\"saveDraft()\">Save Draft<\/button>\r\n  <button class=\"btn-primary\" onclick=\"generatePDF()\">Generate PDF<\/button>\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n<div class=\"toast\" id=\"toast\"><\/div>\r\n\r\n<script>\r\nconst DEFAULT_INCLUSIONS = [\r\n  'Accommodation',\r\n  'Transportation',\r\n  'Meals (as per itinerary)',\r\n  '24x7 Local Support'\r\n];\r\nconst DEFAULT_EXCLUSIONS = [\r\n  'Personal Expenses',\r\n  'Travel Insurance',\r\n  'Activities \/ Guides',\r\n  'All Permits & Passes'\r\n];\r\n\r\n\/* ==========================================================================\r\n   DISTANCE TABLE \u2014 Himachal + Leh Ladakh\r\n   ========================================================================== *\/\r\nconst DISTANCE_TABLE = {\r\n  \/* --- Plains to Himachal --- *\/\r\n  'delhi|chandigarh': {km:245, hrs:'5 hrs'},\r\n  'delhi|shimla': {km:355, hrs:'8 hrs'},\r\n  'delhi|manali': {km:540, hrs:'12 hrs'},\r\n  'delhi|kasol': {km:520, hrs:'12 hrs'},\r\n  'delhi|dharamshala': {km:475, hrs:'10 hrs'},\r\n  'delhi|dalhousie': {km:560, hrs:'11 hrs'},\r\n  'delhi|kasauli': {km:295, hrs:'6 hrs'},\r\n  'delhi|jibhi': {km:500, hrs:'11 hrs'},\r\n  'chandigarh|shimla': {km:115, hrs:'3.5 hrs'},\r\n  'chandigarh|manali': {km:310, hrs:'8 hrs'},\r\n  'chandigarh|kasol': {km:285, hrs:'8 hrs'},\r\n  'chandigarh|dharamshala': {km:240, hrs:'6 hrs'},\r\n  'chandigarh|dalhousie': {km:325, hrs:'7.5 hrs'},\r\n  'chandigarh|kasauli': {km:65, hrs:'2 hrs'},\r\n  'chandigarh|mcleodganj': {km:250, hrs:'6.5 hrs'},\r\n  'chandigarh|jibhi': {km:265, hrs:'7 hrs'},\r\n  'chandigarh|mandi': {km:200, hrs:'5 hrs'},\r\n  \/* --- Shimla circuit --- *\/\r\n  'shimla|manali': {km:250, hrs:'7 hrs'},\r\n  'shimla|kufri': {km:16, hrs:'45 mins'},\r\n  'shimla|narkanda': {km:65, hrs:'2.5 hrs'},\r\n  'shimla|chail': {km:45, hrs:'1.5 hrs'},\r\n  'shimla|kasol': {km:215, hrs:'7 hrs'},\r\n  'shimla|kasauli': {km:75, hrs:'2.5 hrs'},\r\n  'shimla|tattapani': {km:55, hrs:'2 hrs'},\r\n  'shimla|mashobra': {km:12, hrs:'30 mins'},\r\n  'shimla|naldehra': {km:22, hrs:'1 hr'},\r\n  'shimla|jibhi': {km:150, hrs:'5 hrs'},\r\n  'shimla|sarahan': {km:165, hrs:'5.5 hrs'},\r\n  'shimla|sangla': {km:230, hrs:'8 hrs'},\r\n  'kufri|fagu': {km:6, hrs:'20 mins'},\r\n  \/* --- Kinnaur & Spiti circuit --- *\/\r\n  'sarahan|sangla': {km:90, hrs:'3.5 hrs'},\r\n  'sangla|chitkul': {km:25, hrs:'1 hr'},\r\n  'sangla|kalpa': {km:55, hrs:'2 hrs'},\r\n  'sangla|rakcham': {km:14, hrs:'30 mins'},\r\n  'kalpa|reckong peo': {km:12, hrs:'30 mins'},\r\n  'kalpa|nako': {km:105, hrs:'4 hrs'},\r\n  'nako|tabo': {km:65, hrs:'2 hrs'},\r\n  'tabo|kaza': {km:47, hrs:'1.5 hrs'},\r\n  'tabo|dhankar': {km:32, hrs:'1 hr'},\r\n  'dhankar|kaza': {km:32, hrs:'1 hr'},\r\n  'kaza|key': {km:14, hrs:'30 mins'},\r\n  'kaza|kibber': {km:19, hrs:'45 mins'},\r\n  'kaza|langza': {km:16, hrs:'45 mins'},\r\n  'kaza|hikkim': {km:16, hrs:'45 mins'},\r\n  'kaza|komic': {km:18, hrs:'1 hr'},\r\n  'kaza|pin valley': {km:50, hrs:'2 hrs'},\r\n  'kaza|chandratal': {km:95, hrs:'4 hrs'},\r\n  'kaza|manali': {km:200, hrs:'8 hrs'},\r\n  'chandratal|manali': {km:120, hrs:'5 hrs'},\r\n  \/* --- Manali \/ Kullu \/ Parvati --- *\/\r\n  'manali|kasol': {km:75, hrs:'2.5 hrs'},\r\n  'manali|solang': {km:13, hrs:'30 mins'},\r\n  'manali|rohtang': {km:51, hrs:'2 hrs'},\r\n  'manali|atal tunnel': {km:25, hrs:'45 mins'},\r\n  'manali|sissu': {km:40, hrs:'1.5 hrs'},\r\n  'manali|naggar': {km:21, hrs:'45 mins'},\r\n  'manali|kullu': {km:40, hrs:'1.5 hrs'},\r\n  'manali|dharamshala': {km:240, hrs:'7 hrs'},\r\n  'manali|jibhi': {km:100, hrs:'3 hrs'},\r\n  'manali|keylong': {km:115, hrs:'3.5 hrs'},\r\n  'kasol|tosh': {km:18, hrs:'1 hr'},\r\n  'kasol|kheerganga': {km:13, hrs:'1 hr drive + trek'},\r\n  'kasol|manikaran': {km:4, hrs:'15 mins'},\r\n  'kasol|malana': {km:25, hrs:'1.5 hrs'},\r\n  \/* --- Jibhi \/ Tirthan \/ Mandi --- *\/\r\n  'jibhi|tirthan': {km:20, hrs:'45 mins'},\r\n  'jibhi|shoja': {km:8, hrs:'20 mins'},\r\n  'jibhi|jalori': {km:12, hrs:'40 mins'},\r\n  'mandi|prashar': {km:49, hrs:'2 hrs'},\r\n  'mandi|rewalsar': {km:24, hrs:'1 hr'},\r\n  'mandi|manali': {km:110, hrs:'3.5 hrs'},\r\n  'mandi|barot': {km:65, hrs:'2.5 hrs'},\r\n  'bir|barot': {km:55, hrs:'2 hrs'},\r\n  \/* --- Kangra \/ Chamba --- *\/\r\n  'dharamshala|mcleodganj': {km:9, hrs:'30 mins'},\r\n  'dharamshala|dalhousie': {km:120, hrs:'4 hrs'},\r\n  'dharamshala|bir': {km:50, hrs:'1.5 hrs'},\r\n  'dharamshala|palampur': {km:35, hrs:'1 hr'},\r\n  'mcleodganj|triund': {km:9, hrs:'1 hr drive + trek'},\r\n  'mcleodganj|bhagsu': {km:2, hrs:'10 mins'},\r\n  'dalhousie|khajjiar': {km:23, hrs:'1 hr'},\r\n  'dalhousie|chamba': {km:50, hrs:'1.5 hrs'},\r\n  \/* --- Manali\u2013Leh axis --- *\/\r\n  'manali|jispa': {km:140, hrs:'5 hrs'},\r\n  'manali|sarchu': {km:220, hrs:'7 hrs'},\r\n  'jispa|leh': {km:335, hrs:'9 hrs'},\r\n  'jispa|sarchu': {km:85, hrs:'2.5 hrs'},\r\n  'sarchu|leh': {km:255, hrs:'7 hrs'},\r\n  'keylong|jispa': {km:22, hrs:'45 mins'},\r\n  'manali|leh': {km:475, hrs:'2 days (halt at Jispa\/Sarchu)'},\r\n  \/* --- Ladakh circuit --- *\/\r\n  'leh|khardung la': {km:40, hrs:'1.5 hrs'},\r\n  'leh|nubra': {km:120, hrs:'4.5 hrs'},\r\n  'leh|diskit': {km:118, hrs:'4.5 hrs'},\r\n  'leh|hunder': {km:125, hrs:'5 hrs'},\r\n  'leh|turtuk': {km:205, hrs:'7 hrs'},\r\n  'diskit|hunder': {km:8, hrs:'20 mins'},\r\n  'diskit|turtuk': {km:90, hrs:'3 hrs'},\r\n  'nubra|turtuk': {km:90, hrs:'3 hrs'},\r\n  'nubra|pangong': {km:160, hrs:'6 hrs (via Shyok)'},\r\n  'leh|pangong': {km:160, hrs:'5 hrs'},\r\n  'pangong|tso moriri': {km:235, hrs:'8 hrs'},\r\n  'pangong|hanle': {km:180, hrs:'6 hrs'},\r\n  'leh|tso moriri': {km:220, hrs:'7 hrs'},\r\n  'leh|hanle': {km:255, hrs:'7.5 hrs'},\r\n  'tso moriri|tso kar': {km:90, hrs:'2.5 hrs'},\r\n  'leh|lamayuru': {km:110, hrs:'3 hrs'},\r\n  'leh|alchi': {km:65, hrs:'1.5 hrs'},\r\n  'leh|sham valley': {km:70, hrs:'2 hrs'},\r\n  'leh|magnetic hill': {km:28, hrs:'45 mins'},\r\n  'leh|kargil': {km:215, hrs:'5.5 hrs'},\r\n  'kargil|srinagar': {km:200, hrs:'5.5 hrs'},\r\n  'lamayuru|kargil': {km:105, hrs:'3 hrs'},\r\n  'srinagar|leh': {km:420, hrs:'2 days (halt at Kargil)'}\r\n};\r\n\r\nfunction lookupDistance(from, to){\r\n  if(!from || !to) return null;\r\n  const a = normalizeDest(from), b = normalizeDest(to);\r\n  if(!a || !b || a === b) return null;\r\n  const k1 = a + '|' + b, k2 = b + '|' + a;\r\n  if(DISTANCE_TABLE[k1]) return DISTANCE_TABLE[k1];\r\n  if(DISTANCE_TABLE[k2]) return DISTANCE_TABLE[k2];\r\n  \/\/ fuzzy fallback \u2014 only match keys of 4+ chars to avoid false hits on short names\r\n  for(const key in DISTANCE_TABLE){\r\n    const [x,y] = key.split('|');\r\n    if(x.length < 4 || y.length < 4) continue;\r\n    if((a.includes(x) && b.includes(y)) || (a.includes(y) && b.includes(x))){\r\n      return DISTANCE_TABLE[key];\r\n    }\r\n  }\r\n  return null;\r\n}\r\n\r\n\/* ==========================================================================\r\n   ACTIVITY TEMPLATES \u2014 Himachal + Ladakh\r\n   ========================================================================== *\/\r\nconst ACTIVITY_TEMPLATES = {\r\n  'sightseeing': [\r\n    'Visit popular local landmarks and viewpoints',\r\n    'Explore the main market area and try local cuisine',\r\n    'Photo stops at scenic spots'\r\n  ],\r\n  'shimla sightseeing': [\r\n    'Visit The Mall Road, Christ Church and Ridge',\r\n    'Explore Jakhu Temple with views of the city',\r\n    'Walk through Lakkar Bazaar for local handicrafts'\r\n  ],\r\n  'kufri': [\r\n    'Drive up to Kufri (snow point in winter)',\r\n    'Visit Himalayan Nature Park',\r\n    'Optional yak ride and adventure activities'\r\n  ],\r\n  'naldehra': [\r\n    'Visit Naldehra golf course amid cedar forests',\r\n    'Optional horse riding and nature walks'\r\n  ],\r\n  'mashobra': [\r\n    'Nature walks through Mashobra orchards and forests',\r\n    'Visit Craignano and the Presidential Retreat area'\r\n  ],\r\n  'sarahan': [\r\n    'Visit the ancient Bhimakali Temple',\r\n    'Views of Shrikhand Mahadev peak',\r\n    'Walk through Sarahan bird park and village lanes'\r\n  ],\r\n  'sangla': [\r\n    'Drive through the scenic Sangla Valley along the Baspa river',\r\n    'Visit Kamru Fort and Sangla village',\r\n    'Riverside walks and apple orchard photo stops'\r\n  ],\r\n  'chitkul': [\r\n    'Visit Chitkul - India\\'s last village before the Tibet border',\r\n    'Walk along the Baspa river and village wooden houses',\r\n    'Tea at the \"Hindustan ka Aakhri Dhaba\"'\r\n  ],\r\n  'kalpa': [\r\n    'Sunrise views of the Kinner Kailash range',\r\n    'Visit Kalpa village, monastery and Roghi village',\r\n    'Photo stop at Suicide Point cliff road'\r\n  ],\r\n  'nako': [\r\n    'Visit Nako Lake and the 11th-century Nako Monastery',\r\n    'Walk through the mud-brick village lanes'\r\n  ],\r\n  'tabo': [\r\n    'Visit Tabo Monastery (996 CE) - the \"Ajanta of the Himalayas\"',\r\n    'Explore the meditation caves above the village'\r\n  ],\r\n  'dhankar': [\r\n    'Visit the cliff-top Dhankar Monastery and fort',\r\n    'Optional short trek to Dhankar Lake',\r\n    'Confluence views of Spiti and Pin rivers'\r\n  ],\r\n  'kaza': [\r\n    'Explore Kaza market and Sakya monastery',\r\n    'World\\'s highest fuel station photo stop',\r\n    'Cafe time with Spiti valley views'\r\n  ],\r\n  'key': [\r\n    'Visit the iconic Key Monastery - largest in Spiti',\r\n    'Interact with monks and see ancient thangkas'\r\n  ],\r\n  'kibber': [\r\n    'Visit Kibber village and wildlife sanctuary area',\r\n    'Chicham Bridge - Asia\\'s highest suspension bridge'\r\n  ],\r\n  'langza': [\r\n    'Visit Langza - the fossil village with the giant Buddha statue',\r\n    'Fossil hunting with local kids'\r\n  ],\r\n  'hikkim': [\r\n    'Send a postcard from the world\\'s highest post office at Hikkim'\r\n  ],\r\n  'komic': [\r\n    'Visit Komic - among the world\\'s highest motorable villages',\r\n    'Tangyud Monastery visit'\r\n  ],\r\n  'pin valley': [\r\n    'Drive into Pin Valley National Park up to Mudh village',\r\n    'Spot Himalayan wildlife and unique Pin landscapes'\r\n  ],\r\n  'chandratal': [\r\n    'Visit the crescent-shaped Chandratal (Moon Lake)',\r\n    'Short lakeside walk and photography',\r\n    'Optional overnight camp near the lake'\r\n  ],\r\n  'manali sightseeing': [\r\n    'Visit Hadimba Temple and Manu Temple',\r\n    'Stroll along Mall Road and Old Manali cafes',\r\n    'Vashisht hot springs and Tibetan monastery'\r\n  ],\r\n  'solang': [\r\n    'Visit Solang Valley for snow & adventure activities',\r\n    'Optional ropeway, paragliding and zorbing',\r\n    'Photo stops along the Beas river'\r\n  ],\r\n  'rohtang': [\r\n    'Drive to Rohtang Pass \/ Atal Tunnel (subject to permits & weather)',\r\n    'Snow point experience and photography',\r\n    'Sissu \/ Koksar valley views (via tunnel)'\r\n  ],\r\n  'atal tunnel': [\r\n    'Drive through the iconic Atal Tunnel',\r\n    'Visit Sissu village and waterfall',\r\n    'Photo stops in Lahaul valley'\r\n  ],\r\n  'kasol': [\r\n    'Riverside walks along the Parvati river',\r\n    'Explore Kasol market and Israeli cafes',\r\n    'Short visit to Manikaran Sahib gurudwara'\r\n  ],\r\n  'tosh': [\r\n    'Drive \/ short trek to Tosh village',\r\n    'Cafes with mountain views',\r\n    'Photography in apple orchards'\r\n  ],\r\n  'kheerganga': [\r\n    'Trek to Kheerganga (10-12 km round trip)',\r\n    'Visit the natural hot springs',\r\n    'Camp \/ day return depending on plan'\r\n  ],\r\n  'manikaran': [\r\n    'Visit Manikaran Sahib Gurudwara',\r\n    'Experience the natural hot springs',\r\n    'Riverside time at Parvati'\r\n  ],\r\n  'jibhi': [\r\n    'Visit Jibhi waterfall and Mini Thailand',\r\n    'Riverside cafes and wooden-cottage village walks',\r\n    'Optional drive to Jalori Pass and Serolsar Lake trek'\r\n  ],\r\n  'tirthan': [\r\n    'Riverside time along the Tirthan river',\r\n    'Optional trout fishing (permit-based)',\r\n    'Village walks around Gushaini and GHNP gate'\r\n  ],\r\n  'shoja': [\r\n    'Forest walks around Shoja village',\r\n    'Views from Jalori Pass and Raghupur Fort trek'\r\n  ],\r\n  'prashar': [\r\n    'Drive\/trek to Prashar Lake with its floating island',\r\n    '360-degree Dhauladhar views from the pagoda temple'\r\n  ],\r\n  'rewalsar': [\r\n    'Visit Rewalsar Lake - sacred to Buddhists, Hindus & Sikhs',\r\n    'Giant Padmasambhava statue and monasteries'\r\n  ],\r\n  'barot': [\r\n    'Explore the Barot valley along the Uhl river',\r\n    'Optional trout fishing and forest walks'\r\n  ],\r\n  'horseriding': [\r\n    'Horse riding through the meadows',\r\n    'Local viewpoint photography'\r\n  ],\r\n  'horse riding': [\r\n    'Horse riding through the meadows',\r\n    'Local viewpoint photography'\r\n  ],\r\n  'paragliding': [\r\n    'Paragliding session (subject to weather)',\r\n    'Pre-flight briefing with certified pilot'\r\n  ],\r\n  'river rafting': [\r\n    'White water rafting session on the Beas',\r\n    'Safety briefing and equipment provided'\r\n  ],\r\n  'rafting': [\r\n    'White water rafting on the river',\r\n    'Safety briefing and equipment included'\r\n  ],\r\n  'trekking': [\r\n    'Guided trek with local route expert',\r\n    'Scenic viewpoints and photography stops'\r\n  ],\r\n  'trek': [\r\n    'Guided trek along scenic mountain trails',\r\n    'Tea \/ lunch break at viewpoint'\r\n  ],\r\n  'shopping': [\r\n    'Free time for shopping at local markets',\r\n    'Pick up handicrafts, shawls and souvenirs'\r\n  ],\r\n  'mall road': [\r\n    'Evening walk on Mall Road',\r\n    'Cafes, shopping and people-watching'\r\n  ],\r\n  'hadimba': [\r\n    'Visit the cedar-shaded Hadimba Temple',\r\n    'Photo stops in the surrounding forest'\r\n  ],\r\n  'jakhu': [\r\n    'Visit Jakhu Temple with the giant Hanuman statue',\r\n    'Panoramic views of Shimla town'\r\n  ],\r\n  'mcleodganj': [\r\n    'Visit Tsuglagkhang Complex (Dalai Lama Temple)',\r\n    'Walk through McLeodganj market',\r\n    'Bhagsunag waterfall (seasonal)'\r\n  ],\r\n  'triund': [\r\n    'Trek to Triund (9 km, half-day)',\r\n    'Panoramic views of Dhauladhar range',\r\n    'Optional packed lunch at the top'\r\n  ],\r\n  'khajjiar': [\r\n    'Visit Khajjiar - the \"Mini Switzerland of India\"',\r\n    'Walk around the meadow & Khajjiar Lake',\r\n    'Photo stops in deodar forests'\r\n  ],\r\n  'dalhousie': [\r\n    'Visit Panchpula and Subhash Baoli',\r\n    'Walk along The Mall and St. Johns Church',\r\n    'Sunset point at Dainkund (if time permits)'\r\n  ],\r\n  'bir': [\r\n    'Visit Bir village and Tibetan colony',\r\n    'Optional paragliding from Billing (subject to weather)',\r\n    'Cafe-hopping in Bir'\r\n  ],\r\n  \/* --- Ladakh --- *\/\r\n  'leh sightseeing': [\r\n    'Visit Leh Palace and Shanti Stupa (sunset views)',\r\n    'Explore Leh Main Bazaar and Tibetan markets',\r\n    'Hall of Fame war museum and Spituk Monastery'\r\n  ],\r\n  'leh': [\r\n    'Arrival at Leh - full rest for acclimatisation',\r\n    'Easy evening walk at Shanti Stupa \/ Leh market',\r\n    'Hydrate well; avoid exertion on arrival day'\r\n  ],\r\n  'khardung la': [\r\n    'Drive over Khardung La (5,359 m) - among the world\\'s highest motorable passes',\r\n    'Photo stop at the top (10-15 min max at this altitude)'\r\n  ],\r\n  'nubra': [\r\n    'Visit Diskit Monastery with the 32 m Maitreya Buddha',\r\n    'Sand dunes of Hunder - double-humped Bactrian camel ride',\r\n    'Sunset over the Shyok river valley'\r\n  ],\r\n  'diskit': [\r\n    'Visit Diskit Monastery and the giant Maitreya Buddha statue',\r\n    'Views over the Nubra-Shyok confluence'\r\n  ],\r\n  'hunder': [\r\n    'Bactrian double-humped camel ride on the Hunder sand dunes',\r\n    'ATV rides and dune photography (optional)'\r\n  ],\r\n  'turtuk': [\r\n    'Visit Turtuk - Balti village opened to tourists in 2010',\r\n    'Walk through apricot orchards and stone lanes',\r\n    'Views towards the Karakoram \/ LOC ridgelines'\r\n  ],\r\n  'pangong': [\r\n    'Drive to Pangong Tso (4,225 m) - the famous blue lake',\r\n    'Lakeside photography at the 3 Idiots point',\r\n    'Overnight camp\/homestay by the lake (carry warm layers)'\r\n  ],\r\n  'tso moriri': [\r\n    'Visit the high-altitude Tso Moriri lake at Korzok',\r\n    'Spot migratory birds and Changpa nomad camps',\r\n    'Korzok Monastery visit'\r\n  ],\r\n  'hanle': [\r\n    'Visit Hanle Monastery and the Indian Astronomical Observatory',\r\n    'Stargazing in India\\'s first Dark Sky Reserve',\r\n    'Milky Way photography (moonless nights are best)'\r\n  ],\r\n  'lamayuru': [\r\n    'Visit Lamayuru Monastery - among Ladakh\\'s oldest',\r\n    'Photo stops at the surreal \"Moonland\" landscapes'\r\n  ],\r\n  'alchi': [\r\n    'Visit the 11th-century Alchi Monastery murals',\r\n    'Riverside walk along the Indus'\r\n  ],\r\n  'sham valley': [\r\n    'Magnetic Hill optical-illusion stop',\r\n    'Gurudwara Pathar Sahib visit',\r\n    'Sangam - confluence of Indus & Zanskar rivers',\r\n    'Hall of Fame museum'\r\n  ],\r\n  'magnetic hill': [\r\n    'Experience the Magnetic Hill optical illusion',\r\n    'Photo stop on the Leh-Kargil highway'\r\n  ],\r\n  'sangam': [\r\n    'Visit Sangam - confluence of the Indus & Zanskar rivers',\r\n    'Optional river rafting at the confluence (seasonal)'\r\n  ],\r\n  'kargil': [\r\n    'Visit the Kargil War Memorial at Dras (en route)',\r\n    'Overnight halt on the Srinagar-Leh axis'\r\n  ],\r\n  'camel ride': [\r\n    'Double-humped Bactrian camel ride on the sand dunes'\r\n  ],\r\n  'stargazing': [\r\n    'Night-sky stargazing session (moonless nights are best)',\r\n    'Astro-photography of the Milky Way'\r\n  ],\r\n  'leisure': [\r\n    'Free day at leisure',\r\n    'Optional spa or in-hotel relaxation',\r\n    'Explore the property and surroundings'\r\n  ],\r\n  'acclimatisation': [\r\n    'Full rest day for high-altitude acclimatisation',\r\n    'Hydrate well, avoid alcohol and heavy exertion',\r\n    'Easy local walk in the evening if feeling fine'\r\n  ],\r\n  'arrival': [\r\n    'Welcome at pickup point and drive to hotel',\r\n    'Hotel check-in and refreshments',\r\n    'Evening at leisure'\r\n  ],\r\n  'departure': [\r\n    'Hotel check-out after breakfast',\r\n    'Drive back to drop point',\r\n    'Trip concludes with sweet memories'\r\n  ],\r\n  'check-in': [\r\n    'Hotel check-in and refreshments',\r\n    'Evening at leisure to acclimatise'\r\n  ],\r\n  'check in': [\r\n    'Hotel check-in and refreshments',\r\n    'Evening at leisure to acclimatise'\r\n  ]\r\n};\r\n\r\nfunction generateActivities(parts, isFirstDay, isLastDay){\r\n  const activities = [];\r\n  const used = new Set();\r\n\r\n  if(isFirstDay){\r\n    activities.push('Welcome at pickup point and drive to ' + (parts[parts.length-1] || 'destination'));\r\n  }\r\n\r\n  parts.forEach(p => {\r\n    const lower = p.toLowerCase().trim();\r\n    if(ACTIVITY_TEMPLATES[lower]){\r\n      ACTIVITY_TEMPLATES[lower].forEach(a => {\r\n        if(!used.has(a)){ activities.push(a); used.add(a); }\r\n      });\r\n      return;\r\n    }\r\n    \/\/ try normalised destination key (handles aliases like \"Pangong Lake\")\r\n    const norm = normalizeDest(p);\r\n    if(norm && ACTIVITY_TEMPLATES[norm]){\r\n      ACTIVITY_TEMPLATES[norm].forEach(a => {\r\n        if(!used.has(a)){ activities.push(a); used.add(a); }\r\n      });\r\n      return;\r\n    }\r\n    for(const key in ACTIVITY_TEMPLATES){\r\n      if(lower.includes(key) || key.includes(lower)){\r\n        ACTIVITY_TEMPLATES[key].forEach(a => {\r\n          if(!used.has(a)){ activities.push(a); used.add(a); }\r\n        });\r\n        return;\r\n      }\r\n    }\r\n    if(p.length > 1 && !lower.match(\/^(day\\s*\\d+|to|and|via)$\/)){\r\n      const generic = 'Visit ' + p.trim() + ' and explore the area';\r\n      if(!used.has(generic)){\r\n        activities.push(generic);\r\n        used.add(generic);\r\n      }\r\n    }\r\n  });\r\n\r\n  if(isLastDay){\r\n    activities.push('Hotel check-out and drive back to drop point');\r\n  }\r\n\r\n  return activities.slice(0, 5).join('\\n');\r\n}\r\n\r\nlet quickPlanRowCount = 0;\r\n\r\nfunction addQuickPlanRow(initial){\r\n  quickPlanRowCount++;\r\n  const container = document.getElementById('quick-plan-rows');\r\n  const idx = container.children.length + 1;\r\n  const div = document.createElement('div');\r\n  div.className = 'day-input-row';\r\n  div.innerHTML = `\r\n    <div class=\"day-input-label\">DAY ${idx}<\/div>\r\n    <input type=\"text\" class=\"quick-plan-input\" placeholder=\"e.g. Chandigarh, Shimla, Shimla Sightseeing\" value=\"${initial?escapeAttr(initial):''}\">\r\n  `;\r\n  container.appendChild(div);\r\n}\r\n\r\nfunction generateFromQuickPlan(){\r\n  const inputs = document.querySelectorAll('.quick-plan-input');\r\n  if(!inputs.length){\r\n    showToast('Add at least one day in Quick Plan', true);\r\n    return;\r\n  }\r\n\r\n  const rawDays = [];\r\n  inputs.forEach(inp => {\r\n    const val = inp.value.trim();\r\n    if(val) rawDays.push(val);\r\n  });\r\n\r\n  if(!rawDays.length){\r\n    showToast('Quick Plan rows are empty', true);\r\n    return;\r\n  }\r\n\r\n  document.getElementById('days-container').innerHTML = '';\r\n\r\n  let prevStay = '';\r\n\r\n  rawDays.forEach((line, idx) => {\r\n    const parts = line.split(',').map(s => s.trim()).filter(Boolean);\r\n    if(!parts.length) return;\r\n\r\n    const isFirstDay = idx === 0;\r\n    const isLastDay = idx === rawDays.length - 1;\r\n\r\n    \/\/ Day-1 usually has dinner only, last day breakfast only \u2014 matches real quotes\r\n    const defaultMeals = isFirstDay ? 'Dinner'\r\n                       : isLastDay  ? 'Breakfast'\r\n                       : 'Breakfast + Dinner';\r\n\r\n    const activityKeywords = ['sightseeing','horseriding','horse riding','paragliding','rafting',\r\n      'river rafting','trekking','trek','shopping','leisure','arrival','departure',\r\n      'check-in','check in','snow','snow point','adventure','camel ride','stargazing',\r\n      'acclimatisation','acclimatization'];\r\n    const places = [];\r\n    const activityParts = [];\r\n    parts.forEach(p => {\r\n      const lower = p.toLowerCase();\r\n      const isActivity = activityKeywords.some(k => lower === k || lower.endsWith(' ' + k) || lower.includes(' sightseeing'));\r\n      if(isActivity){\r\n        activityParts.push(p);\r\n      } else {\r\n        places.push(p);\r\n      }\r\n    });\r\n\r\n    let route = '';\r\n    let stay = '';\r\n    if(places.length >= 2){\r\n      route = places.join(' -> ');\r\n      stay = places[places.length - 1];\r\n    } else if(places.length === 1){\r\n      if(prevStay && places[0].toLowerCase() !== prevStay.toLowerCase()){\r\n        route = prevStay + ' -> ' + places[0];\r\n        stay = places[0];\r\n      } else {\r\n        route = places[0] + ' Local Sightseeing';\r\n        stay = places[0];\r\n      }\r\n    } else {\r\n      route = (prevStay || 'Local') + ' - Leisure \/ Activities';\r\n      stay = prevStay;\r\n    }\r\n\r\n    let dist = '';\r\n    if(places.length >= 2){\r\n      const d = lookupDistance(places[0], places[places.length-1]);\r\n      if(d) dist = d.km + ' km \/ ' + d.hrs;\r\n    } else if(places.length === 1 && prevStay && places[0].toLowerCase() !== prevStay.toLowerCase()){\r\n      const d = lookupDistance(prevStay, places[0]);\r\n      if(d) dist = d.km + ' km \/ ' + d.hrs;\r\n    }\r\n\r\n    const hotelName = stay ? 'Hotel in ' + stay + ' (or similar)' : '';\r\n\r\n    const acts = generateActivities(parts, isFirstDay, isLastDay);\r\n\r\n    addDay({\r\n      route: route,\r\n      stay: stay,\r\n      hotel: hotelName,\r\n      dist: dist,\r\n      acts: acts,\r\n      meals: defaultMeals\r\n    });\r\n\r\n    if(stay) prevStay = stay;\r\n  });\r\n\r\n  \/\/ Convenience: auto-fill drop date from trip start + trip length (only if blank)\r\n  const start = document.getElementById('trip_start').value;\r\n  const dropEl = document.getElementById('drop_date');\r\n  if(start && dropEl && !dropEl.value){\r\n    const dd = new Date(start + 'T00:00:00');\r\n    dd.setDate(dd.getDate() + rawDays.length - 1);\r\n    dropEl.value = dd.toISOString().slice(0,10);\r\n  }\r\n\r\n  showToast('Day-by-day plan generated! Review and tweak below.');\r\n  document.querySelector('#days-container').scrollIntoView({behavior:'smooth', block:'start'});\r\n}\r\n\r\nlet dayCount = 0;\r\nlet addonCount = 0;\r\n\r\nfunction showToast(msg, isError){\r\n  const t = document.getElementById('toast');\r\n  t.textContent = msg;\r\n  t.className = 'toast show' + (isError ? ' error' : '');\r\n  setTimeout(() => { t.className = 'toast' + (isError ? ' error' : ''); }, 2400);\r\n}\r\n\r\nfunction fmtDate(iso){\r\n  if(!iso) return '';\r\n  const d = new Date(iso + 'T00:00:00');\r\n  if(isNaN(d)) return iso;\r\n  const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];\r\n  return d.getDate() + ' ' + months[d.getMonth()] + ' ' + d.getFullYear();\r\n}\r\n\r\nfunction fmtTime12(t24){\r\n  if(!t24) return '';\r\n  const [h,m] = t24.split(':').map(Number);\r\n  const hh = h % 12 || 12;\r\n  const ampm = h < 12 ? 'AM' : 'PM';\r\n  return hh + ':' + (m<10?'0'+m:m) + ' ' + ampm;\r\n}\r\n\r\nfunction fmtMoneyPDF(n){\r\n  const num = Number(n||0);\r\n  return 'Rs. ' + num.toLocaleString('en-IN');\r\n}\r\n\r\nfunction dayDateFor(dayIdx){\r\n  const start = document.getElementById('trip_start').value;\r\n  if(!start) return '';\r\n  const d = new Date(start + 'T00:00:00');\r\n  d.setDate(d.getDate() + dayIdx);\r\n  return d.toISOString().slice(0,10);\r\n}\r\n\r\nfunction refreshAllDayDates(){\r\n  document.querySelectorAll('.day-card').forEach((card, i) => {\r\n    const dateInput = card.querySelector('.day-date');\r\n    if(dateInput && !dateInput.dataset.userOverride){\r\n      dateInput.value = dayDateFor(i);\r\n    }\r\n  });\r\n}\r\n\r\nfunction addDay(initial){\r\n  dayCount++;\r\n  const container = document.getElementById('days-container');\r\n  const div = document.createElement('div');\r\n  div.className = 'day-card';\r\n  const idx = container.children.length;\r\n  const defaultMeals = (initial && initial.meals) || 'Breakfast + Dinner';\r\n  div.innerHTML = `\r\n    <div class=\"day-num\">DAY ${idx+1}<\/div>\r\n    <button class=\"remove-btn\" onclick=\"removeDay(this)\">Remove<\/button>\r\n    <div class=\"day-fields\">\r\n      <div class=\"grid g2\">\r\n        <div class=\"field\">\r\n          <label>Date<\/label>\r\n          <input type=\"date\" class=\"day-date\" onchange=\"this.dataset.userOverride='1'\" value=\"${initial&&initial.date?initial.date:dayDateFor(idx)}\">\r\n        <\/div>\r\n        <div class=\"field\">\r\n          <label>Stay (city \/ \u2014)<\/label>\r\n          <input type=\"text\" class=\"day-stay\" placeholder=\"Kasol\" value=\"${initial?escapeAttr(initial.stay||''):''}\">\r\n        <\/div>\r\n      <\/div>\r\n      <div class=\"field\" style=\"margin-top:10px\">\r\n        <label>Day Title (short \u2014 for the day card heading)<\/label>\r\n        <input type=\"text\" class=\"day-title\" placeholder=\"e.g. Arrival in Shimla \/ Chitkul Full-Day\" value=\"${initial?escapeAttr(initial.title||''):''}\">\r\n      <\/div>\r\n      <div class=\"field\" style=\"margin-top:10px\">\r\n        <label>Route \/ Plan <span class=\"req\">*<\/span><\/label>\r\n        <input type=\"text\" class=\"day-route\" placeholder=\"Delhi -> Shimla\" value=\"${initial?escapeAttr(initial.route||''):''}\" required>\r\n      <\/div>\r\n      <div class=\"grid g2\" style=\"margin-top:10px\">\r\n        <div class=\"field\">\r\n          <label>Hotel Name (this night)<\/label>\r\n          <input type=\"text\" class=\"day-hotel\" placeholder=\"Adi Kasol\" value=\"${initial?escapeAttr(initial.hotel||''):''}\">\r\n        <\/div>\r\n        <div class=\"field\">\r\n          <label>Distance \/ Drive Time<\/label>\r\n          <input type=\"text\" class=\"day-dist\" placeholder=\"180 km \/ 6 hrs\" value=\"${initial?escapeAttr(initial.dist||''):''}\">\r\n        <\/div>\r\n      <\/div>\r\n      <div class=\"grid g2\" style=\"margin-top:10px\">\r\n        <div class=\"field\">\r\n          <label>Meals (this day)<\/label>\r\n          <input type=\"text\" class=\"day-meals\" placeholder=\"Breakfast + Dinner\" value=\"${escapeAttr(defaultMeals)}\">\r\n        <\/div>\r\n        <div class=\"field\">\r\n          <label>Difficulty Level<\/label>\r\n          <input type=\"text\" class=\"day-level\" placeholder=\"Easy \/ Moderate \/ Hard\" value=\"${initial?escapeAttr(initial.level||'Easy'):'Easy'}\">\r\n        <\/div>\r\n      <\/div>\r\n      <div class=\"field\" style=\"margin-top:10px\">\r\n        <label>Activities \/ Notes (one per line)<\/label>\r\n        <textarea class=\"day-acts\" rows=\"4\" placeholder=\"One activity per line\">${initial?escapeAttr(initial.acts||''):''}<\/textarea>\r\n      <\/div>\r\n    <\/div>\r\n  `;\r\n  container.appendChild(div);\r\n  if(initial && initial.date){\r\n    div.querySelector('.day-date').dataset.userOverride = '1';\r\n  }\r\n}\r\n\r\nfunction removeDay(btn){\r\n  const card = btn.closest('.day-card');\r\n  card.remove();\r\n  document.querySelectorAll('.day-card').forEach((c, i) => {\r\n    c.querySelector('.day-num').textContent = 'DAY ' + (i+1);\r\n  });\r\n  refreshAllDayDates();\r\n}\r\n\r\nfunction escapeAttr(s){\r\n  return String(s||'').replace(\/&\/g,'&amp;').replace(\/\"\/g,'&quot;').replace(\/<\/g,'&lt;').replace(\/>\/g,'&gt;');\r\n}\r\n\r\nfunction addAddon(initial){\r\n  addonCount++;\r\n  const container = document.getElementById('addons-container');\r\n  const div = document.createElement('div');\r\n  div.className = 'addon-row';\r\n  div.innerHTML = `\r\n    <button class=\"remove-btn\" onclick=\"this.closest('.addon-row').remove()\">Remove<\/button>\r\n    <div class=\"grid g3\" style=\"padding-right:80px\">\r\n      <div class=\"field\">\r\n        <label>Add-on Name<\/label>\r\n        <input type=\"text\" class=\"addon-name\" placeholder=\"Rohtang Pass 4x4\" value=\"${initial?escapeAttr(initial.name||''):''}\">\r\n      <\/div>\r\n      <div class=\"field\">\r\n        <label>Indicative Price (optional)<\/label>\r\n        <input type=\"text\" class=\"addon-price\" placeholder=\"\u20b93,500 per cab\" value=\"${initial?escapeAttr(initial.price||''):''}\">\r\n      <\/div>\r\n      <div class=\"field\">\r\n        <label>Note<\/label>\r\n        <input type=\"text\" class=\"addon-note\" placeholder=\"Subject to availability\" value=\"${initial?escapeAttr(initial.note||''):''}\">\r\n      <\/div>\r\n    <\/div>\r\n  `;\r\n  container.appendChild(div);\r\n}\r\n\r\nfunction readForm(){\r\n  const v = id => document.getElementById(id).value.trim();\r\n  const days = [];\r\n  document.querySelectorAll('.day-card').forEach(card => {\r\n    days.push({\r\n      date: card.querySelector('.day-date').value,\r\n      stay: card.querySelector('.day-stay').value.trim(),\r\n      title: (card.querySelector('.day-title')||{value:''}).value.trim(),\r\n      route: card.querySelector('.day-route').value.trim(),\r\n      hotel: card.querySelector('.day-hotel').value.trim(),\r\n      dist: card.querySelector('.day-dist').value.trim(),\r\n      meals: card.querySelector('.day-meals').value.trim(),\r\n      level: (card.querySelector('.day-level')||{value:''}).value.trim(),\r\n      acts: card.querySelector('.day-acts').value.trim()\r\n    });\r\n  });\r\n  const addons = [];\r\n  document.querySelectorAll('.addon-row').forEach(row => {\r\n    const name = row.querySelector('.addon-name').value.trim();\r\n    if(!name) return;\r\n    addons.push({\r\n      name: name,\r\n      price: row.querySelector('.addon-price').value.trim(),\r\n      note: row.querySelector('.addon-note').value.trim()\r\n    });\r\n  });\r\n  return {\r\n    customer: {\r\n      name: v('cust_name'),\r\n      phone: v('cust_phone'),\r\n      email: v('cust_email'),\r\n      from: v('cust_from'),\r\n      adults: parseInt(v('pax_adults'))||0,\r\n      children: parseInt(v('pax_children'))||0\r\n    },\r\n    trip: {\r\n      title: v('trip_title'),\r\n      subtitle: v('trip_subtitle'),\r\n      tags: v('trip_tags'),\r\n      start: v('trip_start'),\r\n      cost: parseInt(v('trip_cost'))||0,\r\n      vehicle: v('trip_vehicle')\r\n    },\r\n    pickup: {\r\n      loc: v('pickup_loc'),\r\n      date: v('pickup_date'),\r\n      time: v('pickup_time')\r\n    },\r\n    drop: {\r\n      loc: v('drop_loc'),\r\n      date: v('drop_date'),\r\n      time: v('drop_time')\r\n    },\r\n    days: days,\r\n    inclusions: v('inclusions').split('\\n').map(s=>s.trim()).filter(Boolean),\r\n    exclusions: v('exclusions').split('\\n').map(s=>s.trim()).filter(Boolean),\r\n    documents: v('documents').split('\\n').map(s=>s.trim()).filter(Boolean),\r\n    localTip: v('local_tip'),\r\n    addons: addons,\r\n    quoteDate: new Date().toISOString().slice(0,10)\r\n  };\r\n}\r\n\r\nfunction setForm(data){\r\n  if(!data) return;\r\n  const set = (id, val) => { const el = document.getElementById(id); if(el && val !== undefined) el.value = val; };\r\n  set('cust_name', data.customer?.name);\r\n  set('cust_phone', data.customer?.phone);\r\n  set('cust_email', data.customer?.email);\r\n  set('cust_from', data.customer?.from);\r\n  set('pax_adults', data.customer?.adults);\r\n  set('pax_children', data.customer?.children);\r\n  set('trip_title', data.trip?.title);\r\n  set('trip_subtitle', data.trip?.subtitle);\r\n  set('trip_tags', data.trip?.tags);\r\n  set('trip_start', data.trip?.start);\r\n  set('trip_cost', data.trip?.cost);\r\n  set('trip_vehicle', data.trip?.vehicle);\r\n  \/\/ legacy stats fields (data.stats) are ignored \u2014 now auto-derived from destinations\r\n  set('pickup_loc', data.pickup?.loc);\r\n  set('pickup_date', data.pickup?.date);\r\n  set('pickup_time', data.pickup?.time);\r\n  set('drop_loc', data.drop?.loc);\r\n  set('drop_date', data.drop?.date);\r\n  set('drop_time', data.drop?.time);\r\n  set('inclusions', (data.inclusions && data.inclusions.length ? data.inclusions : DEFAULT_INCLUSIONS).join('\\n'));\r\n  set('exclusions', (data.exclusions && data.exclusions.length ? data.exclusions : DEFAULT_EXCLUSIONS).join('\\n'));\r\n  set('documents', (data.documents||[]).join('\\n'));\r\n  set('local_tip', data.localTip);\r\n  document.getElementById('days-container').innerHTML = '';\r\n  (data.days||[]).forEach(d => addDay(d));\r\n  document.getElementById('addons-container').innerHTML = '';\r\n  (data.addons||[]).forEach(a => addAddon(a));\r\n}\r\n\r\nfunction validateForm(d){\r\n  const errs = [];\r\n  if(!d.customer.name) errs.push('Customer name');\r\n  if(!d.customer.phone) errs.push('Customer phone');\r\n  if(!d.trip.title) errs.push('Trip title');\r\n  if(!d.trip.start) errs.push('Trip start date');\r\n  if(!d.trip.cost) errs.push('Total cost');\r\n  if(!d.trip.vehicle) errs.push('Vehicle');\r\n  if(!d.pickup.loc) errs.push('Pickup location');\r\n  if(!d.pickup.date) errs.push('Pickup date');\r\n  if(!d.pickup.time) errs.push('Pickup time');\r\n  if(!d.drop.loc) errs.push('Drop location');\r\n  if(!d.drop.date) errs.push('Drop date');\r\n  if(!d.drop.time) errs.push('Drop time');\r\n  if(d.days.length === 0) errs.push('At least one day');\r\n  d.days.forEach((day, i) => {\r\n    if(!day.route) errs.push('Day ' + (i+1) + ' route');\r\n  });\r\n  return errs;\r\n}\r\n\r\nfunction saveDraft(){\r\n  const d = readForm();\r\n  const fname = (d.customer.name || 'draft').replace(\/[^a-zA-Z0-9]+\/g, '-').toLowerCase() + '-' + d.quoteDate + '.json';\r\n  const blob = new Blob([JSON.stringify(d, null, 2)], {type:'application\/json'});\r\n  const url = URL.createObjectURL(blob);\r\n  const a = document.createElement('a');\r\n  a.href = url; a.download = fname; a.click();\r\n  URL.revokeObjectURL(url);\r\n  showToast('Draft saved');\r\n}\r\n\r\nfunction loadDraft(){\r\n  document.getElementById('draft-file').click();\r\n}\r\nfunction handleDraftFile(ev){\r\n  const file = ev.target.files[0];\r\n  if(!file) return;\r\n  const reader = new FileReader();\r\n  reader.onload = e => {\r\n    try {\r\n      const data = JSON.parse(e.target.result);\r\n      setForm(data);\r\n      showToast('Draft loaded');\r\n    } catch(err){ showToast('Invalid draft file', true); }\r\n  };\r\n  reader.readAsText(file);\r\n  ev.target.value = '';\r\n}\r\n\r\nfunction resetForm(){\r\n  if(!confirm('Reset all form data?')) return;\r\n  document.querySelectorAll('input, textarea').forEach(el => {\r\n    if(el.id === 'pax_adults'){\r\n      el.value = 2;\r\n    } else if(el.id === 'pax_children'){\r\n      el.value = 0;\r\n    } else {\r\n      el.value = '';\r\n    }\r\n  });\r\n  document.getElementById('inclusions').value = DEFAULT_INCLUSIONS.join('\\n');\r\n  document.getElementById('exclusions').value = DEFAULT_EXCLUSIONS.join('\\n');\r\n  document.getElementById('days-container').innerHTML = '';\r\n  document.getElementById('addons-container').innerHTML = '';\r\n  document.getElementById('quick-plan-rows').innerHTML = '';\r\n  addQuickPlanRow();\r\n  showToast('Form reset');\r\n}\r\n\r\ndocument.addEventListener('DOMContentLoaded', () => {\r\n  const ts = document.getElementById('trip_start');\r\n  if(ts) ts.addEventListener('change', () => {\r\n    refreshAllDayDates();\r\n    \/\/ convenience: sync pickup date with trip start if pickup is blank or was auto-set\r\n    const pu = document.getElementById('pickup_date');\r\n    if(pu && (!pu.value || pu.dataset.autoSet === '1')){\r\n      pu.value = ts.value;\r\n      pu.dataset.autoSet = '1';\r\n    }\r\n  });\r\n  const pu = document.getElementById('pickup_date');\r\n  if(pu) pu.addEventListener('input', () => { delete pu.dataset.autoSet; });\r\n  document.getElementById('inclusions').value = DEFAULT_INCLUSIONS.join('\\n');\r\n  document.getElementById('exclusions').value = DEFAULT_EXCLUSIONS.join('\\n');\r\n  addQuickPlanRow();\r\n});\r\n\r\nfunction generatePDF(){\r\n  const d = readForm();\r\n  const errs = validateForm(d);\r\n  if(errs.length){\r\n    showToast('Missing: ' + errs.slice(0,3).join(', ') + (errs.length>3?'...':''), true);\r\n    return;\r\n  }\r\n  buildPDF(d);\r\n}\r\n\r\n\/* ==========================================================================\r\n   DESTINATION KNOWLEDGE BASE \u2014 Himachal + Leh Ladakh\r\n   Used to auto-derive At-a-Glance, Documents & Permits, and Local Tip\r\n   for the PDF \u2014 based on destinations the agent typed in the day-by-day plan.\r\n   Keys are lowercase. Aliases get normalised in `normalizeDest`.\r\n   ========================================================================== *\/\r\nconst DEST_KB = {\r\n  \/* ---------------- SHIMLA REGION ---------------- *\/\r\n  'shimla': {\r\n    altitude: 2276, region: 'Shimla',\r\n    airport: 'Chandigarh (115 km)',\r\n    tags: ['family','heritage','snow'],\r\n    permits: [],\r\n    tip: 'Shimla: The Kalka-Shimla toy train (UNESCO World Heritage, since 1903) is unmissable - book Shivalik Deluxe tickets 2-3 months ahead on IRCTC. Mall Road and The Ridge are pedestrian-only - cabs drop at Cart Road, expect a short walk uphill.'\r\n  },\r\n  'kufri': {\r\n    altitude: 2720, region: 'Shimla',\r\n    airport: 'Chandigarh (130 km)',\r\n    tags: ['family','snow','adventure'],\r\n    permits: [],\r\n    tip: 'Kufri: In peak winter (Dec-Feb), Kufri can be a long crawl due to snow tourism - start by 9am from Shimla. Yak rides and adventure activities are negotiable; agree price upfront.'\r\n  },\r\n  'narkanda': {\r\n    altitude: 2708, region: 'Shimla',\r\n    airport: 'Chandigarh (180 km)',\r\n    tags: ['offbeat','snow'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'chail': {\r\n    altitude: 2250, region: 'Shimla',\r\n    airport: 'Chandigarh (110 km)',\r\n    tags: ['offbeat','heritage','family'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'kasauli': {\r\n    altitude: 1927, region: 'Shimla',\r\n    airport: 'Chandigarh (65 km)',\r\n    tags: ['family','heritage','weekend'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'tattapani': {\r\n    altitude: 655, region: 'Shimla',\r\n    airport: 'Chandigarh (135 km)',\r\n    tags: ['offbeat','spiritual'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'mashobra': {\r\n    altitude: 2146, region: 'Shimla',\r\n    airport: 'Chandigarh (125 km)',\r\n    tags: ['family','offbeat','honeymoon'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'naldehra': {\r\n    altitude: 2044, region: 'Shimla',\r\n    airport: 'Chandigarh (135 km)',\r\n    tags: ['family','offbeat'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'sarahan': {\r\n    altitude: 2313, region: 'Shimla',\r\n    airport: 'Shimla (165 km) \/ Chandigarh (280 km)',\r\n    tags: ['heritage','spiritual','offbeat'],\r\n    permits: [],\r\n    tip: 'Sarahan: The Bhimakali Temple opens early - visit at morning aarti for the best experience. Cameras\/leather items are deposited at the gate. Clear mornings give direct views of Shrikhand Mahadev peak.'\r\n  },\r\n  \/* ---------------- KINNAUR ---------------- *\/\r\n  'sangla': {\r\n    altitude: 2696, region: 'Kinnaur',\r\n    airport: 'Shimla (220 km) \/ Chandigarh (335 km)',\r\n    tags: ['offbeat','adventure','tribal'],\r\n    permits: ['Inner Line Permit NOT needed for Sangla-Chitkul-Kalpa for Indian travellers (only photo ID)','Foreign nationals need ILP even for Sangla-Chitkul - carry passport'],\r\n    tip: 'Sangla & Kinnaur: The NH-5 cliff-road between Karcham and Powari is narrow and prone to shooting stones - we use experienced Himachal drivers only. Carry layers; nights drop below 5 deg C even in June.'\r\n  },\r\n  'rakcham': {\r\n    altitude: 3050, region: 'Kinnaur',\r\n    airport: 'Shimla (235 km)',\r\n    tags: ['offbeat','tribal'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'chitkul': {\r\n    altitude: 3450, region: 'Kinnaur',\r\n    airport: 'Shimla (250 km)',\r\n    tags: ['offbeat','tribal','adventure'],\r\n    permits: ['No special permit for Indian travellers - photo ID required'],\r\n    tip: 'Chitkul (3,450 m) is India\\'s last road-head village before the Indo-Tibet border. Locals do not eat meat in Chitkul - menus are pure veg. The last ATM is at Reckong Peo; carry cash.'\r\n  },\r\n  'kalpa': {\r\n    altitude: 2960, region: 'Kinnaur',\r\n    airport: 'Shimla (230 km)',\r\n    tags: ['offbeat','tribal','heritage'],\r\n    permits: ['Photo ID for hotel check-in'],\r\n    tip: ''\r\n  },\r\n  'reckong peo': {\r\n    altitude: 2290, region: 'Kinnaur',\r\n    airport: 'Shimla (235 km)',\r\n    tags: ['transit'],\r\n    permits: ['SDM office at Reckong Peo issues Inner Line Permits for Spiti'],\r\n    tip: ''\r\n  },\r\n  \/* ---------------- SPITI ---------------- *\/\r\n  'nako': {\r\n    altitude: 3625, region: 'Spiti',\r\n    airport: 'Shimla (350 km)',\r\n    tags: ['offbeat','spiritual','tribal'],\r\n    permits: ['Inner Line Permit required (we arrange via SDM Reckong Peo)'],\r\n    tip: ''\r\n  },\r\n  'tabo': {\r\n    altitude: 3280, region: 'Spiti',\r\n    airport: 'Bhuntar (300 km) \/ Shimla (380 km)',\r\n    tags: ['offbeat','spiritual','heritage'],\r\n    permits: ['Inner Line Permit required between Reckong Peo and Sumdo (we arrange)'],\r\n    tip: 'Tabo: Tabo Monastery (founded 996 CE) is one of the oldest continuously functioning Buddhist enclaves in the Himalayas - photography is not permitted inside the older temples. Plan for 1-2 hours minimum.'\r\n  },\r\n  'dhankar': {\r\n    altitude: 3894, region: 'Spiti',\r\n    airport: 'Bhuntar (260 km)',\r\n    tags: ['offbeat','spiritual','adventure'],\r\n    permits: ['Inner Line Permit required'],\r\n    tip: ''\r\n  },\r\n  'kaza': {\r\n    altitude: 3650, region: 'Spiti',\r\n    airport: 'Bhuntar (200 km) \/ Shimla (412 km)',\r\n    tags: ['offbeat','adventure','tribal'],\r\n    permits: ['Inner Line Permit required between Reckong Peo and Sumdo (we arrange via SDM Reckong Peo)'],\r\n    tip: 'Spiti Valley: ALWAYS acclimatise via Shimla\/Sangla\/Kalpa before Kaza - never rush up from Manali side in 1-2 days. Carry cash (ATMs only at Kaza, often offline), Diamox (consult a doctor first), and warm layers even in June. The Manali-Spiti route via Kunzum La (4,551 m) is open only late May to late September; the Shimla-Kaza axis stays open most of the year.'\r\n  },\r\n  'key': {\r\n    altitude: 4166, region: 'Spiti',\r\n    airport: 'Bhuntar (215 km)',\r\n    tags: ['offbeat','spiritual'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'kibber': {\r\n    altitude: 4270, region: 'Spiti',\r\n    airport: 'Bhuntar (220 km)',\r\n    tags: ['offbeat','adventure','wildlife'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'hikkim': {\r\n    altitude: 4440, region: 'Spiti',\r\n    airport: 'Bhuntar (220 km)',\r\n    tags: ['offbeat'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'langza': {\r\n    altitude: 4420, region: 'Spiti',\r\n    airport: 'Bhuntar (220 km)',\r\n    tags: ['offbeat','heritage'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'komic': {\r\n    altitude: 4587, region: 'Spiti',\r\n    airport: 'Bhuntar (225 km)',\r\n    tags: ['offbeat'],\r\n    permits: [],\r\n    tip: 'Komic (4,587 m) is among the highest motorable villages in the world - acclimatise at Kaza for at least one full day before driving up. Tangyud Monastery here is ~500 years old.'\r\n  },\r\n  'demul': {\r\n    altitude: 4380, region: 'Spiti',\r\n    airport: 'Bhuntar (230 km)',\r\n    tags: ['offbeat','tribal'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'lhalung': {\r\n    altitude: 3720, region: 'Spiti',\r\n    airport: 'Bhuntar (220 km)',\r\n    tags: ['offbeat','spiritual'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'pin valley': {\r\n    altitude: 3700, region: 'Spiti',\r\n    airport: 'Bhuntar (220 km)',\r\n    tags: ['offbeat','wildlife','adventure'],\r\n    permits: ['Pin Valley National Park entry permit at Kungri \/ Mikkim'],\r\n    tip: ''\r\n  },\r\n  'chandratal': {\r\n    altitude: 4300, region: 'Lahaul-Spiti',\r\n    airport: 'Bhuntar (155 km)',\r\n    tags: ['offbeat','adventure'],\r\n    permits: [],\r\n    tip: 'Chandratal (Moon Lake) is accessible only late-May to mid-October. The last 14 km from Batal is rough - we use sturdy SUVs only. Camping at the lake itself is no longer permitted; tents are 2-3 km away.'\r\n  },\r\n  'kunzum': {\r\n    altitude: 4551, region: 'Lahaul-Spiti',\r\n    airport: 'Bhuntar (135 km)',\r\n    tags: ['offbeat','adventure'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  \/* ---------------- KULLU \/ MANALI \/ PARVATI ---------------- *\/\r\n  'manali': {\r\n    altitude: 2050, region: 'Kullu',\r\n    airport: 'Bhuntar (50 km)',\r\n    tags: ['family','adventure','snow','honeymoon'],\r\n    permits: ['Rohtang Pass requires online permit from rohtangpermits.nic.in (we arrange)','Atal Tunnel & Sissu\/Koksar do not require Rohtang permit'],\r\n    tip: 'Manali: Rohtang Pass permits are capped daily and fill up fast - book 7-10 days ahead. Atal Tunnel doesn\\'t need a Rohtang permit and gives quicker access to Sissu and Lahaul. Old Manali cafes shut early (10pm).'\r\n  },\r\n  'solang': {\r\n    altitude: 2560, region: 'Kullu',\r\n    airport: 'Bhuntar (63 km)',\r\n    tags: ['family','adventure','snow'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'rohtang': {\r\n    altitude: 3978, region: 'Kullu',\r\n    airport: 'Bhuntar (101 km)',\r\n    tags: ['snow','adventure'],\r\n    permits: ['Online Rohtang permit required (we arrange)','Closed Tuesdays for maintenance'],\r\n    tip: ''\r\n  },\r\n  'atal tunnel': {\r\n    altitude: 3100, region: 'Lahaul',\r\n    airport: 'Bhuntar (75 km)',\r\n    tags: ['family','adventure'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'sissu': {\r\n    altitude: 3120, region: 'Lahaul',\r\n    airport: 'Bhuntar (90 km)',\r\n    tags: ['offbeat','snow'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'keylong': {\r\n    altitude: 3080, region: 'Lahaul',\r\n    airport: 'Bhuntar (120 km)',\r\n    tags: ['offbeat','transit'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'jispa': {\r\n    altitude: 3200, region: 'Lahaul',\r\n    airport: 'Bhuntar (140 km)',\r\n    tags: ['offbeat','transit','adventure'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'sarchu': {\r\n    altitude: 4290, region: 'Lahaul',\r\n    airport: 'Leh (255 km) \/ Bhuntar (230 km)',\r\n    tags: ['adventure','transit'],\r\n    permits: [],\r\n    tip: 'Sarchu (4,290 m) is a tough overnight halt - many travellers feel AMS here. If your group includes kids or seniors, we recommend halting lower at Jispa (3,200 m) instead.'\r\n  },\r\n  'naggar': {\r\n    altitude: 1851, region: 'Kullu',\r\n    airport: 'Bhuntar (30 km)',\r\n    tags: ['heritage','offbeat'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'kullu': {\r\n    altitude: 1278, region: 'Kullu',\r\n    airport: 'Bhuntar (10 km)',\r\n    tags: ['family','adventure'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'kasol': {\r\n    altitude: 1640, region: 'Parvati Valley',\r\n    airport: 'Bhuntar (32 km)',\r\n    tags: ['backpacker','offbeat','adventure'],\r\n    permits: [],\r\n    tip: 'Kasol & Parvati Valley: Mobile networks are patchy beyond Manikaran - download offline maps. The Kheerganga trek is 10-12 km round trip; start by 7am to be back before dark.'\r\n  },\r\n  'tosh': {\r\n    altitude: 2400, region: 'Parvati Valley',\r\n    airport: 'Bhuntar (45 km)',\r\n    tags: ['backpacker','offbeat'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'kheerganga': {\r\n    altitude: 2960, region: 'Parvati Valley',\r\n    airport: 'Bhuntar (50 km)',\r\n    tags: ['adventure','spiritual'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'manikaran': {\r\n    altitude: 1760, region: 'Parvati Valley',\r\n    airport: 'Bhuntar (35 km)',\r\n    tags: ['spiritual','family'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'malana': {\r\n    altitude: 2652, region: 'Parvati Valley',\r\n    airport: 'Bhuntar (50 km)',\r\n    tags: ['offbeat','heritage'],\r\n    permits: ['Photography of locals\/temples discouraged - respect village customs'],\r\n    tip: ''\r\n  },\r\n  \/* ---------------- TIRTHAN \/ JIBHI \/ MANDI ---------------- *\/\r\n  'jibhi': {\r\n    altitude: 1600, region: 'Tirthan Valley',\r\n    airport: 'Bhuntar (60 km)',\r\n    tags: ['offbeat','backpacker','family'],\r\n    permits: [],\r\n    tip: 'Jibhi & Tirthan: This is slow-travel country - plan 2+ nights, not a rushed stopover. Jalori Pass (3,120 m) closes in heavy snow (Jan-Feb). Trout fishing in the Tirthan needs a daily permit from the Fisheries Dept (we arrange).'\r\n  },\r\n  'tirthan': {\r\n    altitude: 1600, region: 'Tirthan Valley',\r\n    airport: 'Bhuntar (50 km)',\r\n    tags: ['offbeat','wildlife','family'],\r\n    permits: ['Great Himalayan National Park entry permit for treks inside GHNP (we arrange)'],\r\n    tip: ''\r\n  },\r\n  'shoja': {\r\n    altitude: 2700, region: 'Tirthan Valley',\r\n    airport: 'Bhuntar (65 km)',\r\n    tags: ['offbeat'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'mandi': {\r\n    altitude: 800, region: 'Mandi',\r\n    airport: 'Bhuntar (60 km)',\r\n    tags: ['heritage','transit'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'prashar': {\r\n    altitude: 2730, region: 'Mandi',\r\n    airport: 'Bhuntar (85 km)',\r\n    tags: ['offbeat','spiritual','trekking'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'rewalsar': {\r\n    altitude: 1360, region: 'Mandi',\r\n    airport: 'Bhuntar (75 km)',\r\n    tags: ['spiritual','offbeat'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'barot': {\r\n    altitude: 1835, region: 'Mandi',\r\n    airport: 'Gaggal (95 km)',\r\n    tags: ['offbeat','wildlife'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  \/* ---------------- KANGRA \/ CHAMBA ---------------- *\/\r\n  'dharamshala': {\r\n    altitude: 1457, region: 'Kangra',\r\n    airport: 'Gaggal (15 km) \/ Chandigarh (240 km)',\r\n    tags: ['spiritual','family','heritage'],\r\n    permits: [],\r\n    tip: 'Dharamshala-McLeodganj: The Dalai Lama\\'s public teachings happen seasonally - check dalailama.com for dates. McLeodganj parking is brutal in season; many hotels are a short walk uphill from the cab drop.'\r\n  },\r\n  'mcleodganj': {\r\n    altitude: 2082, region: 'Kangra',\r\n    airport: 'Gaggal (20 km)',\r\n    tags: ['spiritual','family','heritage'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'bhagsu': {\r\n    altitude: 2080, region: 'Kangra',\r\n    airport: 'Gaggal (22 km)',\r\n    tags: ['backpacker','spiritual'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'triund': {\r\n    altitude: 2828, region: 'Kangra',\r\n    airport: 'Gaggal (28 km)',\r\n    tags: ['adventure','trekking'],\r\n    permits: ['Forest Department day-trek registration at McLeodganj'],\r\n    tip: ''\r\n  },\r\n  'bir': {\r\n    altitude: 1525, region: 'Kangra',\r\n    airport: 'Gaggal (60 km)',\r\n    tags: ['adventure','offbeat'],\r\n    permits: [],\r\n    tip: 'Bir-Billing: Paragliding window is March-June and Sept-Nov; monsoon shuts the take-off. Tandem flights are weather-dependent - we will not put guests up in unsafe conditions.'\r\n  },\r\n  'palampur': {\r\n    altitude: 1219, region: 'Kangra',\r\n    airport: 'Gaggal (40 km)',\r\n    tags: ['family','heritage'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'dalhousie': {\r\n    altitude: 1970, region: 'Chamba',\r\n    airport: 'Gaggal (130 km) \/ Pathankot (80 km)',\r\n    tags: ['family','heritage','honeymoon'],\r\n    permits: [],\r\n    tip: 'Dalhousie & Khajjiar: Khajjiar (the \"mini Switzerland\") is at its best on weekday mornings - weekends crowd up after 11am. Carry warm layers; it gets cold even in May.'\r\n  },\r\n  'khajjiar': {\r\n    altitude: 1951, region: 'Chamba',\r\n    airport: 'Gaggal (130 km)',\r\n    tags: ['family','honeymoon'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'chamba': {\r\n    altitude: 996, region: 'Chamba',\r\n    airport: 'Gaggal (115 km)',\r\n    tags: ['heritage','offbeat'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  \/* ---------------- LEH LADAKH ---------------- *\/\r\n  'leh': {\r\n    altitude: 3500, region: 'Ladakh',\r\n    airport: 'Leh - Kushok Bakula Rimpochee (4 km)',\r\n    tags: ['adventure','spiritual','offbeat','family'],\r\n    permits: ['Indian nationals: photo ID + Ladakh environment & Red Cross fee (paid at Leh \/ online - we arrange)'],\r\n    tip: 'Leh (3,500 m): If flying in, keep the first 24-36 hours as pure rest - no sightseeing, no showers with cold water, lots of fluids. AMS hits fit young travellers too. Carry cash; cards\/UPI often fail outside Leh town. Fuel up at Karu\/Leh - there are NO fuel pumps at Pangong, Nubra interior or Hanle side.'\r\n  },\r\n  'khardung la': {\r\n    altitude: 5359, region: 'Ladakh',\r\n    airport: 'Leh (40 km)',\r\n    tags: ['adventure'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'nubra': {\r\n    altitude: 3048, region: 'Ladakh',\r\n    airport: 'Leh (120 km)',\r\n    tags: ['adventure','offbeat','family'],\r\n    permits: ['Inner Line Permit for Nubra Valley (we arrange; carry 4-5 photocopies for checkposts)'],\r\n    tip: 'Nubra Valley: The Hunder camel ride queue peaks 4-6pm - go right after lunch instead. If continuing to Pangong, take the direct Shyok river road (saves a full day vs returning to Leh) - road condition permitting.'\r\n  },\r\n  'diskit': {\r\n    altitude: 3144, region: 'Ladakh',\r\n    airport: 'Leh (118 km)',\r\n    tags: ['spiritual','offbeat'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'hunder': {\r\n    altitude: 3100, region: 'Ladakh',\r\n    airport: 'Leh (125 km)',\r\n    tags: ['adventure','family'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'turtuk': {\r\n    altitude: 2900, region: 'Ladakh',\r\n    airport: 'Leh (205 km)',\r\n    tags: ['offbeat','tribal','heritage'],\r\n    permits: ['Turtuk is close to the LOC - carry original photo ID; permits checked strictly at Thoise\/Bogdang'],\r\n    tip: 'Turtuk: A Balti village that opened to tourists only in 2010 - one of the last villages before the LOC. Apricot season (Jul-Aug) is magical. Homestays are basic but warm; manage expectations for luxury-segment guests.'\r\n  },\r\n  'pangong': {\r\n    altitude: 4225, region: 'Ladakh',\r\n    airport: 'Leh (160 km)',\r\n    tags: ['adventure','offbeat','honeymoon'],\r\n    permits: ['Inner Line Permit for the Pangong circuit (we arrange; carry photocopies)'],\r\n    tip: 'Pangong Tso (4,225 m): Nights at the lake drop below freezing even in summer - camps provide hot water bottles, but pack proper layers. Guests with heart\/BP conditions should skip the night halt and day-trip instead. No fuel, ATMs or reliable network at the lake.'\r\n  },\r\n  'tso moriri': {\r\n    altitude: 4522, region: 'Ladakh',\r\n    airport: 'Leh (220 km)',\r\n    tags: ['offbeat','wildlife','adventure'],\r\n    permits: ['Inner Line Permit for the Tso Moriri \/ Changthang circuit (we arrange)'],\r\n    tip: ''\r\n  },\r\n  'tso kar': {\r\n    altitude: 4530, region: 'Ladakh',\r\n    airport: 'Leh (155 km)',\r\n    tags: ['offbeat','wildlife'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'hanle': {\r\n    altitude: 4500, region: 'Ladakh',\r\n    airport: 'Leh (255 km)',\r\n    tags: ['offbeat','adventure'],\r\n    permits: ['Hanle \/ Umling La: additional permission required (restricted border zone - we arrange; rules change seasonally)','Foreign nationals are currently not permitted at Hanle \/ Umling La'],\r\n    tip: 'Hanle (4,500 m): India\\'s first Dark Sky Reserve - plan the night halt around a new moon for the best Milky Way views. Use red-light torches at night (white light is restricted near the observatory). Homestay capacity is tiny; we book well in advance.'\r\n  },\r\n  'umling la': {\r\n    altitude: 5799, region: 'Ladakh',\r\n    airport: 'Leh (330 km)',\r\n    tags: ['adventure'],\r\n    permits: ['Special permission required (restricted zone); Indian nationals only'],\r\n    tip: ''\r\n  },\r\n  'lamayuru': {\r\n    altitude: 3510, region: 'Ladakh',\r\n    airport: 'Leh (110 km)',\r\n    tags: ['spiritual','offbeat','heritage'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'alchi': {\r\n    altitude: 3100, region: 'Ladakh',\r\n    airport: 'Leh (65 km)',\r\n    tags: ['heritage','spiritual'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'sham valley': {\r\n    altitude: 3300, region: 'Ladakh',\r\n    airport: 'Leh (70 km)',\r\n    tags: ['family','spiritual'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'magnetic hill': {\r\n    altitude: 3300, region: 'Ladakh',\r\n    airport: 'Leh (28 km)',\r\n    tags: ['family'],\r\n    permits: [],\r\n    tip: ''\r\n  },\r\n  'kargil': {\r\n    altitude: 2676, region: 'Ladakh',\r\n    airport: 'Leh (215 km) \/ Srinagar (200 km)',\r\n    tags: ['heritage','transit'],\r\n    permits: [],\r\n    tip: ''\r\n  }\r\n};\r\n\r\n\/\/ Map common alternate spellings\/aliases to canonical KB keys\r\nconst DEST_ALIASES = {\r\n  'mc leodganj':'mcleodganj','mcleod ganj':'mcleodganj','mcleodgan':'mcleodganj',\r\n  'reckongpeo':'reckong peo','recong peo':'reckong peo',\r\n  'pin':'pin valley','pin national park':'pin valley','mudh':'pin valley',\r\n  'kunzum la':'kunzum','kunzum pass':'kunzum',\r\n  'atal':'atal tunnel','atal tunel':'atal tunnel',\r\n  'shimla sightseeing':'shimla',\r\n  'manali sightseeing':'manali',\r\n  'leh sightseeing':'leh',\r\n  'chitkul village':'chitkul',\r\n  'key monastery':'key','ki monastery':'key','kee monastery':'key','ki':'key','kee':'key',\r\n  'chandrataal':'chandratal','chandra taal':'chandratal','chandra tal':'chandratal','moon lake':'chandratal',\r\n  'jibhi valley':'jibhi','banjar':'jibhi','jalori':'shoja','jalori pass':'shoja',\r\n  'tirthan valley':'tirthan','gushaini':'tirthan',\r\n  'prashar lake':'prashar','parashar':'prashar','parashar lake':'prashar',\r\n  'ladakh':'leh','leh ladakh':'leh',\r\n  'khardungla':'khardung la','khardung':'khardung la','khardung pass':'khardung la',\r\n  'nubra valley':'nubra','nubra valley diskit':'nubra',\r\n  'pangong tso':'pangong','pangong lake':'pangong','pangong so':'pangong',\r\n  'tsomoriri':'tso moriri','tso moriri lake':'tso moriri','moriri':'tso moriri','korzok':'tso moriri',\r\n  'tsokar':'tso kar',\r\n  'umlingla':'umling la','umling':'umling la','umling la pass':'umling la',\r\n  'sham':'sham valley',\r\n  'dras':'kargil','drass':'kargil',\r\n  'sarahan bhimakali':'sarahan'\r\n};\r\n\r\nfunction normalizeDest(s){\r\n  if(!s) return '';\r\n  const k = String(s).toLowerCase().trim()\r\n    .replace(\/[\\(\\)]\/g,'')\r\n    .replace(\/\\s+\/g,' ');\r\n  if(DEST_ALIASES[k]) return DEST_ALIASES[k];\r\n  if(DEST_KB[k]) return k;\r\n  \/\/ try removing trailing words like \"monastery\", \"village\", \"valley\", \"lake\"\r\n  const trimmed = k.replace(\/\\s+(monastery|village|valley|pass|temple|town|lake|gompa)$\/,'');\r\n  if(DEST_ALIASES[trimmed]) return DEST_ALIASES[trimmed];\r\n  if(DEST_KB[trimmed]) return trimmed;\r\n  return k;\r\n}\r\n\r\nfunction extractDestinations(d){\r\n  \/\/ Walk every day's stay + route, return ordered unique list of canonical KB keys.\r\n  const seen = new Set();\r\n  const ordered = [];\r\n  function add(s){\r\n    const key = normalizeDest(s);\r\n    if(!key) return;\r\n    if(seen.has(key)) return;\r\n    seen.add(key);\r\n    ordered.push(key);\r\n  }\r\n  d.days.forEach(day => {\r\n    if(day.stay) add(day.stay);\r\n    \/\/ route is like \"Sangla -> Kalpa\" or \"Kaza -> Kunzum La -> Chandratal -> Manali (~190 km)\"\r\n    if(day.route){\r\n      const cleaned = day.route.replace(\/\\(.*?\\)\/g,'').replace(\/~?\\d+\\s*km\/gi,'');\r\n      \/\/ Split on arrows, pipes, \"to\"\/\"via\" words, and SPACED hyphens only \u2014\r\n      \/\/ so hyphenated place names like \"Leh-Ladakh\"\/\"Bir-Billing\" split correctly\r\n      \/\/ but multi-word names stay intact.\r\n      cleaned.split(\/->|\u2192|\\||\\s-\\s|-|\\bto\\b|\\bvia\\b\/i).forEach(part => add(part.trim()));\r\n    }\r\n  });\r\n  return ordered;\r\n}\r\n\r\nfunction deriveAtAGlance(d){\r\n  const dests = extractDestinations(d).filter(k => DEST_KB[k]);\r\n  if(!dests.length){\r\n    return {\r\n      altitude: 'Mixed Himalayan terrain',\r\n      besttime: 'Mar - Nov (varies by region)',\r\n      airport: 'Chandigarh \/ Bhuntar \/ Leh',\r\n      idealfor: 'Family',\r\n      stops: []\r\n    };\r\n  }\r\n\r\n  \/\/ ALTITUDE: highest destination wins\r\n  const highest = dests.reduce((best, k) => {\r\n    const alt = DEST_KB[k].altitude;\r\n    return (!best || alt > best.alt) ? {key:k, alt} : best;\r\n  }, null);\r\n  const titleCase = s => s.replace(\/\\b\\w\/g, c => c.toUpperCase());\r\n  let altitudeStr;\r\n  if(highest.alt >= 3500){\r\n    \/\/ High altitude - call it out + show another high point if available\r\n    const second = dests.filter(k => k !== highest.key && DEST_KB[k].altitude >= 3000)\r\n                        .sort((a,b) => DEST_KB[b].altitude - DEST_KB[a].altitude)[0];\r\n    altitudeStr = titleCase(highest.key) + ': ' + highest.alt.toLocaleString() + ' m';\r\n    if(second){\r\n      altitudeStr += ' \/ ' + titleCase(second) + ': ' + DEST_KB[second].altitude.toLocaleString() + ' m';\r\n    }\r\n  } else {\r\n    altitudeStr = 'Up to ' + highest.alt.toLocaleString() + ' m (' + titleCase(highest.key) + ')';\r\n  }\r\n\r\n  \/\/ BEST TIME: depends on region mix\r\n  const regions = new Set(dests.map(k => DEST_KB[k].region));\r\n  let bestTime;\r\n  if(regions.has('Ladakh')){\r\n    bestTime = 'May - Sep (road trips); fly-in Apr - Oct';\r\n  } else if(regions.has('Spiti') || regions.has('Lahaul-Spiti') || regions.has('Lahaul')){\r\n    if(dests.includes('chandratal') || dests.includes('kunzum')){\r\n      bestTime = 'Late May - Sept (Manali side); year-round via Shimla';\r\n    } else {\r\n      bestTime = 'May - Oct (via Manali); year-round via Shimla';\r\n    }\r\n  } else if(regions.has('Kinnaur')){\r\n    bestTime = 'Apr - Oct (best); winter access limited';\r\n  } else if(regions.has('Parvati Valley') || regions.has('Kullu') || regions.has('Tirthan Valley') || regions.has('Mandi')){\r\n    bestTime = 'Mar - Jun & Sept - Nov';\r\n  } else if(regions.has('Kangra') || regions.has('Chamba')){\r\n    bestTime = 'Mar - Jun & Sept - Nov';\r\n  } else {\r\n    bestTime = 'Year-round (snow Dec - Feb)';\r\n  }\r\n\r\n  \/\/ NEAREST AIRPORT: pick the airport of the deepest (last) major destination\r\n  const airportSrc = dests[dests.length-1];\r\n  const airport = DEST_KB[airportSrc].airport;\r\n\r\n  \/\/ IDEAL FOR: union of tags, prioritised\r\n  const tagCount = {};\r\n  dests.forEach(k => DEST_KB[k].tags.forEach(t => { tagCount[t] = (tagCount[t]||0)+1; }));\r\n  const tagPriority = ['offbeat','adventure','spiritual','heritage','snow','family','honeymoon','tribal','backpacker','wildlife','trekking'];\r\n  const topTags = Object.keys(tagCount)\r\n    .sort((a,b) => (tagCount[b]-tagCount[a]) || (tagPriority.indexOf(a)-tagPriority.indexOf(b)))\r\n    .slice(0,2)\r\n    .map(t => t.charAt(0).toUpperCase() + t.slice(1));\r\n  const idealFor = topTags.join(', ') || 'Family';\r\n\r\n  \/\/ STOPS: unique stays in order\r\n  const stayList = [];\r\n  const seenStay = new Set();\r\n  d.days.forEach(day => {\r\n    if(day.stay && !seenStay.has(day.stay.toLowerCase()) && day.stay.toLowerCase() !== 'in transit'){\r\n      seenStay.add(day.stay.toLowerCase());\r\n      stayList.push(day.stay);\r\n    }\r\n  });\r\n\r\n  return {\r\n    altitude: altitudeStr,\r\n    besttime: bestTime,\r\n    airport: airport,\r\n    idealfor: idealFor,\r\n    stops: stayList.slice(0, 6)\r\n  };\r\n}\r\n\r\nfunction deriveDocuments(d){\r\n  const dests = extractDestinations(d).filter(k => DEST_KB[k]);\r\n\r\n  const out = [];\r\n\r\n  \/\/ 1. Always: photo ID\r\n  out.push('Valid photo ID for hotel check-in (Aadhaar \/ Passport)');\r\n\r\n  const inSpiti   = dests.some(k => ['Spiti','Lahaul-Spiti'].includes(DEST_KB[k].region));\r\n  const inKinnaur = dests.some(k => DEST_KB[k].region === 'Kinnaur');\r\n  const inLadakh  = dests.some(k => DEST_KB[k].region === 'Ladakh');\r\n\r\n  \/\/ 2. Spiti \/ Kinnaur ILP consolidation\r\n  if(inSpiti){\r\n    out.push('Inner Line Permit required between Reckong Peo and Sumdo for the Spiti circuit (we arrange via SDM Reckong Peo)');\r\n  }\r\n  if(inKinnaur && !inSpiti){\r\n    out.push('Inner Line Permit NOT needed for Sangla-Chitkul-Kalpa for Indian travellers (photo ID sufficient)');\r\n  } else if(inKinnaur && inSpiti){\r\n    out.push('No special permit for Sangla-Chitkul-Kalpa for Indian travellers; ILP kicks in beyond Akpa');\r\n  }\r\n\r\n  \/\/ 3. Pin Valley NP if visited\r\n  if(dests.includes('pin valley')){\r\n    out.push('Pin Valley National Park entry permit at Kungri \/ Mikkim (we arrange)');\r\n  }\r\n\r\n  \/\/ 4. GHNP treks (Tirthan)\r\n  if(dests.includes('tirthan')){\r\n    out.push('Great Himalayan National Park entry permit for treks inside GHNP (we arrange)');\r\n  }\r\n\r\n  \/\/ 5. Rohtang permit if Rohtang in plan (not Atal Tunnel)\r\n  if(dests.includes('rohtang')){\r\n    out.push('Online Rohtang Pass permit required (we arrange via rohtangpermits.nic.in)');\r\n    out.push('Rohtang Pass closed Tuesdays for maintenance');\r\n  }\r\n  if(dests.includes('manali') && !dests.includes('rohtang') && (dests.includes('atal tunnel') || dests.includes('sissu'))){\r\n    out.push('Atal Tunnel route to Sissu \/ Koksar does not require a Rohtang permit');\r\n  }\r\n\r\n  \/\/ 6. Triund forest registration\r\n  if(dests.includes('triund')){\r\n    out.push('Forest Department day-trek registration at McLeodganj for Triund');\r\n  }\r\n\r\n  \/\/ 7. LADAKH permits block\r\n  if(inLadakh){\r\n    out.push('Ladakh environment & Red Cross fee for all visitors (paid at Leh \/ online - we arrange)');\r\n    const ilpAreas = ['nubra','diskit','hunder','turtuk','pangong','tso moriri','tso kar','hanle','umling la'];\r\n    if(dests.some(k => ilpAreas.includes(k))){\r\n      out.push('Inner Line Permit for Nubra \/ Pangong \/ Tso Moriri circuit (we arrange; carry 4-5 photocopies for army checkposts)');\r\n    }\r\n    if(dests.includes('turtuk')){\r\n      out.push('Turtuk: original photo ID mandatory - checked strictly at Thoise \/ Bogdang checkposts');\r\n    }\r\n    if(dests.includes('hanle') || dests.includes('umling la')){\r\n      out.push('Hanle \/ Umling La: additional restricted-zone permission required (Indian nationals only; rules change seasonally - we reconfirm at booking)');\r\n    }\r\n    out.push('Foreign nationals: Protected Area Permit (PAP) required for Nubra, Pangong & Tso Moriri (issued via registered agent, minimum 2 travellers)');\r\n  }\r\n\r\n  \/\/ 8. Foreign-traveller catch-all for Kinnaur-Spiti\r\n  if(inSpiti || inKinnaur){\r\n    out.push('Foreign nationals: passport + Inner Line Permit required for the Kinnaur-Spiti circuit');\r\n  }\r\n\r\n  \/\/ Dedup case-insensitively while preserving order\r\n  const seen = new Set();\r\n  return out.filter(v => {\r\n    const k = v.toLowerCase();\r\n    if(seen.has(k)) return false;\r\n    seen.add(k);\r\n    return true;\r\n  });\r\n}\r\n\r\nfunction deriveLocalTip(d){\r\n  const dests = extractDestinations(d).filter(k => DEST_KB[k]);\r\n  \/\/ Region priority \u2014 most \"specialised\" \/ restricted first; family stuff last\r\n  const regionPriority = {\r\n    'Ladakh': 0,\r\n    'Spiti': 1, 'Lahaul-Spiti': 1, 'Lahaul': 2,\r\n    'Kinnaur': 3, 'Parvati Valley': 4, 'Tirthan Valley': 4,\r\n    'Kullu': 5, 'Mandi': 5, 'Chamba': 6, 'Kangra': 7, 'Shimla': 8\r\n  };\r\n\r\n  \/\/ Group destinations by region, keep first (and only) tip per region\r\n  const regionTips = new Map();\r\n  dests.forEach(k => {\r\n    const entry = DEST_KB[k];\r\n    if(entry.tip && !regionTips.has(entry.region)){\r\n      regionTips.set(entry.region, entry.tip);\r\n    }\r\n  });\r\n\r\n  \/\/ Sort by region priority, take top 3, join with paragraph breaks\r\n  const sorted = [...regionTips.entries()]\r\n    .sort((a, b) => (regionPriority[a[0]] || 99) - (regionPriority[b[0]] || 99))\r\n    .slice(0, 3)\r\n    .map(([_, tip]) => tip);\r\n\r\n  return sorted.join('\\n\\n');\r\n}\r\n\r\nfunction pdfSafe(s){\r\n  return String(s||'')\r\n    .replace(\/[\\u2013\\u2014]\/g, '-')\r\n    .replace(\/[\\u2018\\u2019]\/g, \"'\")\r\n    .replace(\/[\\u201C\\u201D]\/g, '\"')\r\n    .replace(\/\\u2026\/g, '...')\r\n    .replace(\/\\u2192\/g, '->')\r\n    .replace(\/\\u20B9\/g, 'Rs.')\r\n    .replace(\/\\u00D7\/g, 'x')        \/\/ \u00d7 (e.g. \"4\u00d74\")\r\n    .replace(\/\\u00B0\/g, ' deg ')    \/\/ \u00b0 (temperatures)\r\n    .replace(\/\\u2022\/g, '-')        \/\/ bullet\r\n    .replace(\/[\\u00A0]\/g, ' ')\r\n    .replace(\/[^\\x20-\\x7E\\n]\/g, '?');\r\n}\r\n\r\n\/* ==========================================================================\r\n   PDF BUILDER  -  HimTrails brand brochure layout\r\n   ========================================================================== *\/\r\nfunction buildPDF(d){\r\n  const { jsPDF } = window.jspdf;\r\n  const doc = new jsPDF({unit:'pt', format:'a4'});\r\n  const W = doc.internal.pageSize.getWidth();   \/\/ 595\r\n  const H = doc.internal.pageSize.getHeight();  \/\/ 842\r\n  const M = 40;\r\n\r\n  \/\/ Brand palette\r\n  const DARK   = [15,76,58];      \/\/ primary deep green\r\n  const MID    = [26,122,94];     \/\/ accent green\r\n  const LIGHT  = [200,230,213];   \/\/ pale green text on dark\r\n  const CREAM  = [248,245,238];   \/\/ warm cream bg\r\n  const PAPER  = [253,252,249];   \/\/ body bg\r\n  const BORDER = [220,225,222];\r\n  const TEXT   = [40,55,70];\r\n  const MUTED  = [120,135,140];\r\n  const RED    = [185,51,50];\r\n  const AMBER_BG = [254,243,232];\r\n  const AMBER_BORDER = [245,210,163];\r\n  const AMBER_TEXT   = [124,77,16];\r\n\r\n  const days = d.days.length;\r\n  const nights = Math.max(0, days - 1);\r\n  const tagLine = (d.trip.tags && d.trip.tags.trim())\r\n    ? d.trip.tags.trim()\r\n    : (d.customer.from ? 'From ' + d.customer.from : '');\r\n  const fullTagLine = nights + 'N \/ ' + days + 'D' + (tagLine ? ' | ' + tagLine : '');\r\n\r\n  \/\/ ====== AUTO-DERIVED CONTENT ======\r\n  const autoGlance = deriveAtAGlance(d);\r\n  const autoDocs   = deriveDocuments(d);\r\n  const autoTip    = deriveLocalTip(d);\r\n\r\n  const finalGlance    = autoGlance;\r\n  const finalDocs      = (d.documents && d.documents.length) ? d.documents : autoDocs;\r\n  const finalLocalTip  = (d.localTip && d.localTip.trim())   ? d.localTip   : autoTip;\r\n\r\n  \/* ========== SHARED CHROME ========== *\/\r\n  function brandHeaderBand(){\r\n    doc.setFillColor(...DARK); doc.rect(0,0,W,46,'F');\r\n    doc.setFillColor(...MID); doc.rect(0,46,W,2,'F');\r\n    doc.setTextColor(255,255,255);\r\n    doc.setFont('helvetica','bold').setFontSize(12);\r\n    doc.text('HIMTRAILS', M, 22);\r\n    doc.setFont('helvetica','normal').setFontSize(7);\r\n    doc.setTextColor(...LIGHT);\r\n    doc.text('BREATHE THE HEIGHTS, LIVE THE TRAILS', M, 35);\r\n  }\r\n\r\n  function pageFooter(){\r\n    doc.setDrawColor(...BORDER).setLineWidth(0.5);\r\n    doc.line(M, H-30, W-M, H-30);\r\n    doc.setFont('helvetica','normal').setFontSize(8);\r\n    doc.setTextColor(...MUTED);\r\n    doc.text('HimTrails - Breathe the Heights, Live the Trails', M, H-15);\r\n    doc.setFont('helvetica','bold');\r\n    doc.setTextColor(...DARK);\r\n    doc.text('WhatsApp +91 77176 97177 - www.himtrails.in', W-M, H-15, {align:'right'});\r\n  }\r\n\r\n  function continuationStrip(yStart){\r\n    doc.setFont('helvetica','bold').setFontSize(11);\r\n    doc.setTextColor(...DARK);\r\n    const titleLines = doc.splitTextToSize(pdfSafe(d.trip.title), W - 2*M - 130);\r\n    doc.text(titleLines.slice(0,1), M, yStart);\r\n    doc.setFillColor(...MID);\r\n    doc.rect(M, yStart+5, 28, 2, 'F');\r\n  }\r\n\r\n  \/* ========== PAGE 1 \u2014 COVER ========== *\/\r\n  function renderCover(){\r\n    brandHeaderBand();\r\n\r\n    let cy = 92;\r\n    doc.setFont('helvetica','italic').setFontSize(10);\r\n    doc.setTextColor(...MID);\r\n    doc.text('Your Himalayan Itinerary', W\/2, cy, {align:'center'});\r\n    cy += 8;\r\n    doc.setDrawColor(...MID).setLineWidth(0.7);\r\n    doc.line(W\/2 - 28, cy, W\/2 + 28, cy);\r\n    cy += 24;\r\n\r\n    doc.setFont('helvetica','bold').setFontSize(13);\r\n    doc.setTextColor(...MUTED);\r\n    doc.text('CRAFTED BY LOCAL EXPERTS, BASED IN SHIMLA', W\/2, cy, {align:'center'});\r\n    cy += 28;\r\n\r\n    \/\/ YOUR DESTINATION block\r\n    doc.setFillColor(...CREAM);\r\n    doc.roundedRect(M, cy, W-2*M, 88, 10, 10, 'F');\r\n    doc.setDrawColor(...BORDER).setLineWidth(0.6);\r\n    doc.roundedRect(M, cy, W-2*M, 88, 10, 10, 'S');\r\n\r\n    doc.setFont('helvetica','bold').setFontSize(9);\r\n    doc.setTextColor(...MID);\r\n    doc.text('YOUR DESTINATION', W\/2, cy+18, {align:'center'});\r\n\r\n    let fontSize = 28;\r\n    doc.setFont('helvetica','bold');\r\n    doc.setFontSize(fontSize);\r\n    let titleW = doc.getTextWidth(pdfSafe(d.trip.title));\r\n    while(titleW > W - 2*M - 40 && fontSize > 16){\r\n      fontSize -= 1;\r\n      doc.setFontSize(fontSize);\r\n      titleW = doc.getTextWidth(pdfSafe(d.trip.title));\r\n    }\r\n    doc.setTextColor(...DARK);\r\n    doc.text(pdfSafe(d.trip.title), W\/2, cy+48, {align:'center'});\r\n\r\n    if(d.trip.subtitle){\r\n      doc.setFont('helvetica','normal').setFontSize(9.5);\r\n      doc.setTextColor(...TEXT);\r\n      const subLines = doc.splitTextToSize(pdfSafe(d.trip.subtitle), W - 2*M - 50);\r\n      doc.text(subLines.slice(0,2), W\/2, cy+66, {align:'center'});\r\n    }\r\n    cy += 100;\r\n\r\n    doc.setFont('helvetica','bold').setFontSize(11);\r\n    doc.setTextColor(...DARK);\r\n    doc.text(pdfSafe(fullTagLine), W\/2, cy, {align:'center'});\r\n    cy += 24;\r\n\r\n    \/* ========== AT A GLANCE ========== *\/\r\n    doc.setFont('helvetica','bold').setFontSize(10);\r\n    doc.setTextColor(...MID);\r\n    doc.text('AT A GLANCE', W\/2, cy, {align:'center'});\r\n    cy += 6;\r\n    doc.setDrawColor(...MID).setLineWidth(0.7);\r\n    doc.line(W\/2 - 22, cy, W\/2 + 22, cy);\r\n    cy += 18;\r\n\r\n    const stats = [\r\n      {label:'ALTITUDE',         value: finalGlance.altitude || '-'},\r\n      {label:'BEST TIME',        value: finalGlance.besttime || '-'},\r\n      {label:'NEAREST AIRPORT',  value: finalGlance.airport  || '-'},\r\n      {label:'IDEAL FOR',        value: finalGlance.idealfor || '-'}\r\n    ];\r\n    const statColW = (W - 2*M - 24) \/ 4;\r\n    const statH = 78;\r\n    stats.forEach((s, i) => {\r\n      const sx = M + i*(statColW + 8);\r\n      doc.setFillColor(...PAPER);\r\n      doc.setDrawColor(...BORDER).setLineWidth(0.6);\r\n      doc.roundedRect(sx, cy, statColW, statH, 8, 8, 'FD');\r\n      doc.setFillColor(...MID);\r\n      doc.roundedRect(sx, cy, statColW, 3, 1.5, 1.5, 'F');\r\n\r\n      doc.setFont('helvetica','bold').setFontSize(8);\r\n      doc.setTextColor(...MID);\r\n      doc.text(pdfSafe(s.label), sx + statColW\/2, cy + 22, {align:'center'});\r\n\r\n      doc.setFont('helvetica','normal').setFontSize(9);\r\n      doc.setTextColor(...TEXT);\r\n      const valLines = doc.splitTextToSize(pdfSafe(s.value), statColW - 14);\r\n      const startY = cy + 40;\r\n      doc.text(valLines.slice(0,3), sx + statColW\/2, startY, {align:'center'});\r\n    });\r\n    cy += statH + 26;\r\n\r\n    \/* ========== YOUR ROUTE ========== *\/\r\n    doc.setFont('helvetica','bold').setFontSize(10);\r\n    doc.setTextColor(...MID);\r\n    doc.text('YOUR ROUTE', W\/2, cy, {align:'center'});\r\n    cy += 6;\r\n    doc.setDrawColor(...MID).setLineWidth(0.7);\r\n    doc.line(W\/2 - 18, cy, W\/2 + 18, cy);\r\n    cy += 22;\r\n\r\n    const stops = (finalGlance.stops || []).slice(0, 6);\r\n    if(stops.length){\r\n      const pillW = (W - 2*M - (stops.length-1)*10) \/ stops.length;\r\n      const pillH = 56;\r\n      stops.forEach((s, i) => {\r\n        const px = M + i*(pillW + 10);\r\n        doc.setFillColor(...DARK);\r\n        doc.roundedRect(px, cy, pillW, pillH, 8, 8, 'F');\r\n\r\n        doc.setFont('helvetica','bold').setFontSize(7);\r\n        doc.setTextColor(...LIGHT);\r\n        doc.text('STOP ' + (i+1), px + pillW\/2, cy + 18, {align:'center'});\r\n\r\n        let fs = 11;\r\n        doc.setFont('helvetica','bold').setFontSize(fs);\r\n        let tw = doc.getTextWidth(pdfSafe(s));\r\n        while(tw > pillW - 12 && fs > 7){\r\n          fs -= 0.5;\r\n          doc.setFontSize(fs);\r\n          tw = doc.getTextWidth(pdfSafe(s));\r\n        }\r\n        doc.setTextColor(255,255,255);\r\n        doc.text(pdfSafe(s), px + pillW\/2, cy + 38, {align:'center'});\r\n\r\n        if(i < stops.length - 1){\r\n          const cx = px + pillW + 5;\r\n          const cyArrow = cy + pillH\/2;\r\n          doc.setDrawColor(...MID).setLineWidth(1.5);\r\n          doc.line(cx - 3, cyArrow - 4, cx + 1, cyArrow);\r\n          doc.line(cx - 3, cyArrow + 4, cx + 1, cyArrow);\r\n        }\r\n      });\r\n      cy += pillH + 24;\r\n    }\r\n\r\n    \/* ========== \"What's inside\" line ========== *\/\r\n    doc.setFont('helvetica','italic').setFontSize(9);\r\n    doc.setTextColor(...MUTED);\r\n    doc.text(\r\n      'What\\'s inside this itinerary',\r\n      W\/2, cy, {align:'center'}\r\n    );\r\n    cy += 14;\r\n    doc.setFont('helvetica','normal').setFontSize(9);\r\n    doc.setTextColor(...TEXT);\r\n    doc.text(\r\n      'Day-by-day plan with drive times & distances - Documents & permits required - Local tips from our team.',\r\n      W\/2, cy, {align:'center'}\r\n    );\r\n    cy += 22;\r\n\r\n    \/* ========== TRUST BADGES ========== *\/\r\n    const badges = [\r\n      {num:'3,973+',  cap:'HAPPY TRAVELLERS'},\r\n      {num:'4.9\/5',   cap:'GOOGLE RATING'},\r\n      {num:'5+ Yrs',  cap:'HIMALAYAN EXPERTS'},\r\n      {num:'24x7',    cap:'SUPPORT'}\r\n    ];\r\n    const bColW = (W - 2*M) \/ 4;\r\n    const bH = 56;\r\n    badges.forEach((b, i) => {\r\n      const bx = M + i*bColW;\r\n      if(i > 0){\r\n        doc.setDrawColor(...BORDER).setLineWidth(0.5);\r\n        doc.line(bx, cy + 8, bx, cy + bH - 8);\r\n      }\r\n      doc.setFont('helvetica','bold').setFontSize(18);\r\n      doc.setTextColor(...DARK);\r\n      doc.text(b.num, bx + bColW\/2, cy + 24, {align:'center'});\r\n      doc.setFont('helvetica','bold').setFontSize(7.5);\r\n      doc.setTextColor(...MID);\r\n      doc.text(b.cap, bx + bColW\/2, cy + 42, {align:'center'});\r\n    });\r\n  }\r\n\r\n  \/* ========== DAY CARD ========== *\/\r\n  function dayKind(idx, total){\r\n    if(idx === 0) return 'ARRIVAL';\r\n    if(idx === total - 1) return 'DEPARTURE';\r\n    return 'DAY';\r\n  }\r\n\r\n  function dayActivities(day){\r\n    return (day.acts||'').split('\\n').map(s => s.trim()).filter(Boolean);\r\n  }\r\n\r\n  function dayChips(day){\r\n    const chips = [];\r\n    if(day.stay)  chips.push({k:'STAY',  v: day.stay});\r\n    if(day.hotel) chips.push({k:'HOTEL', v: day.hotel});\r\n    if(day.dist){\r\n      if(day.dist.includes(' \/ ')){\r\n        const parts = day.dist.split(' \/ ').map(s => s.trim());\r\n        chips.push({k:'DIST', v: parts[0]});\r\n        chips.push({k:'TIME', v: parts[1]});\r\n      } else {\r\n        chips.push({k:'DIST', v: day.dist});\r\n      }\r\n    }\r\n    if(day.meals) chips.push({k:'MEALS', v: day.meals});\r\n    if(day.level) chips.push({k:'LEVEL', v: day.level});\r\n    \/\/ cap at 5 chips \u2014 drop LEVEL first, then merged HOTEL wins over STAY\r\n    if(chips.length > 5){\r\n      const li = chips.findIndex(c => c.k === 'LEVEL');\r\n      if(li > -1) chips.splice(li, 1);\r\n    }\r\n    if(chips.length > 5){\r\n      const si = chips.findIndex(c => c.k === 'STAY');\r\n      if(si > -1) chips.splice(si, 1);\r\n    }\r\n    return chips;\r\n  }\r\n\r\n  function measureDayH(day){\r\n    const acts = dayActivities(day);\r\n    let h = 0;\r\n    h += 50;        \/\/ header bar\r\n    h += 16;        \/\/ ROUTE row\r\n    h += 4;         \/\/ gap\r\n    let actH = 0;\r\n    doc.setFont('helvetica','normal').setFontSize(9.5);\r\n    acts.forEach(a => {\r\n      const lines = doc.splitTextToSize(pdfSafe(a), W - 2*M - 60);\r\n      actH += lines.length * 13 + 3;\r\n    });\r\n    if(!acts.length) actH = 12;\r\n    h += actH;\r\n    h += 6;         \/\/ gap before chip footer\r\n    h += 32;        \/\/ chip footer\r\n    h += 8;         \/\/ bottom padding\r\n    return h;\r\n  }\r\n\r\n  function renderDay(day, idx, total, y){\r\n    const h = measureDayH(day);\r\n    const kind = dayKind(idx, total);\r\n    const acts = dayActivities(day);\r\n\r\n    doc.setFillColor(253,252,249);\r\n    doc.setDrawColor(...BORDER).setLineWidth(0.6);\r\n    doc.roundedRect(M, y, W-2*M, h, 8, 8, 'FD');\r\n\r\n    \/\/ ===== Header strip =====\r\n    const numSize = 40;\r\n    doc.setFillColor(...DARK);\r\n    doc.roundedRect(M, y, numSize, numSize, 8, 8, 'F');\r\n    doc.setFillColor(...DARK);\r\n    doc.rect(M + numSize - 8, y, 8, numSize, 'F');\r\n    doc.setTextColor(255,255,255);\r\n    doc.setFont('helvetica','bold').setFontSize(20);\r\n    doc.text(String(idx+1), M + numSize\/2, y + 27, {align:'center'});\r\n\r\n    \/\/ Top-right: kind tag + day date\r\n    doc.setFont('helvetica','bold').setFontSize(8);\r\n    doc.setTextColor(...MID);\r\n    doc.text(kind, W - M - 14, y + 16, {align:'right'});\r\n    if(day.date){\r\n      doc.setFont('helvetica','normal').setFontSize(8);\r\n      doc.setTextColor(...MUTED);\r\n      doc.text(pdfSafe(fmtDate(day.date)), W - M - 14, y + 28, {align:'right'});\r\n    }\r\n\r\n    \/\/ Day title\r\n    const titleX = M + numSize + 14;\r\n    const titleMaxW = W - M - titleX - 90;   \/\/ leave space for kind\/date on the right\r\n    let titleText = day.title || day.route || '';\r\n    doc.setFont('helvetica','bold').setFontSize(13);\r\n    doc.setTextColor(...DARK);\r\n    let tLines = doc.splitTextToSize(pdfSafe(titleText), titleMaxW);\r\n    doc.text(tLines.slice(0,1), titleX, y + 24);\r\n    doc.setFillColor(...MID);\r\n    doc.rect(titleX, y + 30, 24, 2, 'F');\r\n\r\n    \/\/ ROUTE line\r\n    let cy = y + 50;\r\n    if(day.route){\r\n      doc.setFont('helvetica','bold').setFontSize(8);\r\n      doc.setTextColor(...MID);\r\n      doc.text('ROUTE', M + 14, cy + 4);\r\n      doc.setFont('helvetica','normal').setFontSize(9.5);\r\n      doc.setTextColor(...TEXT);\r\n      const routeText = pdfSafe(day.route);\r\n      const routeLines = doc.splitTextToSize(routeText, W - 2*M - 28 - doc.getTextWidth('ROUTE') - 8);\r\n      doc.text(routeLines.slice(0,1), M + 14 + doc.getTextWidth('ROUTE') + 8, cy + 4);\r\n      cy += 16;\r\n    }\r\n    cy += 2;\r\n\r\n    \/\/ Activities\r\n    if(acts.length){\r\n      doc.setFont('helvetica','normal').setFontSize(9.5);\r\n      doc.setTextColor(...TEXT);\r\n      acts.forEach(a => {\r\n        doc.setFillColor(...MID);\r\n        doc.circle(M + 18, cy + 3, 1.6, 'F');\r\n        const lines = doc.splitTextToSize(pdfSafe(a), W - 2*M - 60);\r\n        doc.text(lines, M + 26, cy + 5);\r\n        cy += lines.length * 13 + 3;\r\n      });\r\n    } else {\r\n      doc.setFont('helvetica','italic').setFontSize(9);\r\n      doc.setTextColor(...MUTED);\r\n      doc.text('Day plan to be finalised at confirmation.', M + 18, cy + 5);\r\n      cy += 12;\r\n    }\r\n\r\n    \/\/ ===== Chip footer =====\r\n    const chipY = y + h - 36;\r\n    doc.setDrawColor(...BORDER).setLineWidth(0.5);\r\n    doc.line(M + 12, chipY - 2, W - M - 12, chipY - 2);\r\n\r\n    const chips = dayChips(day);\r\n    if(chips.length){\r\n      const chipColW = (W - 2*M - 24) \/ chips.length;\r\n      chips.forEach((c, i) => {\r\n        const cx = M + 12 + i * chipColW;\r\n        if(i > 0){\r\n          doc.setDrawColor(...BORDER).setLineWidth(0.5);\r\n          doc.line(cx, chipY + 5, cx, chipY + 28);\r\n        }\r\n        doc.setFont('helvetica','bold').setFontSize(7.5);\r\n        doc.setTextColor(...MID);\r\n        doc.text(c.k, cx + chipColW\/2, chipY + 12, {align:'center'});\r\n\r\n        doc.setFont('helvetica','normal').setFontSize(9);\r\n        doc.setTextColor(...DARK);\r\n        let val = pdfSafe(c.v);\r\n        const maxW = chipColW - 10;\r\n        if(doc.getTextWidth(val) > maxW){\r\n          while(doc.getTextWidth(val + '...') > maxW && val.length > 4){\r\n            val = val.slice(0, -1);\r\n          }\r\n          val = val + '...';\r\n        }\r\n        doc.text(val, cx + chipColW\/2, chipY + 25, {align:'center'});\r\n      });\r\n    }\r\n\r\n    return h;\r\n  }\r\n\r\n  \/* ========== TRIP ESSENTIALS ========== *\/\r\n  function essentialsBlocks(){\r\n    const blocks = [];\r\n\r\n    blocks.push({\r\n      type:'essentials_heading',\r\n      h: 60,\r\n      keepWithNext: true,\r\n      render: y => {\r\n        doc.setFont('helvetica','bold').setFontSize(20);\r\n        doc.setTextColor(...DARK);\r\n        doc.text('Trip Essentials', M, y + 22);\r\n        doc.setFillColor(...MID);\r\n        doc.rect(M, y + 28, 36, 2.5, 'F');\r\n        doc.setFont('helvetica','normal').setFontSize(10);\r\n        doc.setTextColor(...MUTED);\r\n        doc.text('Documents required, what\\'s included, and next steps', M, y + 46);\r\n      }\r\n    });\r\n\r\n    \/\/ Documents & Permits \u2014 built here, pushed AFTER add-ons for better flow\r\n    let docsBlock = null;\r\n    if(finalDocs.length){\r\n      const docsLineH = 14;\r\n      let dh = 40;\r\n      doc.setFont('helvetica','normal').setFontSize(9.5);\r\n      finalDocs.forEach(p => {\r\n        const lines = doc.splitTextToSize(pdfSafe(p), W - 2*M - 50);\r\n        dh += lines.length * docsLineH + 3;\r\n      });\r\n      dh += 14;\r\n      docsBlock = {\r\n        type:'documents',\r\n        h: dh,\r\n        render: y => {\r\n          doc.setFillColor(...PAPER);\r\n          doc.setDrawColor(...BORDER).setLineWidth(0.6);\r\n          doc.roundedRect(M, y, W-2*M, dh, 8, 8, 'FD');\r\n          doc.setFillColor(...MID);\r\n          doc.roundedRect(M, y, 4, dh, 2, 2, 'F');\r\n          doc.setFont('helvetica','bold').setFontSize(10);\r\n          doc.setTextColor(...DARK);\r\n          doc.text('DOCUMENTS & PERMITS REQUIRED', M+15, y+22);\r\n\r\n          doc.setFont('helvetica','normal').setFontSize(9.5);\r\n          doc.setTextColor(...TEXT);\r\n          let cy = y + 40;\r\n          finalDocs.forEach(p => {\r\n            doc.setFillColor(...MID);\r\n            doc.circle(M + 18, cy - 2, 1.5, 'F');\r\n            const lines = doc.splitTextToSize(pdfSafe(p), W - 2*M - 50);\r\n            doc.text(lines, M + 26, cy);\r\n            cy += lines.length * docsLineH + 3;\r\n          });\r\n        }\r\n      };\r\n    }\r\n\r\n    \/\/ Inclusions \/ Exclusions side-by-side\r\n    const halfW = (W - 2*M - 14) \/ 2;\r\n    function listH(items){\r\n      let h = 36;\r\n      items.forEach(p => {\r\n        const lines = doc.splitTextToSize(pdfSafe(p), halfW - 32);\r\n        h += lines.length * 13 + 4;\r\n      });\r\n      return h + 12;\r\n    }\r\n    const ieH = Math.max(listH(d.inclusions), listH(d.exclusions), 100);\r\n    blocks.push({\r\n      type:'ie',\r\n      h: ieH,\r\n      render: y => {\r\n        doc.setFillColor(245,251,247);\r\n        doc.setDrawColor(...BORDER).setLineWidth(0.6);\r\n        doc.roundedRect(M, y, halfW, ieH, 8, 8, 'FD');\r\n        doc.setFont('helvetica','bold').setFontSize(10);\r\n        doc.setTextColor(...MID);\r\n        doc.text(\"WHAT'S INCLUDED\", M+14, y+22);\r\n        let cy = y + 40;\r\n        d.inclusions.forEach(p => {\r\n          doc.setFont('helvetica','bold').setFontSize(11);\r\n          doc.setTextColor(...MID);\r\n          doc.text('+', M + 16, cy);\r\n          doc.setFont('helvetica','normal').setFontSize(9.5);\r\n          doc.setTextColor(...TEXT);\r\n          const lines = doc.splitTextToSize(pdfSafe(p), halfW - 32);\r\n          doc.text(lines, M + 26, cy);\r\n          cy += lines.length * 13 + 4;\r\n        });\r\n\r\n        const xL = M + halfW + 14;\r\n        doc.setFillColor(254,247,245);\r\n        doc.setDrawColor(252,210,200).setLineWidth(0.6);\r\n        doc.roundedRect(xL, y, halfW, ieH, 8, 8, 'FD');\r\n        doc.setFont('helvetica','bold').setFontSize(10);\r\n        doc.setTextColor(...RED);\r\n        doc.text(\"WHAT'S NOT INCLUDED\", xL+14, y+22);\r\n        cy = y + 40;\r\n        d.exclusions.forEach(p => {\r\n          doc.setFont('helvetica','bold').setFontSize(11);\r\n          doc.setTextColor(...RED);\r\n          doc.text('x', xL + 16, cy);\r\n          doc.setFont('helvetica','normal').setFontSize(9.5);\r\n          doc.setTextColor(...TEXT);\r\n          const lines = doc.splitTextToSize(pdfSafe(p), halfW - 32);\r\n          doc.text(lines, xL + 26, cy);\r\n          cy += lines.length * 13 + 4;\r\n        });\r\n      }\r\n    });\r\n\r\n    \/\/ Optional Add-ons\r\n    if(d.addons.length){\r\n      let ah = 38;\r\n      d.addons.forEach(a => {\r\n        const desc = a.name + (a.note ? ' - ' + a.note : '') + (a.price ? ' (' + a.price + ')' : '');\r\n        const lines = doc.splitTextToSize(pdfSafe(desc), W - 2*M - 36);\r\n        ah += lines.length * 13 + 4;\r\n      });\r\n      ah += 14;\r\n      blocks.push({\r\n        type:'addons',\r\n        h: ah,\r\n        render: y => {\r\n          doc.setFillColor(...AMBER_BG);\r\n          doc.setDrawColor(...AMBER_BORDER).setLineWidth(0.6);\r\n          doc.roundedRect(M, y, W-2*M, ah, 8, 8, 'FD');\r\n          doc.setFillColor(217,119,6);\r\n          doc.roundedRect(M, y, 4, ah, 2, 2, 'F');\r\n          doc.setFont('helvetica','bold').setFontSize(10);\r\n          doc.setTextColor(...AMBER_TEXT);\r\n          doc.text('OPTIONAL ADD-ONS - SUBJECT TO AVAILABILITY', M+15, y+22);\r\n          doc.setFont('helvetica','normal').setFontSize(9.5);\r\n          doc.setTextColor(92,69,0);\r\n          let cy = y + 40;\r\n          d.addons.forEach(a => {\r\n            doc.setFillColor(...AMBER_TEXT);\r\n            doc.circle(M + 18, cy - 2, 1.5, 'F');\r\n            const desc = a.name + (a.note ? ' - ' + a.note : '') + (a.price ? ' (' + a.price + ')' : '');\r\n            const lines = doc.splitTextToSize(pdfSafe(desc), W - 2*M - 36);\r\n            doc.text(lines, M + 26, cy);\r\n            cy += lines.length * 13 + 4;\r\n          });\r\n        }\r\n      });\r\n    }\r\n\r\n    \/\/ Documents & Permits \u2014 after addons so it pairs with the Local Tip\r\n    if(docsBlock) blocks.push(docsBlock);\r\n\r\n    \/\/ Local Tip callout\r\n    if(finalLocalTip){\r\n      const tipText = pdfSafe(finalLocalTip);\r\n      doc.setFont('helvetica','normal').setFontSize(9.5);\r\n      const paragraphs = tipText.split(\/\\n\\n+\/).map(p => p.trim()).filter(Boolean);\r\n      const wrappedParas = paragraphs.map(p => doc.splitTextToSize(p, W - 2*M - 50));\r\n      const lineH = 13;\r\n      const paraGap = 8;\r\n      let bodyH = 0;\r\n      wrappedParas.forEach((lines, i) => {\r\n        bodyH += lines.length * lineH;\r\n        if(i < wrappedParas.length - 1) bodyH += paraGap;\r\n      });\r\n      const th = 36 + bodyH + 18;\r\n      blocks.push({\r\n        type:'tip',\r\n        h: th,\r\n        render: y => {\r\n          doc.setFillColor(...CREAM);\r\n          doc.setDrawColor(...BORDER).setLineWidth(0.6);\r\n          doc.roundedRect(M, y, W-2*M, th, 8, 8, 'FD');\r\n          doc.setFillColor(...DARK);\r\n          doc.roundedRect(M, y, 4, th, 2, 2, 'F');\r\n          doc.setFont('helvetica','bold').setFontSize(10);\r\n          doc.setTextColor(...DARK);\r\n          doc.text('HIMTRAILS LOCAL TIP', M+15, y+22);\r\n          doc.setFont('helvetica','normal').setFontSize(9.5);\r\n          doc.setTextColor(...TEXT);\r\n          let cy = y + 40;\r\n          wrappedParas.forEach((lines, i) => {\r\n            doc.text(lines, M+15, cy);\r\n            cy += lines.length * lineH;\r\n            if(i < wrappedParas.length - 1) cy += paraGap;\r\n          });\r\n        }\r\n      });\r\n    }\r\n\r\n    return blocks;\r\n  }\r\n\r\n  \/* ========== FINAL CTA ========== *\/\r\n  function renderCtaHero(yStart){\r\n    let cy = yStart;\r\n\r\n    doc.setFont('helvetica','italic').setFontSize(10);\r\n    doc.setTextColor(...MID);\r\n    doc.text(pdfSafe(d.trip.title), W\/2, cy, {align:'center'});\r\n    cy += 8;\r\n    doc.setDrawColor(...MID).setLineWidth(0.7);\r\n    doc.line(W\/2 - 22, cy, W\/2 + 22, cy);\r\n    cy += 32;\r\n\r\n    doc.setFont('helvetica','bold').setFontSize(26);\r\n    doc.setTextColor(...DARK);\r\n    doc.text('Ready to make this real?', W\/2, cy, {align:'center'});\r\n    cy += 20;\r\n\r\n    doc.setFont('helvetica','normal').setFontSize(11);\r\n    doc.setTextColor(...TEXT);\r\n    doc.text(\r\n      'Get a FREE customised quote with hotels, cabs & inclusions tailored to your dates.',\r\n      W\/2, cy, {align:'center'}\r\n    );\r\n    cy += 30;\r\n\r\n    const pillW = 280, pillH = 48;\r\n    const pillX = (W - pillW) \/ 2;\r\n    doc.setFillColor(...DARK);\r\n    doc.roundedRect(pillX, cy, pillW, pillH, 24, 24, 'F');\r\n    doc.setFont('helvetica','bold').setFontSize(13);\r\n    doc.setTextColor(255,255,255);\r\n    doc.text('WhatsApp:  +91 77176 97177', W\/2, cy + 30, {align:'center'});\r\n    cy += pillH + 14;\r\n\r\n    doc.setFont('helvetica','normal').setFontSize(10);\r\n    doc.setTextColor(...MUTED);\r\n    doc.text('Website: www.himtrails.in   |   Email: go.himtrails@gmail.com', W\/2, cy, {align:'center'});\r\n    cy += 30;\r\n\r\n    const sumW = 320, sumH = 70;\r\n    const sumX = (W - sumW) \/ 2;\r\n    doc.setFillColor(...PAPER);\r\n    doc.setDrawColor(...BORDER).setLineWidth(0.6);\r\n    doc.roundedRect(sumX, cy, sumW, sumH, 8, 8, 'FD');\r\n    doc.setFont('helvetica','bold').setFontSize(8);\r\n    doc.setTextColor(...MID);\r\n    doc.text('THIS QUOTE IS FOR', sumX + 14, cy + 16);\r\n    doc.setFont('helvetica','bold').setFontSize(11);\r\n    doc.setTextColor(...DARK);\r\n    const paxText = d.customer.adults + ' Adult' + (d.customer.adults>1?'s':'') +\r\n                    (d.customer.children>0 ? ' + ' + d.customer.children + ' Child' + (d.customer.children>1?'ren':'') : '');\r\n    doc.text(pdfSafe(d.customer.name) + ' - ' + paxText, sumX + 14, cy + 31);\r\n\r\n    doc.setDrawColor(...BORDER).setLineWidth(0.4);\r\n    doc.line(sumX + 14, cy + 40, sumX + sumW - 14, cy + 40);\r\n\r\n    doc.setFont('helvetica','bold').setFontSize(8);\r\n    doc.setTextColor(...MID);\r\n    doc.text('TOTAL TRIP COST', sumX + 14, cy + 56);\r\n    doc.setFont('helvetica','bold').setFontSize(13);\r\n    doc.setTextColor(...DARK);\r\n    doc.text(fmtMoneyPDF(d.trip.cost), sumX + sumW - 14, cy + 56, {align:'right'});\r\n\r\n    cy += sumH;\r\n    return cy - yStart;\r\n  }\r\n\r\n  function renderCtaDisclaimer(){\r\n    doc.setFont('helvetica','italic').setFontSize(8);\r\n    doc.setTextColor(...MUTED);\r\n    doc.text(\r\n      'Itinerary customised by HimTrails - hotels, cabs & meals subject to availability at confirmation.',\r\n      W\/2, H - 80, {align:'center'}\r\n    );\r\n    doc.text(\r\n      'All prices include applicable taxes unless stated otherwise. Permits subject to government rules on travel dates.',\r\n      W\/2, H - 68, {align:'center'}\r\n    );\r\n  }\r\n\r\n  function renderCtaPage(){\r\n    brandHeaderBand();\r\n    const heroH = 270;\r\n    const yStart = (H - heroH) \/ 2 - 10;\r\n    renderCtaHero(yStart);\r\n    renderCtaDisclaimer();\r\n  }\r\n\r\n  \/* ========== LAYOUT ENGINE ========== *\/\r\n  renderCover();\r\n\r\n  const contentTop = 80;\r\n  const contentBottom = H - 50;\r\n\r\n  const flowBlocks = [];\r\n\r\n  flowBlocks.push({\r\n    type:'section_heading',\r\n    label:'DAY-BY-DAY ITINERARY',\r\n    h: 44,\r\n    keepWithNext: true,\r\n    render: (y) => {\r\n      doc.setFont('helvetica','bold').setFontSize(20);\r\n      doc.setTextColor(...DARK);\r\n      doc.text('Day-by-Day Itinerary', M, y + 22);\r\n      doc.setFillColor(...MID);\r\n      doc.rect(M, y + 28, 36, 2.5, 'F');\r\n      doc.setFont('helvetica','normal').setFontSize(10);\r\n      doc.setTextColor(...MUTED);\r\n      const dateRange = d.pickup.date && d.drop.date\r\n        ? fmtDate(d.pickup.date) + ' to ' + fmtDate(d.drop.date)\r\n        : pdfSafe(fullTagLine);\r\n      doc.text(pdfSafe(dateRange), W-M, y + 22, {align:'right'});\r\n    }\r\n  });\r\n\r\n  d.days.forEach((day, i) => {\r\n    flowBlocks.push({\r\n      type:'day',\r\n      idx: i,\r\n      h: measureDayH(day),\r\n      render: y => renderDay(day, i, d.days.length, y),\r\n      gapAfter: 8\r\n    });\r\n  });\r\n\r\n  essentialsBlocks().forEach(b => flowBlocks.push({...b, gapAfter: 10}));\r\n\r\n  doc.addPage();\r\n  brandHeaderBand();\r\n  continuationStrip(74);\r\n  let cy = contentTop + 14;\r\n\r\n  function newContentPage(){\r\n    pageFooter();\r\n    doc.addPage();\r\n    brandHeaderBand();\r\n    continuationStrip(74);\r\n    cy = contentTop + 14;\r\n  }\r\n\r\n  for(let i = 0; i < flowBlocks.length; i++){\r\n    const b = flowBlocks[i];\r\n    let need = b.h + (b.gapAfter || 0);\r\n    if(b.keepWithNext && flowBlocks[i+1]){\r\n      need += flowBlocks[i+1].h + 6;\r\n    }\r\n    if(cy + need > contentBottom){\r\n      newContentPage();\r\n    }\r\n    b.render(cy);\r\n    cy += b.h + (b.gapAfter || 0);\r\n  }\r\n\r\n  \/\/ CTA placement: inline if it fits, otherwise dedicated final page\r\n  const ctaInlineH = 280;\r\n  const ctaTopGap = 30;\r\n  if(cy + ctaTopGap + ctaInlineH <= contentBottom){\r\n    renderCtaHero(cy + ctaTopGap);\r\n    pageFooter();\r\n  } else {\r\n    pageFooter();\r\n    doc.addPage();\r\n    renderCtaPage();\r\n    pageFooter();\r\n  }\r\n\r\n  \/* --- Re-stamp page numbers on every page --- *\/\r\n  const totalPages = doc.internal.getNumberOfPages();\r\n  for(let p = 1; p <= totalPages; p++){\r\n    doc.setPage(p);\r\n    if(p === 1){\r\n      doc.setFillColor(255,255,255);\r\n      doc.rect(0, H-40, W, 40, 'F');\r\n      doc.setDrawColor(...BORDER).setLineWidth(0.5);\r\n      doc.line(M, H-30, W-M, H-30);\r\n      doc.setFont('helvetica','normal').setFontSize(8);\r\n      doc.setTextColor(...MUTED);\r\n      doc.text('HimTrails - Breathe the Heights, Live the Trails', M, H-15);\r\n      doc.setFont('helvetica','bold');\r\n      doc.setTextColor(...DARK);\r\n      doc.text('WhatsApp +91 77176 97177 - www.himtrails.in', W-M, H-15, {align:'right'});\r\n    } else {\r\n      doc.setFillColor(255,255,255);\r\n      doc.rect(W - M - 110, 60, 110, 22, 'F');\r\n      doc.setFont('helvetica','normal').setFontSize(8.5);\r\n      doc.setTextColor(...MUTED);\r\n      doc.text('Page ' + p + ' \/ ' + totalPages, W-M, 74, {align:'right'});\r\n\r\n      doc.setFillColor(255,255,255);\r\n      doc.rect(0, H-40, W, 40, 'F');\r\n      doc.setDrawColor(...BORDER).setLineWidth(0.5);\r\n      doc.line(M, H-30, W-M, H-30);\r\n      doc.setFont('helvetica','normal').setFontSize(8);\r\n      doc.setTextColor(...MUTED);\r\n      doc.text('HimTrails - Breathe the Heights, Live the Trails', M, H-15);\r\n      doc.setFont('helvetica','bold');\r\n      doc.setTextColor(...DARK);\r\n      doc.text('WhatsApp +91 77176 97177 - www.himtrails.in   |   Page ' + p + ' \/ ' + totalPages, W-M, H-15, {align:'right'});\r\n    }\r\n  }\r\n\r\n  const fname = 'HimTrails-' + (d.customer.name||'Quote').replace(\/[^a-zA-Z0-9]+\/g,'-') + '-' +\r\n                new Date().toISOString().slice(0,10).replace(\/-\/g,'') + '.pdf';\r\n  doc.save(fname);\r\n  showToast('PDF generated');\r\n}\r\n<\/script>\r\n<\/body>\r\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>HimTrails \u2014 Custom Itinerary Builder HimTrails \u2014 Custom Itinerary Builder Generate brand-grade PDF quotes for confirmed customers Load Draft Save Draft Reset Fill the form below, then click Generate PDF at the bottom. Save your work as a draft (.json file) any time \u2014 reload it later to edit. Covers all Himachal destinations + Leh [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_canvas","meta":{"_acf_changed":false,"googlesitekit_rrm_CAowjtHDDA:productID":"","ocean_front_end_style_editor":"no","ocean_post_layout":"","ocean_both_sidebars_style":"","ocean_both_sidebars_content_width":0,"ocean_both_sidebars_sidebars_width":0,"ocean_sidebar":"","ocean_second_sidebar":"","ocean_disable_margins":"enable","ocean_add_body_class":"","ocean_shortcode_before_top_bar":"","ocean_shortcode_after_top_bar":"","ocean_shortcode_before_header":"","ocean_shortcode_after_header":"","ocean_has_shortcode":"","ocean_shortcode_after_title":"","ocean_shortcode_before_footer_widgets":"","ocean_shortcode_after_footer_widgets":"","ocean_shortcode_before_footer_bottom":"","ocean_shortcode_after_footer_bottom":"","ocean_display_top_bar":"default","ocean_display_header":"default","ocean_header_style":"","ocean_center_header_left_menu":"","ocean_custom_header_template":"","ocean_custom_logo":0,"ocean_custom_retina_logo":0,"ocean_custom_logo_max_width":0,"ocean_custom_logo_tablet_max_width":0,"ocean_custom_logo_mobile_max_width":0,"ocean_custom_logo_max_height":0,"ocean_custom_logo_tablet_max_height":0,"ocean_custom_logo_mobile_max_height":0,"ocean_header_custom_menu":"","ocean_menu_typo_font_family":"","ocean_menu_typo_font_subset":"","ocean_menu_typo_font_size":0,"ocean_menu_typo_font_size_tablet":0,"ocean_menu_typo_font_size_mobile":0,"ocean_menu_typo_font_size_unit":"px","ocean_menu_typo_font_weight":"","ocean_menu_typo_font_weight_tablet":"","ocean_menu_typo_font_weight_mobile":"","ocean_menu_typo_transform":"","ocean_menu_typo_transform_tablet":"","ocean_menu_typo_transform_mobile":"","ocean_menu_typo_line_height":0,"ocean_menu_typo_line_height_tablet":0,"ocean_menu_typo_line_height_mobile":0,"ocean_menu_typo_line_height_unit":"","ocean_menu_typo_spacing":0,"ocean_menu_typo_spacing_tablet":0,"ocean_menu_typo_spacing_mobile":0,"ocean_menu_typo_spacing_unit":"","ocean_menu_link_color":"","ocean_menu_link_color_hover":"","ocean_menu_link_color_active":"","ocean_menu_link_background":"","ocean_menu_link_hover_background":"","ocean_menu_link_active_background":"","ocean_menu_social_links_bg":"","ocean_menu_social_hover_links_bg":"","ocean_menu_social_links_color":"","ocean_menu_social_hover_links_color":"","ocean_disable_title":"default","ocean_disable_heading":"default","ocean_post_title":"","ocean_post_subheading":"","ocean_post_title_style":"","ocean_post_title_background_color":"","ocean_post_title_background":0,"ocean_post_title_bg_image_position":"","ocean_post_title_bg_image_attachment":"","ocean_post_title_bg_image_repeat":"","ocean_post_title_bg_image_size":"","ocean_post_title_height":0,"ocean_post_title_bg_overlay":0.5,"ocean_post_title_bg_overlay_color":"","ocean_disable_breadcrumbs":"default","ocean_breadcrumbs_color":"","ocean_breadcrumbs_separator_color":"","ocean_breadcrumbs_links_color":"","ocean_breadcrumbs_links_hover_color":"","ocean_display_footer_widgets":"default","ocean_display_footer_bottom":"default","ocean_custom_footer_template":"","footnotes":""},"class_list":["post-16217","page","type-page","status-publish","hentry","entry"],"acf":[],"_links":{"self":[{"href":"https:\/\/himtrails.in\/hi\/wp-json\/wp\/v2\/pages\/16217","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/himtrails.in\/hi\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/himtrails.in\/hi\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/himtrails.in\/hi\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/himtrails.in\/hi\/wp-json\/wp\/v2\/comments?post=16217"}],"version-history":[{"count":20,"href":"https:\/\/himtrails.in\/hi\/wp-json\/wp\/v2\/pages\/16217\/revisions"}],"predecessor-version":[{"id":987758,"href":"https:\/\/himtrails.in\/hi\/wp-json\/wp\/v2\/pages\/16217\/revisions\/987758"}],"wp:attachment":[{"href":"https:\/\/himtrails.in\/hi\/wp-json\/wp\/v2\/media?parent=16217"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}