Skip to main content

Seat Selection

Posts tagged with "Seat Selection"

Browse: All Pilgrimage Spiritual Travel Temple Guide International Trips Budget Travel Air India IndiGo Aviation News New Routes Travel News 2026 Travel Guide Andhra Pradesh Qatar Airways Travel Advisory Middle East honeymoon Airlines Emirates Flight Disruptions

Seat Selection Articles

Free Seat Selection on 60% of Flights: What India's New Airline Rule Means for You
Mar 23, 2026

Free Seat Selection on 60% of Flights: What India's New Airline Rule Means for You

The Indian government just mandated airlines to offer free seat selection on at least 60% of seats — up from the current 20%. Here's what travellers need to know, why airlines are pushing back, and how it could affect your next flight booking.
flights india aviation passenger rights
Read more →
DGCA's New Rule: 60% of Flight Seats Must Be Free — What It Means for You
Mar 19, 2026

DGCA's New Rule: 60% of Flight Seats Must Be Free — What It Means for You

India just made flying cheaper. The DGCA now requires airlines to offer at least 60% of seats on every flight without any selection charge. Families on the same PNR must be seated together. Here's everything you need to know.
DGCA Passenger Rights Policy Update
Read more →
in the input terminates this script block (reflected XSS). // HEX_TAG emits \u003C / \u003E — functionally identical to JS. var CTX = {"cta_type":"flights_landing","url":"/blog?tag=Seat+Selection","nearest_airport":null,"route_from":null,"route_to":null}; var BASE = "https:\/\/www.fareeagle.com"; var PREVIEW_REQ = null; var INTENT = "flights"; var LS_KEY = 'fecta_dismissed_v2'; var LS_CAPTURED = 'fecta_captured_v2'; var LS_TTL_DAYS = 7; // dismiss: 7 days (respect user's close action) var LS_CAPTURED_TTL_DAYS = 30; // captured: 30 days (re-prompt after a month — handles cleared cache, route changes, updated numbers) // ============================================================ // Admin / testing bypass — visit any page with ?fecta_test=1 to // force-show the modal regardless of localStorage state. Great for QA, // stakeholder demos, and owner previews without DevTools. // ============================================================ var FECTA_TEST = false; try { FECTA_TEST = /[?&]fecta_test=1(&|$)/.test(window.location.search); } catch(e){} // ============================================================ // Bot detection — computed ONCE, up front, used in two places: // 1. fetchPreview() is skipped for bots. Googlebot's renderer DOES // execute requestIdleCallback'd fetches, so previously every bot // render POSTed to /api/fare-preview.php → FarePreviewService → // DB. At crawl volumes that's the per-request-DB-work pattern // that tripped Google's host throttler in May. Bots don't need // hydrated prices; the server-rendered HTML is what they index. // 2. Modal trigger registration is skipped (below) so crawlers // never see an open modal / intrusive interstitial. // Impression logging still fires for bots — intentional, keeps // analytics visibility into what bots are crawling (sendBeacon is a // single cheap insert, not DB-heavy like fare-preview). // ============================================================ var FECTA_BOT_RE = /bot|crawler|spider|crawling|googlebot|bingbot|slurp|duckduckbot|baiduspider|yandex|sogou|exabot|facebookexternalhit|twitterbot|linkedinbot|whatsapp|telegram|ahrefs|semrush|mj12|dotbot|applebot|petalbot|seznam|gptbot|chatgpt|ccbot|anthropic|claude|perplexity|indexnow|headlesschrome|phantomjs|puppeteer|playwright|selenium/i; var FECTA_IS_BOT = FECTA_BOT_RE.test(navigator.userAgent || ''); var backdrop = document.getElementById(ID + '-backdrop'); if (!backdrop) return; var fab = document.querySelector('[data-fecta-fab="'+ID+'"]'); var shown = false; var dismissed = false; var triggered = false; var previewContainer = backdrop.querySelector('[data-fecta-preview]'); var inlinePriceSlot = document.querySelector('[data-fecta-inline-price]'); // Focus trap state var lastFocusedBeforeOpen = null; var focusableSelector = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'; // ============================================================ // Frequency cap helpers // ============================================================ function withinCap(key, ttlDays) { try { var raw = localStorage.getItem(key); if (!raw) return false; var t = parseInt(raw, 10); if (!t) return false; return (Date.now() - t) < ttlDays * 24 * 3600 * 1000; } catch(e){ return false; } } function isRecentlyDismissed(){ return withinCap(LS_KEY, LS_TTL_DAYS); } function isCaptured(){ // Legacy values may have been stored as '1' (no timestamp). Treat those // as captured-forever-for-now BUT honor the 30-day cap by reading the // stored string as a timestamp if numeric, else grandfather to 30 days ago. try { var raw = localStorage.getItem(LS_CAPTURED); if (!raw) return false; var t = parseInt(raw, 10); if (!t || isNaN(t) || t < 1000000000000) { // Legacy '1' value — migrate to a timestamp 29 days ago so user // gets one more day of quiet, then modal re-surfaces. try { localStorage.setItem(LS_CAPTURED, String(Date.now() - 29*24*3600*1000)); } catch(e){} return true; } return (Date.now() - t) < LS_CAPTURED_TTL_DAYS * 24 * 3600 * 1000; } catch(e){ return false; } } function markDismissed(){ try { localStorage.setItem(LS_KEY, String(Date.now())); } catch(e){} if (fab && !isCaptured()) fab.classList.add('show'); } function markCaptured(){ // Store timestamp instead of '1' so we can age it out try { localStorage.setItem(LS_CAPTURED, String(Date.now())); } catch(e){} if (fab) fab.classList.remove('show'); } // ============================================================ // Activity logging (non-blocking, sendBeacon preferred) // ============================================================ function log(event, extra){ try { var body = Object.assign({ type: event, cta_type: TYPE }, CTX, extra || {}); var url = BASE + '/api/log-activity.php'; if (navigator.sendBeacon) { navigator.sendBeacon(url, new Blob([JSON.stringify(body)], {type:'application/json'})); } else { fetch(url, { method: 'POST', body: JSON.stringify(body), keepalive: true, headers: {'Content-Type':'application/json'} }).catch(function(){}); } } catch(e){} } // ============================================================ // Focus trap — keeps keyboard focus inside modal while open // ============================================================ function trapFocus(e){ if (!shown || e.key !== 'Tab') return; var focusables = backdrop.querySelectorAll(focusableSelector); if (!focusables.length) return; var first = focusables[0]; var last = focusables[focusables.length - 1]; if (e.shiftKey) { if (document.activeElement === first) { e.preventDefault(); last.focus(); } } else { if (document.activeElement === last) { e.preventDefault(); first.focus(); } } } // ============================================================ // Open / close // ============================================================ function openModal(trigger){ if (shown || dismissed) return; shown = true; lastFocusedBeforeOpen = document.activeElement; backdrop.classList.add('open'); document.body.style.overflow = 'hidden'; if (fab) fab.classList.remove('show'); // Move focus into modal (close button is always present and focusable) setTimeout(function(){ var closeBtn = backdrop.querySelector('[data-fecta-close]'); if (closeBtn) closeBtn.focus(); }, 50); log('cta_modal_open', { trigger: trigger || 'auto' }); } function closeModal(reason){ if (!shown) return; shown = false; dismissed = true; backdrop.classList.remove('open'); document.body.style.overflow = ''; markDismissed(); // Restore focus to trigger element if (lastFocusedBeforeOpen && lastFocusedBeforeOpen.focus) { try { lastFocusedBeforeOpen.focus(); } catch(e){} } log('cta_modal_close', { reason: reason || 'manual' }); } // ============================================================ // Single-fire trigger guard // ============================================================ function safeOpen(reason){ if (triggered || dismissed || shown) return; triggered = true; openModal(reason); } // ============================================================ // Close handlers // ============================================================ backdrop.addEventListener('click', function(e){ if (e.target === backdrop) closeModal('backdrop'); }); document.querySelectorAll('[data-fecta-close="'+ID+'"]').forEach(function(b){ b.addEventListener('click', function(){ closeModal('close_btn'); }); }); document.addEventListener('keydown', function(e){ if (!shown) return; if (e.key === 'Escape') { closeModal('escape'); return; } trapFocus(e); }); // Open from inline "More options" / FAB buttons document.querySelectorAll('[data-fecta-open="'+ID+'"]').forEach(function(b){ b.addEventListener('click', function(){ openModal('inline_btn'); }); }); if (fab) fab.addEventListener('click', function(){ openModal('fab'); }); // CTA click logging (links don't need preventDefault — user navigates) document.querySelectorAll('[data-fecta-cta]').forEach(function(a){ a.addEventListener('click', function(){ log('cta_click', { action: a.getAttribute('data-fecta-cta') }); }); }); // ============================================================ // Phone capture // ============================================================ var form = document.querySelector('[data-fecta-phone="'+ID+'"]'); if (form) { form.addEventListener('submit', function(e){ e.preventDefault(); var phone = form.querySelector('input[type=tel]').value.replace(/\D/g,''); var msg = form.parentElement.querySelector('.wa-msg'); var btn = form.querySelector('button'); msg.className = 'wa-msg'; msg.textContent = ''; if (!/^[6-9]\d{9}$/.test(phone)) { msg.className = 'wa-msg error'; msg.textContent = 'Please enter a valid 10-digit Indian mobile number.'; return; } btn.disabled = true; btn.textContent = 'Sending…'; fetch(BASE + '/api/capture-lead.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', body: JSON.stringify({ phone: phone, source: 'exit_intent', context: CTX }) }).then(function(r){ return r.json(); }).then(function(j){ if (j && j.ok) { msg.className = 'wa-msg success'; msg.textContent = '✓ Done. We\'ll WhatsApp you when prices move.'; markCaptured(); log('cta_lead_captured', { score: j.score, bucket: j.bucket }); setTimeout(function(){ closeModal('after_capture'); }, 1600); } else if (j && j.error === 'too_many_requests') { msg.className = 'wa-msg error'; msg.textContent = 'Too many attempts. Please try again later.'; btn.disabled = false; btn.textContent = 'Notify me'; } else { msg.className = 'wa-msg error'; msg.textContent = (j && j.error === 'invalid_phone') ? 'Please enter a valid number.' : 'Could not save. Please try again.'; btn.disabled = false; btn.textContent = 'Notify me'; } }).catch(function(){ msg.className = 'wa-msg error'; msg.textContent = 'Network issue. Please try again.'; btn.disabled = false; btn.textContent = 'Notify me'; }); }); } // ============================================================ // Client-side impression log (replaces server-side logging to keep TTFB fast) // Fires once per page load, after DOM is ready. // ============================================================ function logImpression(){ log('cta_impression', { route: (CTX.route_from && CTX.route_to) ? (CTX.route_from + '-' + CTX.route_to) : null, intent: INTENT, has_preview_req: !!PREVIEW_REQ }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', logImpression); } else { logImpression(); } // ============================================================ // Preview hydration — fetch prices from /api/fare-preview.php // Fills modal cards + inline strip price slot // ============================================================ function indianFormat(n){ if (typeof n !== 'number' || !isFinite(n)) return ''; // Indian number formatting: 12,34,567 not 1,234,567 var s = Math.round(n).toString(); if (s.length <= 3) return s; var last3 = s.slice(-3); var rest = s.slice(0, -3); rest = rest.replace(/\B(?=(\d{2})+(?!\d))/g, ','); return rest + ',' + last3; } function formatINR(n){ return '₹' + indianFormat(n); } function airlineLogoUrl(code){ if (!code) return ''; // Must match FarePreviewService::airlineLogoUrl() — uses Aviasales CDN return 'https://pics.avs.io/80/40/' + encodeURIComponent(String(code).toUpperCase()) + '.png'; } function safeText(s){ return (s == null) ? '' : String(s); } function escapeAttr(s){ return String(s == null ? '' : s) .replace(/&/g,'&').replace(/"/g,'"') .replace(//g,'>'); } function renderFlightCard(f){ var html = '
' + '
Flights from
' + '
' + indianFormat(f.from) + ' ' + (f.freshness === 'fresh' ? '●' : '○') + '
'; if (f.direct_from) { html += '
Direct from ₹' + indianFormat(f.direct_from) + '
'; } else { html += '
'; } if (f.airlines && f.airlines.length) { html += '
'; f.airlines.forEach(function(a){ html += '' + escapeAttr(a.name) + ''; }); html += '
'; } html += '
'; return html; } function renderHotelCard(h){ var subParts = []; if (h.avg_stars) { var stars = Math.max(1, Math.min(5, h.avg_stars|0)); subParts.push(new Array(stars + 1).join('★')); } if (h.cheapest_name) { var name = h.cheapest_name; if (name.length > 24) name = name.slice(0, 24) + '…'; subParts.push(escapeAttr(name)); } return '
' + '
Hotels from
' + '
' + indianFormat(h.from_per_night) + ' /night
' + '
' + subParts.join(' ') + '
' + '
'; } function renderTripCard(t){ return '
' + '
Full trip from
' + '
' + indianFormat(t.combined_from) + '
' + '
' + (t.nights|0) + 'n · ' + (t.adults|0) + ' adults
' + '
'; } function hydratePreview(data){ if (!data) return; var cardOrderMap = { 'hotels': ['hotel', 'trip', 'flight'], 'trip': ['trip', 'hotel', 'flight'], 'flights': ['flight', 'trip', 'hotel'] }; var order = cardOrderMap[INTENT] || ['flight', 'trip', 'hotel']; var cardCount = (data.flight ? 1 : 0) + (data.hotel ? 1 : 0) + (data.trip ? 1 : 0); if (cardCount === 0) return; // Modal cards if (previewContainer) { var html = '
'; for (var i = 0; i < order.length; i++) { var k = order[i]; if (k === 'flight' && data.flight) html += renderFlightCard(data.flight); else if (k === 'hotel' && data.hotel) html += renderHotelCard(data.hotel); else if (k === 'trip' && data.trip) html += renderTripCard(data.trip); } html += '
'; previewContainer.innerHTML = html; previewContainer.hidden = false; previewContainer.removeAttribute('aria-hidden'); } // Inline strip price if (inlinePriceSlot) { var inlinePrice = null; if (INTENT === 'flights' && data.flight && data.flight.from) { inlinePrice = formatINR(data.flight.from) + '+'; } else if (INTENT === 'hotels' && data.hotel && data.hotel.from_per_night) { inlinePrice = formatINR(data.hotel.from_per_night) + '/night+'; } else if (INTENT === 'trip' && data.trip && data.trip.combined_from) { inlinePrice = formatINR(data.trip.combined_from) + '+'; } if (inlinePrice) { inlinePriceSlot.textContent = ' · ' + inlinePrice; inlinePriceSlot.style.color = '#e8734a'; inlinePriceSlot.hidden = false; } } } function fetchPreview(){ if (!PREVIEW_REQ) return; // Defer until idle / after DOM ready to avoid competing with LCP resources var run = function(){ fetch(BASE + '/api/fare-preview.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, credentials: 'same-origin', body: JSON.stringify(PREVIEW_REQ) }).then(function(r){ return r.ok ? r.json() : null; }) .then(function(j){ if (j && j.ok && j.data && j.data.has_data) hydratePreview(j.data); }).catch(function(){ /* silent */ }); }; if (window.requestIdleCallback) { window.requestIdleCallback(run, { timeout: 2000 }); } else { setTimeout(run, 300); } } if (!FECTA_IS_BOT) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', fetchPreview); } else { fetchPreview(); } } // ============================================================ // Trigger strategy — multi-signal, anti-false-positive // ============================================================ // DESKTOP triggers (first-of): // 1. Exit-intent — cursor exits viewport upward, with X-bounds check // to avoid false positives from cursor moving to tabs/bookmarks // 2. Tab hidden (visibilitychange) — user switches away // 3. 60% scroll + 4s idle — engaged reader pausing // 4. 75s dwell timer — lingering visitor // // MOBILE triggers (first-of): // 1. 50% scroll + 5s idle // 2. 60s dwell // 3. pagehide — iOS Safari tab-close equivalent // // All respect frequency cap. safeOpen() guarantees fires-once-per-page. // beforeunload intentionally NOT used — browsers ignore DOM mutations there. // ============================================================ // ============================================================ // Bot suppression — modal never opens for crawlers // ============================================================ // Flag computed once near the top of this IIFE (FECTA_IS_BOT); see the // note there. Impression logging above still fired for bots (intentional // — analytics visibility into what bots crawl); fare-preview fetch and // everything below (modal trigger registration) is skipped so crawlers // never see an open modal that could be flagged as an intrusive // interstitial by Google's renderer. // ============================================================ if (FECTA_IS_BOT) { return; } // ?fecta_test=1 — admin / QA preview: force-open immediately, bypass all caps if (FECTA_TEST) { setTimeout(function(){ safeOpen('fecta_test'); }, 300); // Console breadcrumb so it's obvious this is a forced state try { console.info('[fecta] ?fecta_test=1 active — bypassing frequency caps'); } catch(e){} return; } if (isRecentlyDismissed() || isCaptured()) { if (fab && !isCaptured()) fab.classList.add('show'); return; } var isMobile = window.matchMedia && window.matchMedia('(max-width: 768px)').matches; // ============================================================ // Engagement gate for tab-hide / pagehide triggers // ------------------------------------------------------------ // visibilitychange (desktop) and pagehide (mobile) both fire when a tab // that was opened in the BACKGROUND becomes visible, or on very early // app-switches. Without a gate, a link opened in a background tab pops the // modal within seconds of the user first switching to it — before they've // read anything. We require two things before those signals may fire: // (a) the page was actually visible to the user at least once, and // (b) at least MIN_ENGAGE_MS of genuine foreground time has elapsed. // Exit-intent (mouseleave-up), scroll+idle, and the dwell timers are NOT // gated by this — they already imply engagement on their own. var MIN_ENGAGE_MS = 8000; // require ~8s of real viewing before tab-leave counts var everVisible = (document.visibilityState === 'visible'); var engagedAt = everVisible ? Date.now() : 0; document.addEventListener('visibilitychange', function(){ if (document.visibilityState === 'visible') { everVisible = true; if (!engagedAt) engagedAt = Date.now(); } }); function isEngaged(){ return everVisible && engagedAt > 0 && (Date.now() - engagedAt) >= MIN_ENGAGE_MS; } if (!isMobile) { // 1. Exit-intent — only fire when cursor genuinely exits the top of the viewport, // not when it moves to URL bar / tab strip / bookmark bar (those are still // within browser chrome but clientX can be valid while clientY hits 0). // Require cursor to be well within horizontal bounds to count as "exit up". document.addEventListener('mouseleave', function(e){ if (e.clientY > 0) return; // Ignore corner-chrome movements (nav buttons, menu, etc. at screen edges) var margin = 60; if (e.clientX < margin || e.clientX > (window.innerWidth - margin)) return; safeOpen('exit_intent'); }); // 2. Tab becomes hidden — "user is leaving" signal (Cmd+Tab, tab switch, // minimize, Cmd+W before teardown). Gated by isEngaged() so a // background-tab open can't fire it the instant the user switches in. document.addEventListener('visibilitychange', function(){ if (document.visibilityState === 'hidden' && isEngaged()) safeOpen('visibility_hidden'); }); // 3. 60% scroll + 4s idle var scrolledDesktop = false; var scrollIdleTimer = null; window.addEventListener('scroll', function(){ if (triggered || dismissed || scrolledDesktop) return; var h = document.documentElement; var denom = Math.max(h.scrollHeight, 1); var pct = (h.scrollTop + window.innerHeight) / denom; if (pct > 0.6) { scrolledDesktop = true; clearTimeout(scrollIdleTimer); scrollIdleTimer = setTimeout(function(){ safeOpen('scroll_idle'); }, 4000); } }, { passive: true }); // 4. 75-second dwell setTimeout(function(){ safeOpen('timer_75s'); }, 75000); } else { // MOBILE var scrolled = false; var idleTimer = null; window.addEventListener('scroll', function(){ if (triggered || dismissed || scrolled) return; var h = document.documentElement; var denom = Math.max(h.scrollHeight, 1); var pct = (h.scrollTop + window.innerHeight) / denom; if (pct > 0.5) { scrolled = true; clearTimeout(idleTimer); idleTimer = setTimeout(function(){ safeOpen('scroll_idle'); }, 5000); } }, { passive: true }); setTimeout(function(){ safeOpen('timer_60s'); }, 60000); window.addEventListener('pagehide', function(){ if (isEngaged()) safeOpen('pagehide'); }); } })();
AI trip planning with Aira
Ask, compare, and book in one chat.
Book on WhatsApp
Message us to book flights, hotels & buses.
Made in Guntur, AP
Built in India, for Indian travellers.
Secure bookings
256-bit encryption & verified payments.