{"id":586,"date":"2026-02-23T13:24:50","date_gmt":"2026-02-23T13:24:50","guid":{"rendered":"https:\/\/helsinki-tallinn.com\/ee\/?page_id=586"},"modified":"2026-02-23T13:36:04","modified_gmt":"2026-02-23T13:36:04","slug":"soiduplaan","status":"publish","type":"page","link":"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/","title":{"rendered":"Reaalajas s\u00f5iduplaan"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"586\" class=\"elementor elementor-586\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-96723e3 e-flex e-con-boxed e-con e-parent\" data-id=\"96723e3\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-755c614 elementor-widget elementor-widget-text-editor\" data-id=\"755c614\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Helsingi ja Tallinna vahelise \u00fcleveo t\u00e4ielik reaalajas s\u00f5iduplaan k\u00f5igi kolme operaatori kohta: <strong>Viking Line, Tallink Silja ja Ecker\u00f6 Line. Vaikimisi valik on 3 p\u00e4eva.<\/strong><\/p><h2>Helsingi Tallinna v\u00e4ljumisajad<\/h2><p><em>PS. Kui kl\u00f5psad laeva nimel, saad inglise keeles lisateavet laeva kohta, sealhulgas reaalajas j\u00e4lgimise.<\/em><\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-99e1b65 elementor-widget elementor-widget-shortcode\" data-id=\"99e1b65\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"shortcode.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-shortcode\"><div class=\"tp-wrap\" data-config=\"{&quot;rest&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/ee\\\/wp-json\\\/timetables-pro\\\/v1\\\/timetables&quot;,&quot;route&quot;:161,&quot;days&quot;:3,&quot;autoload&quot;:true,&quot;label&quot;:&quot;Helsingi - Talinna&quot;,&quot;labels&quot;:{&quot;ui_date&quot;:&quot;Kuup&quot;,&quot;ui_days&quot;:&quot;P\\u00e4evad&quot;,&quot;ui_button&quot;:&quot;N\\u00e4ita v\\u00e4ljum&quot;,&quot;status_idle&quot;:&quot;Vali kp ja N\\u00e4ita&quot;,&quot;status_loading&quot;:&quot;Laeb...&quot;,&quot;status_empty&quot;:&quot;No sailings found&quot;,&quot;status_found&quot;:&quot;Leiti %d v\\u00e4ljum&quot;,&quot;th_date&quot;:&quot;Kuup&quot;,&quot;th_dep&quot;:&quot;V\\u00e4l&quot;,&quot;th_arr&quot;:&quot;Saab&quot;,&quot;th_dur&quot;:&quot;Kest&quot;,&quot;th_ship&quot;:&quot;Laev&quot;,&quot;th_op&quot;:&quot;Oper&quot;,&quot;th_route&quot;:&quot;Liin&quot;,&quot;wd_sun&quot;:&quot;P&quot;,&quot;wd_mon&quot;:&quot;E&quot;,&quot;wd_tue&quot;:&quot;T&quot;,&quot;wd_wed&quot;:&quot;K&quot;,&quot;wd_thu&quot;:&quot;N&quot;,&quot;wd_fri&quot;:&quot;R&quot;,&quot;wd_sat&quot;:&quot;L&quot;,&quot;summary_footer_one&quot;:&quot;\\u00bb Check all %s departures on our timetable&quot;,&quot;summary_footer_two&quot;:&quot;\\u00bb Check more %1$s and %2$s departures on our timetable&quot;,&quot;summary_footer_generic&quot;:&quot;Check our timetable for more sailings&quot;,&quot;summary_header&quot;:&quot;The next two departures:&quot;,&quot;summary_no_upcoming&quot;:&quot;No upcoming departures found&quot;,&quot;more_link&quot;:&quot;More sailings&quot;},&quot;showLogo&quot;:true,&quot;shipMap&quot;:{&quot;fi&quot;:&quot;Finlandia&quot;},&quot;shipLinks&quot;:{&quot;fi&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/finlandia\\\/&quot;,&quot;viking xprs&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/viking-xprs\\\/&quot;,&quot;mystar&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/mystar\\\/&quot;,&quot;victoria i&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/victoria\\\/&quot;,&quot;megastar&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/megastar\\\/&quot;},&quot;showShip&quot;:true}\">\n  <div class=\"tp-controls\" role=\"group\" aria-label=\"Timetable controls\">\n    <div>\n      <label for=\"tp-date\">Kuup<\/label><br>\n      <input id=\"tp-date\" class=\"tp-date\" type=\"date\" aria-label=\"Kuup\">\n    <\/div>\n    <div>\n      <label for=\"tp-range\">P\u00e4evad<\/label><br>\n      <select id=\"tp-range\" class=\"tp-range\" aria-label=\"P\u00e4evad\">\n        <option value=\"1\">1<\/option><option value=\"3\">3<\/option><option value=\"7\">7<\/option><option value=\"14\">14<\/option>\n      <\/select>\n    <\/div>\n    <div><button id=\"tp-load\" class=\"tp-btn\">N\u00e4ita v\u00e4ljum<\/button><\/div>\n  <\/div>\n\n  <div id=\"tp-status\" class=\"tp-muted\" aria-live=\"polite\">Vali kp ja N\u00e4ita<\/div>\n  <div id=\"tp-results\"><\/div>\n\n  <div class=\"tp-loader\" aria-hidden=\"true\">\n    <div class=\"tp-loader-card\">\n      <div class=\"tp-spinner\" aria-hidden=\"true\"><\/div>\n      <div class=\"tp-loader-text\">Laeb...<\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  const wrap   = document.currentScript.previousElementSibling;\n  const cfg    = JSON.parse(wrap.getAttribute('data-config')||'{}');\n  const L      = cfg.labels||{};\n  const dateEl = wrap.querySelector('#tp-date');\n  const daysEl = wrap.querySelector('#tp-range');\n  const btn    = wrap.querySelector('#tp-load');\n  const status = wrap.querySelector('#tp-status');\n  const out    = wrap.querySelector('#tp-results');\n  const loader = wrap.querySelector('.tp-loader');\n\n  const showShip = (cfg.showShip !== false && cfg.showShip !== 0 && cfg.showShip !== '0');\n\n  dateEl.valueAsDate = new Date();\n  Array.from(daysEl.options).forEach(o=>{ if(parseInt(o.value,10)===parseInt(cfg.days||7,10)) o.selected=true; });\n\n  function pad(n){ return String(n).padStart(2,'0'); }\n  function iso(d){ return d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate()); }\n  function toLocal(s){ return new Date(s); }\n  function hhmm(d){ return d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); }\n  function dShort(d){ return d.toLocaleDateString([], {day:'numeric', month:'short'}); } \/\/ short, no year\n  function dur(m){ const h=Math.floor(m\/60), r=m%60; return r? (h+'h '+r+'m') : (h+'h'); }\n\n  const WD = [\n    L.wd_sun || 'SUN',\n    L.wd_mon || 'MON',\n    L.wd_tue || 'TUE',\n    L.wd_wed || 'WED',\n    L.wd_thu || 'THU',\n    L.wd_fri || 'FRI',\n    L.wd_sat || 'SAT'\n  ];\n\n  function setLoader(v){ loader.classList.toggle('show', !!v); loader.setAttribute('aria-hidden', v?'false':'true'); }\n\n  function opCell(r){\n    const name = r.opName || ('Operator '+(r.opId||''));\n    const logo = (cfg.showLogo && r.opLogo) ? '<img decoding=\"async\" src=\"'+r.opLogo+'\" alt=\"'+name+'\"> ' : '';\n    const label = logo + '<span>'+name+'<\/span>';\n    return r.opLink ? '<a class=\"tp-op\" href=\"'+r.opLink+'\" target=\"_blank\" rel=\"nofollow noopener\">'+label+'<\/a>' : '<span class=\"tp-op\">'+label+'<\/span>';\n  }\n\n  function shipInfo(original){\n    const key = (original||'').toLowerCase().trim();\n    const label = (cfg.shipMap && cfg.shipMap[key]) || original || '';\n    const href  = (cfg.shipLinks && cfg.shipLinks[key]) || '';\n    return {label, href};\n  }\n\n  function shipBadge(original){\n    const s = shipInfo(original);\n    const badge = '<span class=\"tp-badge\">'+(s.label||'')+'<\/span>';\n    return s.href ? ('<a href=\"'+s.href+'\" target=\"_blank\" rel=\"nofollow noopener\">'+badge+'<\/a>') : badge;\n  }\n\n  function renderTable(rows){\n    const th = {date:L.th_date,dep:L.th_dep,arr:L.th_arr,dur:L.th_dur,ship:L.th_ship,op:L.th_op};\n\n    const headCells = [\n      '<th>'+th.date+'<\/th>',\n      '<th>'+th.dep+'<\/th>',\n      '<th>'+th.arr+'<\/th>',\n      '<th>'+th.dur+'<\/th>'\n    ];\n    if (showShip) {\n      headCells.push('<th>'+th.ship+'<\/th>');\n    }\n    headCells.push('<th>'+th.op+'<\/th>');\n\n    let html = '<table class=\"tp-table\"><thead><tr>'+headCells.join('')+'<\/tr><\/thead><tbody>';\n\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const cells = [\n        '<td>'+dShort(r.dep)+' <span class=\"tp-day\">'+dow+'<\/span><\/td>',\n        '<td>'+hhmm(r.dep)+'<\/td>',\n        '<td>'+hhmm(r.arr)+'<\/td>',\n        '<td>'+dur(r.min)+'<\/td>'\n      ];\n      if (showShip) {\n        cells.push('<td>'+shipBadge(r.ship)+'<\/td>');\n      }\n      cells.push('<td>'+opCell(r)+'<\/td>');\n      html += '<tr>'+cells.join('')+'<\/tr>';\n    });\n    html += '<\/tbody><\/table>';\n    return html;\n  }\n\n  function renderCards(rows){\n    const routeName = cfg.label || '';\n    let html = '<div class=\"tp-cardlist\">';\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const times = hhmm(r.dep) + ' <span class=\"tp-arrow\">\u2192<\/span> ' + hhmm(r.arr);\n      html += '<div class=\"tp-card\">'+\n        \/\/ Row 1: Date + weekday + times\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\"><span class=\"tp-sub\">'+dShort(r.dep)+'<\/span><span class=\"tp-day\">'+dow+'<\/span><\/div>'+\n          '<div class=\"rhs\"><span class=\"tp-time\">'+times+'<\/span><\/div>'+\n        '<\/div>';\n\n      if (showShip || routeName) {\n        html +=\n        \/\/ Row 2: Ship & Route (no prefixes)\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+(showShip ? shipBadge(r.ship) : '')+'<\/div>'+\n          (routeName ? ('<div class=\"rhs\">'+routeName+'<\/div>') : '<div class=\"rhs\"><\/div>')+\n        '<\/div>';\n      }\n\n      html +=\n        \/\/ Row 3: Operator (no book button)\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+opCell(r)+'<\/div>'+\n          '<div class=\"rhs\"><\/div>'+\n        '<\/div>'+\n      '<\/div>';\n    });\n    html += '<\/div>';\n    return html;\n  }\n\n  async function load(){\n    btn.disabled = true; setLoader(true); status.textContent = L.status_loading;\n    const start = new Date(dateEl.value || new Date());\n    const days  = Math.max(1, parseInt(daysEl.value,10)||1);\n    const end   = new Date(start); end.setDate(start.getDate()+days-1);\n    const url = new URL(cfg.rest); url.searchParams.set('route', String(cfg.route));\n    url.searchParams.set('from', iso(start)); url.searchParams.set('to', iso(end));\n    try{\n      const res = await fetch(url.toString(), {credentials:'same-origin'});\n      if(!res.ok) throw new Error('HTTP '+res.status);\n      const json = await res.json();\n      const list = (json && json.data && Array.isArray(json.data.rows)) ? json.data.rows : [];\n      const rows = list.map(t=>({\n        opId:t.operatorId||null, opName:t.operatorName||'', opLogo:t.operatorLogo||'', opLink:t.operatorLink||'',\n        dep:toLocal(t.departureTime), arr:toLocal(t.arrivalTime), min:t.durationInMinutes||0, ship:t.shipName||''\n      })).sort((a,b)=>a.dep-b.dep);\n      if(rows.length===0){ status.textContent = L.status_empty; out.innerHTML=''; return; }\n      status.textContent = (cfg.label? (cfg.label+': '):'') + (L.status_found||'%d sailings found').replace('%d', rows.length);\n      out.innerHTML = renderTable(rows) + renderCards(rows);\n    }catch(e){\n      status.textContent = 'Failed to load data'; out.innerHTML = '<pre>'+String(e.message||e)+'<\/pre>';\n    }finally{ setLoader(false); btn.disabled=false; }\n  }\n\n  btn.addEventListener('click', load);\n  if (cfg.autoload) load();\n})();\n<\/script>\n<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-24399ba elementor-widget elementor-widget-text-editor\" data-id=\"24399ba\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>\u00dclaltoodud interaktiivne s\u00f5iduplaan n\u00e4itab Helsingi\u2013Tallinna suunalisi v\u00e4ljumisi reaalajas. V\u00e4ljumisajad uuendatakse automaatselt vastavalt laevafirmade viimasele graafikule. Iga v\u00e4ljumise juures on n\u00e4ha planeeritud v\u00e4ljumisaeg, saabumisaeg ja opereeriv ettev\u00f5te. Arvesta, et v\u00e4ljumisajad v\u00f5ivad muutuda Soome lahe ilmastikuolude v\u00f5i operatiivsete muudatuste t\u00f5ttu. <strong>Kontrolli vahetult enne v\u00e4ljumist l\u00f5plikku aega alati vedaja juurest.<\/strong><\/p><h2>Tallinn Helsingi s\u00f5iduplaan<\/h2>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6356b18 elementor-widget elementor-widget-shortcode\" data-id=\"6356b18\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"shortcode.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-shortcode\"><div class=\"tp-wrap\" data-config=\"{&quot;rest&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/ee\\\/wp-json\\\/timetables-pro\\\/v1\\\/timetables&quot;,&quot;route&quot;:162,&quot;days&quot;:3,&quot;autoload&quot;:true,&quot;label&quot;:&quot;Tallinna - Helsingi&quot;,&quot;labels&quot;:{&quot;ui_date&quot;:&quot;Kuup&quot;,&quot;ui_days&quot;:&quot;P\\u00e4evad&quot;,&quot;ui_button&quot;:&quot;N\\u00e4ita v\\u00e4ljum&quot;,&quot;status_idle&quot;:&quot;Vali kp ja N\\u00e4ita&quot;,&quot;status_loading&quot;:&quot;Laeb...&quot;,&quot;status_empty&quot;:&quot;No sailings found&quot;,&quot;status_found&quot;:&quot;Leiti %d v\\u00e4ljum&quot;,&quot;th_date&quot;:&quot;Kuup&quot;,&quot;th_dep&quot;:&quot;V\\u00e4l&quot;,&quot;th_arr&quot;:&quot;Saab&quot;,&quot;th_dur&quot;:&quot;Kest&quot;,&quot;th_ship&quot;:&quot;Laev&quot;,&quot;th_op&quot;:&quot;Oper&quot;,&quot;th_route&quot;:&quot;Liin&quot;,&quot;wd_sun&quot;:&quot;P&quot;,&quot;wd_mon&quot;:&quot;E&quot;,&quot;wd_tue&quot;:&quot;T&quot;,&quot;wd_wed&quot;:&quot;K&quot;,&quot;wd_thu&quot;:&quot;N&quot;,&quot;wd_fri&quot;:&quot;R&quot;,&quot;wd_sat&quot;:&quot;L&quot;,&quot;summary_footer_one&quot;:&quot;\\u00bb Check all %s departures on our timetable&quot;,&quot;summary_footer_two&quot;:&quot;\\u00bb Check more %1$s and %2$s departures on our timetable&quot;,&quot;summary_footer_generic&quot;:&quot;Check our timetable for more sailings&quot;,&quot;summary_header&quot;:&quot;The next two departures:&quot;,&quot;summary_no_upcoming&quot;:&quot;No upcoming departures found&quot;,&quot;more_link&quot;:&quot;More sailings&quot;},&quot;showLogo&quot;:true,&quot;shipMap&quot;:{&quot;fi&quot;:&quot;Finlandia&quot;},&quot;shipLinks&quot;:{&quot;fi&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/finlandia\\\/&quot;,&quot;viking xprs&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/viking-xprs\\\/&quot;,&quot;mystar&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/mystar\\\/&quot;,&quot;victoria i&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/victoria\\\/&quot;,&quot;megastar&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/megastar\\\/&quot;},&quot;showShip&quot;:true}\">\n  <div class=\"tp-controls\" role=\"group\" aria-label=\"Timetable controls\">\n    <div>\n      <label for=\"tp-date\">Kuup<\/label><br>\n      <input id=\"tp-date\" class=\"tp-date\" type=\"date\" aria-label=\"Kuup\">\n    <\/div>\n    <div>\n      <label for=\"tp-range\">P\u00e4evad<\/label><br>\n      <select id=\"tp-range\" class=\"tp-range\" aria-label=\"P\u00e4evad\">\n        <option value=\"1\">1<\/option><option value=\"3\">3<\/option><option value=\"7\">7<\/option><option value=\"14\">14<\/option>\n      <\/select>\n    <\/div>\n    <div><button id=\"tp-load\" class=\"tp-btn\">N\u00e4ita v\u00e4ljum<\/button><\/div>\n  <\/div>\n\n  <div id=\"tp-status\" class=\"tp-muted\" aria-live=\"polite\">Vali kp ja N\u00e4ita<\/div>\n  <div id=\"tp-results\"><\/div>\n\n  <div class=\"tp-loader\" aria-hidden=\"true\">\n    <div class=\"tp-loader-card\">\n      <div class=\"tp-spinner\" aria-hidden=\"true\"><\/div>\n      <div class=\"tp-loader-text\">Laeb...<\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  const wrap   = document.currentScript.previousElementSibling;\n  const cfg    = JSON.parse(wrap.getAttribute('data-config')||'{}');\n  const L      = cfg.labels||{};\n  const dateEl = wrap.querySelector('#tp-date');\n  const daysEl = wrap.querySelector('#tp-range');\n  const btn    = wrap.querySelector('#tp-load');\n  const status = wrap.querySelector('#tp-status');\n  const out    = wrap.querySelector('#tp-results');\n  const loader = wrap.querySelector('.tp-loader');\n\n  const showShip = (cfg.showShip !== false && cfg.showShip !== 0 && cfg.showShip !== '0');\n\n  dateEl.valueAsDate = new Date();\n  Array.from(daysEl.options).forEach(o=>{ if(parseInt(o.value,10)===parseInt(cfg.days||7,10)) o.selected=true; });\n\n  function pad(n){ return String(n).padStart(2,'0'); }\n  function iso(d){ return d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate()); }\n  function toLocal(s){ return new Date(s); }\n  function hhmm(d){ return d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); }\n  function dShort(d){ return d.toLocaleDateString([], {day:'numeric', month:'short'}); } \/\/ short, no year\n  function dur(m){ const h=Math.floor(m\/60), r=m%60; return r? (h+'h '+r+'m') : (h+'h'); }\n\n  const WD = [\n    L.wd_sun || 'SUN',\n    L.wd_mon || 'MON',\n    L.wd_tue || 'TUE',\n    L.wd_wed || 'WED',\n    L.wd_thu || 'THU',\n    L.wd_fri || 'FRI',\n    L.wd_sat || 'SAT'\n  ];\n\n  function setLoader(v){ loader.classList.toggle('show', !!v); loader.setAttribute('aria-hidden', v?'false':'true'); }\n\n  function opCell(r){\n    const name = r.opName || ('Operator '+(r.opId||''));\n    const logo = (cfg.showLogo && r.opLogo) ? '<img decoding=\"async\" src=\"'+r.opLogo+'\" alt=\"'+name+'\"> ' : '';\n    const label = logo + '<span>'+name+'<\/span>';\n    return r.opLink ? '<a class=\"tp-op\" href=\"'+r.opLink+'\" target=\"_blank\" rel=\"nofollow noopener\">'+label+'<\/a>' : '<span class=\"tp-op\">'+label+'<\/span>';\n  }\n\n  function shipInfo(original){\n    const key = (original||'').toLowerCase().trim();\n    const label = (cfg.shipMap && cfg.shipMap[key]) || original || '';\n    const href  = (cfg.shipLinks && cfg.shipLinks[key]) || '';\n    return {label, href};\n  }\n\n  function shipBadge(original){\n    const s = shipInfo(original);\n    const badge = '<span class=\"tp-badge\">'+(s.label||'')+'<\/span>';\n    return s.href ? ('<a href=\"'+s.href+'\" target=\"_blank\" rel=\"nofollow noopener\">'+badge+'<\/a>') : badge;\n  }\n\n  function renderTable(rows){\n    const th = {date:L.th_date,dep:L.th_dep,arr:L.th_arr,dur:L.th_dur,ship:L.th_ship,op:L.th_op};\n\n    const headCells = [\n      '<th>'+th.date+'<\/th>',\n      '<th>'+th.dep+'<\/th>',\n      '<th>'+th.arr+'<\/th>',\n      '<th>'+th.dur+'<\/th>'\n    ];\n    if (showShip) {\n      headCells.push('<th>'+th.ship+'<\/th>');\n    }\n    headCells.push('<th>'+th.op+'<\/th>');\n\n    let html = '<table class=\"tp-table\"><thead><tr>'+headCells.join('')+'<\/tr><\/thead><tbody>';\n\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const cells = [\n        '<td>'+dShort(r.dep)+' <span class=\"tp-day\">'+dow+'<\/span><\/td>',\n        '<td>'+hhmm(r.dep)+'<\/td>',\n        '<td>'+hhmm(r.arr)+'<\/td>',\n        '<td>'+dur(r.min)+'<\/td>'\n      ];\n      if (showShip) {\n        cells.push('<td>'+shipBadge(r.ship)+'<\/td>');\n      }\n      cells.push('<td>'+opCell(r)+'<\/td>');\n      html += '<tr>'+cells.join('')+'<\/tr>';\n    });\n    html += '<\/tbody><\/table>';\n    return html;\n  }\n\n  function renderCards(rows){\n    const routeName = cfg.label || '';\n    let html = '<div class=\"tp-cardlist\">';\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const times = hhmm(r.dep) + ' <span class=\"tp-arrow\">\u2192<\/span> ' + hhmm(r.arr);\n      html += '<div class=\"tp-card\">'+\n        \/\/ Row 1: Date + weekday + times\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\"><span class=\"tp-sub\">'+dShort(r.dep)+'<\/span><span class=\"tp-day\">'+dow+'<\/span><\/div>'+\n          '<div class=\"rhs\"><span class=\"tp-time\">'+times+'<\/span><\/div>'+\n        '<\/div>';\n\n      if (showShip || routeName) {\n        html +=\n        \/\/ Row 2: Ship & Route (no prefixes)\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+(showShip ? shipBadge(r.ship) : '')+'<\/div>'+\n          (routeName ? ('<div class=\"rhs\">'+routeName+'<\/div>') : '<div class=\"rhs\"><\/div>')+\n        '<\/div>';\n      }\n\n      html +=\n        \/\/ Row 3: Operator (no book button)\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+opCell(r)+'<\/div>'+\n          '<div class=\"rhs\"><\/div>'+\n        '<\/div>'+\n      '<\/div>';\n    });\n    html += '<\/div>';\n    return html;\n  }\n\n  async function load(){\n    btn.disabled = true; setLoader(true); status.textContent = L.status_loading;\n    const start = new Date(dateEl.value || new Date());\n    const days  = Math.max(1, parseInt(daysEl.value,10)||1);\n    const end   = new Date(start); end.setDate(start.getDate()+days-1);\n    const url = new URL(cfg.rest); url.searchParams.set('route', String(cfg.route));\n    url.searchParams.set('from', iso(start)); url.searchParams.set('to', iso(end));\n    try{\n      const res = await fetch(url.toString(), {credentials:'same-origin'});\n      if(!res.ok) throw new Error('HTTP '+res.status);\n      const json = await res.json();\n      const list = (json && json.data && Array.isArray(json.data.rows)) ? json.data.rows : [];\n      const rows = list.map(t=>({\n        opId:t.operatorId||null, opName:t.operatorName||'', opLogo:t.operatorLogo||'', opLink:t.operatorLink||'',\n        dep:toLocal(t.departureTime), arr:toLocal(t.arrivalTime), min:t.durationInMinutes||0, ship:t.shipName||''\n      })).sort((a,b)=>a.dep-b.dep);\n      if(rows.length===0){ status.textContent = L.status_empty; out.innerHTML=''; return; }\n      status.textContent = (cfg.label? (cfg.label+': '):'') + (L.status_found||'%d sailings found').replace('%d', rows.length);\n      out.innerHTML = renderTable(rows) + renderCards(rows);\n    }catch(e){\n      status.textContent = 'Failed to load data'; out.innerHTML = '<pre>'+String(e.message||e)+'<\/pre>';\n    }finally{ setLoader(false); btn.disabled=false; }\n  }\n\n  btn.addEventListener('click', load);\n  if (cfg.autoload) load();\n})();\n<\/script>\n<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-087c770 elementor-widget elementor-widget-text-editor\" data-id=\"087c770\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p><strong>S\u00f5iduplaanid \u00fclal n\u00e4itavad Tallinna ja Helsingi vahelisi v\u00e4ljumisi m\u00f5lemas suunas.<\/strong> Kuna tegemist on \u00fche k\u00f5ige tihedama rahvusvahelise liiniga P\u00f5hja Euroopas, v\u00f5ivad v\u00e4ljumisajad p\u00e4eva jooksul muutuda. Enne v\u00e4ljumist kontrolli alati vedaja juures l\u00f5plikke v\u00e4ljumisaegu, registreerimise infot ja v\u00f5imalikke muudatusi.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\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>Helsingi ja Tallinna vahelise \u00fcleveo t\u00e4ielik reaalajas s\u00f5iduplaan k\u00f5igi kolme operaatori kohta: Viking Line, Tallink Silja ja Ecker\u00f6 Line. Vaikimisi [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":590,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"inline_featured_image":false,"site-sidebar-layout":"no-sidebar","site-content-layout":"","ast-site-content-layout":"full-width-container","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"class_list":["post-586","page","type-page","status-publish","has-post-thumbnail","hentry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Tallinn Helsingi laeva s\u00f5iduplaan - V\u00e4ljumised ja saabumised<\/title>\n<meta name=\"description\" content=\"Vaata Tallinna ja Helsingi vahelise laeva s\u00f5iduplaani. Aktuaalsed v\u00e4ljumis ja saabumisajad m\u00f5lemas suunas, uuendatud reaalajas.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/\" \/>\n<meta property=\"og:locale\" content=\"et_EE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Tallinn Helsingi laeva s\u00f5iduplaan - V\u00e4ljumised ja saabumised\" \/>\n<meta property=\"og:description\" content=\"Vaata Tallinna ja Helsingi vahelise laeva s\u00f5iduplaani. Aktuaalsed v\u00e4ljumis ja saabumisajad m\u00f5lemas suunas, uuendatud reaalajas.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/\" \/>\n<meta property=\"og:site_name\" content=\"helsinki-tallinn.com Esti\" \/>\n<meta property=\"article:modified_time\" content=\"2026-02-23T13:36:04+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/near-helsinki.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"500\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"2 minutit\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/\",\"url\":\"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/\",\"name\":\"Tallinn Helsingi laeva s\u00f5iduplaan - V\u00e4ljumised ja saabumised\",\"isPartOf\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/near-helsinki.jpg\",\"datePublished\":\"2026-02-23T13:24:50+00:00\",\"dateModified\":\"2026-02-23T13:36:04+00:00\",\"description\":\"Vaata Tallinna ja Helsingi vahelise laeva s\u00f5iduplaani. Aktuaalsed v\u00e4ljumis ja saabumisajad m\u00f5lemas suunas, uuendatud reaalajas.\",\"breadcrumb\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/#breadcrumb\"},\"inLanguage\":\"et\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"et\",\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/#primaryimage\",\"url\":\"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/near-helsinki.jpg\",\"contentUrl\":\"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/near-helsinki.jpg\",\"width\":1920,\"height\":500,\"caption\":\"helsinki\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Helsingi Tallinna\",\"item\":\"https:\/\/helsinki-tallinn.com\/ee\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Reaalajas s\u00f5iduplaan\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/#website\",\"url\":\"https:\/\/helsinki-tallinn.com\/ee\/\",\"name\":\"helsinki-tallinn.com Esti\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/helsinki-tallinn.com\/ee\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"et\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/#organization\",\"name\":\"helsinki-tallinn.com Esti\",\"url\":\"https:\/\/helsinki-tallinn.com\/ee\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"et\",\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/Helsinki-Talinn-powered.png\",\"contentUrl\":\"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/Helsinki-Talinn-powered.png\",\"width\":700,\"height\":200,\"caption\":\"helsinki-tallinn.com Esti\"},\"image\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/ee\/#\/schema\/logo\/image\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Tallinn Helsingi laeva s\u00f5iduplaan - V\u00e4ljumised ja saabumised","description":"Vaata Tallinna ja Helsingi vahelise laeva s\u00f5iduplaani. Aktuaalsed v\u00e4ljumis ja saabumisajad m\u00f5lemas suunas, uuendatud reaalajas.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/","og_locale":"et_EE","og_type":"article","og_title":"Tallinn Helsingi laeva s\u00f5iduplaan - V\u00e4ljumised ja saabumised","og_description":"Vaata Tallinna ja Helsingi vahelise laeva s\u00f5iduplaani. Aktuaalsed v\u00e4ljumis ja saabumisajad m\u00f5lemas suunas, uuendatud reaalajas.","og_url":"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/","og_site_name":"helsinki-tallinn.com Esti","article_modified_time":"2026-02-23T13:36:04+00:00","og_image":[{"width":1920,"height":500,"url":"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/near-helsinki.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"2 minutit"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/","url":"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/","name":"Tallinn Helsingi laeva s\u00f5iduplaan - V\u00e4ljumised ja saabumised","isPartOf":{"@id":"https:\/\/helsinki-tallinn.com\/ee\/#website"},"primaryImageOfPage":{"@id":"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/#primaryimage"},"image":{"@id":"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/#primaryimage"},"thumbnailUrl":"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/near-helsinki.jpg","datePublished":"2026-02-23T13:24:50+00:00","dateModified":"2026-02-23T13:36:04+00:00","description":"Vaata Tallinna ja Helsingi vahelise laeva s\u00f5iduplaani. Aktuaalsed v\u00e4ljumis ja saabumisajad m\u00f5lemas suunas, uuendatud reaalajas.","breadcrumb":{"@id":"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/#breadcrumb"},"inLanguage":"et","potentialAction":[{"@type":"ReadAction","target":["https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/"]}]},{"@type":"ImageObject","inLanguage":"et","@id":"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/#primaryimage","url":"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/near-helsinki.jpg","contentUrl":"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/near-helsinki.jpg","width":1920,"height":500,"caption":"helsinki"},{"@type":"BreadcrumbList","@id":"https:\/\/helsinki-tallinn.com\/ee\/soiduplaan\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Helsingi Tallinna","item":"https:\/\/helsinki-tallinn.com\/ee\/"},{"@type":"ListItem","position":2,"name":"Reaalajas s\u00f5iduplaan"}]},{"@type":"WebSite","@id":"https:\/\/helsinki-tallinn.com\/ee\/#website","url":"https:\/\/helsinki-tallinn.com\/ee\/","name":"helsinki-tallinn.com Esti","description":"","publisher":{"@id":"https:\/\/helsinki-tallinn.com\/ee\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/helsinki-tallinn.com\/ee\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"et"},{"@type":"Organization","@id":"https:\/\/helsinki-tallinn.com\/ee\/#organization","name":"helsinki-tallinn.com Esti","url":"https:\/\/helsinki-tallinn.com\/ee\/","logo":{"@type":"ImageObject","inLanguage":"et","@id":"https:\/\/helsinki-tallinn.com\/ee\/#\/schema\/logo\/image\/","url":"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/Helsinki-Talinn-powered.png","contentUrl":"https:\/\/helsinki-tallinn.com\/ee\/wp-content\/uploads\/sites\/5\/2026\/02\/Helsinki-Talinn-powered.png","width":700,"height":200,"caption":"helsinki-tallinn.com Esti"},"image":{"@id":"https:\/\/helsinki-tallinn.com\/ee\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/helsinki-tallinn.com\/ee\/wp-json\/wp\/v2\/pages\/586","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/helsinki-tallinn.com\/ee\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/helsinki-tallinn.com\/ee\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/helsinki-tallinn.com\/ee\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/helsinki-tallinn.com\/ee\/wp-json\/wp\/v2\/comments?post=586"}],"version-history":[{"count":5,"href":"https:\/\/helsinki-tallinn.com\/ee\/wp-json\/wp\/v2\/pages\/586\/revisions"}],"predecessor-version":[{"id":594,"href":"https:\/\/helsinki-tallinn.com\/ee\/wp-json\/wp\/v2\/pages\/586\/revisions\/594"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/helsinki-tallinn.com\/ee\/wp-json\/wp\/v2\/media\/590"}],"wp:attachment":[{"href":"https:\/\/helsinki-tallinn.com\/ee\/wp-json\/wp\/v2\/media?parent=586"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}