{"id":267,"date":"2024-02-28T16:00:48","date_gmt":"2024-02-28T16:00:48","guid":{"rendered":"https:\/\/hirtshals-kristiansand.com\/nl\/?page_id=267"},"modified":"2026-05-20T07:57:17","modified_gmt":"2026-05-20T07:57:17","slug":"dienstregeling","status":"publish","type":"page","link":"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/","title":{"rendered":"Vaartijden Hirtshals Kristiansand (live dienstregeling)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Op de route tussen Hirtshals en Kristiansand kun je kiezen uit twee rederijen: <a href=\"https:\/\/hirtshals-kristiansand.com\/nl\/fjord-line\/\" type=\"page\" id=\"262\">Fjord Line<\/a> met meerdere afvaarten per dag en <a href=\"https:\/\/hirtshals-kristiansand.com\/nl\/color-line\/\" type=\"page\" id=\"260\">Color Line<\/a> met meestal \u00e9\u00e9n afvaart per dag.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hirtshals Kristiansand vaartijden<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Vroege afvaarten vertrekken vaak rond 09.30 uur vanuit <a href=\"https:\/\/hirtshals-kristiansand.com\/nl\/hirtshals-haven\/\" type=\"page\" id=\"44\">Hirtshals haven<\/a> met Fjord Line. Later op de dag volgt meestal nog een avondafvaart rond 21.45 uur met Color Line (<a href=\"https:\/\/hirtshals-kristiansand.com\/nl\/color-line-superspeed-1\/\" type=\"page\" id=\"484\">SuperSpeed 1<\/a>).<\/p>\n\n\n<div class=\"tp-wrap tp-theme-extended_cards tp-day-setup-modern\" data-config=\"{&quot;rest&quot;:&quot;https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/wp-json\\\/timetables-pro\\\/v1\\\/timetables&quot;,&quot;routes&quot;:[171],&quot;days&quot;:3,&quot;autoload&quot;:true,&quot;theme&quot;:&quot;extended_cards&quot;,&quot;daySetup&quot;:&quot;modern&quot;,&quot;limit10&quot;:false,&quot;routeLabels&quot;:{&quot;171&quot;:&quot;Hirtshals naar Kristiansand&quot;},&quot;opFilter&quot;:[&quot;8&quot;,&quot;27&quot;],&quot;labels&quot;:{&quot;ui_date&quot;:&quot;Datum&quot;,&quot;ui_days&quot;:&quot;Dagen&quot;,&quot;ui_button&quot;:&quot;Afvaarten tonen&quot;,&quot;status_idle&quot;:&quot;Kies een datum en klik op Afvaarten tonen&quot;,&quot;status_loading&quot;:&quot;Laden...&quot;,&quot;status_empty&quot;:&quot;Geen afvaarten gevonden&quot;,&quot;status_found&quot;:&quot;%d afvaarten gevonden&quot;,&quot;th_date&quot;:&quot;Datum&quot;,&quot;th_dep&quot;:&quot;Vertrek&quot;,&quot;th_arr&quot;:&quot;Aankomst&quot;,&quot;th_dur&quot;:&quot;Duur&quot;,&quot;th_ship&quot;:&quot;Schip&quot;,&quot;th_op&quot;:&quot;Operator&quot;,&quot;th_route&quot;:&quot;Route&quot;,&quot;wd_sun&quot;:&quot;ZO&quot;,&quot;wd_mon&quot;:&quot;MA&quot;,&quot;wd_tue&quot;:&quot;DI&quot;,&quot;wd_wed&quot;:&quot;WO&quot;,&quot;wd_thu&quot;:&quot;DO&quot;,&quot;wd_fri&quot;:&quot;VR&quot;,&quot;wd_sat&quot;:&quot;ZA&quot;,&quot;summary_footer_one&quot;:&quot;\\u00bb Bekijk alle %s afvaarten in onze dienstregeling&quot;,&quot;summary_footer_two&quot;:&quot;\\u00bb Bekijk meer %1$s en %2$s afvaarten in onze dienstregeling&quot;,&quot;summary_footer_generic&quot;:&quot;Bekijk onze dienstregeling voor meer afvaarten&quot;,&quot;summary_header&quot;:&quot;De volgende twee afvaarten:&quot;,&quot;summary_no_upcoming&quot;:&quot;Geen aankomende afvaarten gevonden&quot;,&quot;more_link&quot;:&quot;Meer afvaarten&quot;,&quot;local_time_pair&quot;:&quot;Lokale tijd vertrekhaven: %1$s \\\/ Lokale tijd aankomsthaven: %2$s&quot;,&quot;local_time_combined&quot;:&quot;Lokale tijd vertrek- en aankomsthaven: %s&quot;,&quot;tag_often_booked&quot;:&quot;\\u2b50 Vaak geboekt&quot;,&quot;tag_leaving_soon&quot;:&quot;\\ud83d\\udd34 Vertrekt binnenkort&quot;,&quot;tag_currently_sailing&quot;:&quot;\\ud83d\\udfe2 Nu onderweg&quot;,&quot;tag_arrived&quot;:&quot;\\u26ab Aangekomen&quot;,&quot;tag_overnight&quot;:&quot;Nachtvaart&quot;},&quot;showLogo&quot;:true,&quot;activityBoosters&quot;:true,&quot;title&quot;:&quot;Alle overtochten&quot;,&quot;shipMap&quot;:{&quot;bf&quot;:&quot;MS Bergensfjord&quot;,&quot;ff&quot;:&quot;FSTR (Snelste!\\ud83d\\udd25)&quot;,&quot;nb87&quot;:&quot;MS Stavangerfjord&quot;},&quot;shipLinks&quot;:{&quot;bf&quot;:&quot;https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/ms-bergensfjord\\\/&quot;,&quot;superspeed 1&quot;:&quot;https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/color-line-superspeed-1\\\/&quot;,&quot;ff&quot;:&quot;https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/fjord-fstr\\\/&quot;,&quot;nb87&quot;:&quot;https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/ms-stavangerfjord\\\/&quot;},&quot;showShip&quot;:true,&quot;departurePort&quot;:&quot;Hirtshals&quot;,&quot;arrivalPort&quot;:&quot;Kristiansand&quot;,&quot;departureTimezone&quot;:&quot;Europe\\\/Copenhagen&quot;,&quot;arrivalTimezone&quot;:&quot;Europe\\\/Oslo&quot;}\">\n  <div class=\"tp-controls\" role=\"group\" aria-label=\"Timetable controls\">\n    <div>\n      <label for=\"tp-date-ops\">Datum<\/label><br>\n      <input id=\"tp-date-ops\" class=\"tp-date\" type=\"date\" aria-label=\"Datum\">\n    <\/div>\n    <div class=\"tp-day-field\">\n      <label for=\"tp-range-ops\">Dagen<\/label><br>\n      <select id=\"tp-range-ops\" class=\"tp-range\" aria-label=\"Dagen\">\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-ops\" class=\"tp-btn\">Afvaarten tonen<\/button><\/div>\n  <\/div>\n\n  <div class=\"tp-local-nav-row\">\n    <div class=\"tp-local-times\" id=\"tp-local-times-ops\" aria-live=\"polite\"><\/div>\n    <div class=\"tp-day-nav-host\"><\/div>\n  <\/div>\n\n      <h3 style=\"margin:8px 0 12px;color:#0f172a;\">Alle overtochten<\/h3>\n  \n  <div id=\"tp-status-ops\" class=\"tp-muted\" aria-live=\"polite\">Kies een datum en klik op Afvaarten tonen<\/div>\n\n  <div id=\"tp-ops-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\">Laden...<\/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-ops');\n  const daysEl = wrap.querySelector('#tp-range-ops');\n  const btn    = wrap.querySelector('#tp-load-ops');\n  const status = wrap.querySelector('#tp-status-ops');\n  const out    = wrap.querySelector('#tp-ops-results');\n  const loader = wrap.querySelector('.tp-loader');\n  const navHost = wrap.querySelector('.tp-day-nav-host');\n  const localTimesEl = wrap.querySelector('#tp-local-times-ops');\n\n  const showShip = (cfg.showShip !== false && cfg.showShip !== 0 && cfg.showShip !== '0');\n  const activityBoosters = !!cfg.activityBoosters;\n  const daySetup = (cfg.daySetup === 'modern') ? 'modern' : 'classic';\n  const limit10 = !!cfg.limit10;\n  let featuredKey = '';\n  let soonKey = '';\n  let revealAll = false;\n\n  if (daySetup === 'modern') {\n    const dayField = wrap.querySelector('.tp-day-field');\n    if (dayField) dayField.style.display = 'none';\n  }\n\n  dateEl.valueAsDate = new Date();\n  dateEl.min = new Date().toISOString().split('T')[0];\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'}); }\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 formatZoneTime(tz){\n    if (!tz) return '--:--';\n    try {\n      return new Intl.DateTimeFormat([], {hour:'2-digit', minute:'2-digit', hour12:false, timeZone: tz}).format(new Date());\n    } catch (e) {\n      return '--:--';\n    }\n  }\n\n  function renderLocalTimes(){\n    if (!localTimesEl) return;\n    if (!cfg.departureTimezone || !cfg.arrivalTimezone) {\n      localTimesEl.textContent = '';\n      return;\n    }\n    const depTime = formatZoneTime(cfg.departureTimezone);\n    const arrTime = formatZoneTime(cfg.arrivalTimezone);\n    const pairTpl = L.local_time_pair || 'Departure Port Local Time: %1$s \/ Arrival Port Local Time: %2$s';\n    const combinedTpl = L.local_time_combined || 'Departure & Arrival Port Local Time: %s';\n    if (depTime === arrTime) {\n      localTimesEl.textContent = combinedTpl.replace('%s', depTime);\n      return;\n    }\n    localTimesEl.textContent = pairTpl.replace('%1$s', depTime).replace('%2$s', arrTime);\n  }\n\n  function setStatusInline(html){\n    let box = wrap.querySelector('.tp-controls-status');\n    if (!box) {\n      box = document.createElement('div');\n      box.className = 'tp-controls-status';\n      wrap.querySelector('.tp-controls').appendChild(box);\n    }\n    box.innerHTML = html || '';\n  }\n\n  function clearStatusInline(){\n    const box = wrap.querySelector('.tp-controls-status');\n    if (box) box.innerHTML = '';\n  }\n\n  function navHtml(){\n    if (daySetup !== 'modern') return '';\n    const base = new Date(dateEl.value || new Date());\n    const today = new Date();\n    const tomorrow = new Date(today);\n    tomorrow.setDate(today.getDate() + 1);\n    const isToday = base.toDateString() === today.toDateString();\n    let prevLabel = 'Show previous day';\n    let nextLabel = 'Show next day';\n    if (isToday) nextLabel = 'Show tomorrow';\n    if (base.toDateString() === tomorrow.toDateString()) prevLabel = 'Show today';\n    return '<div class=\"tp-day-nav\">'\n      + (isToday ? '' : '<a href=\"#\" data-shift=\"-1\">'+prevLabel+'<\/a>')\n      + '<a href=\"#\" data-shift=\"1\">'+nextLabel+'<\/a>'\n      + '<\/div>';\n  }\n\n  function shiftDate(delta){\n    const base = new Date(dateEl.value || new Date());\n    base.setDate(base.getDate() + delta);\n    dateEl.value = iso(base);\n    load();\n  }\n\n  function moreButtonHtml(hiddenCount){\n    if (!limit10 || revealAll || hiddenCount <= 0) return '';\n    return '<div style=\"margin-top:12px;text-align:center\">'\n      + '<button type=\"button\" class=\"tp-btn tp-more-btn\">Show more departures ('+hiddenCount+')<\/button>'\n      + '<\/div>';\n  }\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 rowKey(r){\n    return [String(r.routeId||''), String(r.opId||''), String(+r.dep||0), String(+r.arr||0)].join('|');\n  }\n\n  function pickFeaturedKey(rows, avoidKey){\n    if (!activityBoosters || !rows.length) return '';\n    const routesSum = Array.isArray(cfg.routes) ? cfg.routes.reduce((acc, n)=>acc + (parseInt(n,10)||0), 0) : 0;\n    const dateSeed = parseInt(String(dateEl.value || '').replace(\/-\/g,''), 10) || 0;\n\n    let pool = rows;\n    if (avoidKey) {\n      const soonIdx = rows.findIndex(r => rowKey(r) === avoidKey);\n      if (soonIdx >= 0) {\n        const later = rows.slice(soonIdx + 1).filter(r => rowKey(r) !== avoidKey);\n        pool = later.length ? later : rows.filter(r => rowKey(r) !== avoidKey);\n      } else {\n        pool = rows.filter(r => rowKey(r) !== avoidKey);\n      }\n    }\n\n    if (!pool.length) return '';\n    const idx = Math.abs((routesSum + dateSeed + rows.length) % pool.length);\n    return rowKey(pool[idx]);\n  }\n\n  function pickSoonKey(rows){\n    if (!activityBoosters || !rows.length) return '';\n    const now = Date.now();\n    const todayStr = new Date(now).toDateString();\n    const firstUpcoming = rows.find(r => (r.dep instanceof Date) && r.dep.getTime() > now && r.dep.toDateString() === todayStr);\n    return firstUpcoming ? rowKey(firstUpcoming) : '';\n  }\n\n  function boostersFor(r){\n    if (!activityBoosters) return [];\n    const now = Date.now();\n    const depMs = (r.dep instanceof Date) ? r.dep.getTime() : 0;\n    const arrMs = (r.arr instanceof Date) ? r.arr.getTime() : 0;\n    const depIsToday = (r.dep instanceof Date) && (new Date(now).toDateString() === r.dep.toDateString());\n\n    if (depMs > 0 && arrMs > 0 && depMs <= now && arrMs > now) {\n      return [{cls:'tp-booster-sailing', text:(L.tag_currently_sailing || '\ud83d\udfe2 Currently sailing')}];\n    }\n    if (depIsToday && arrMs > 0 && arrMs <= now) {\n      return [{cls:'tp-booster-arrived', text:(L.tag_arrived || '\u26ab Arrived')}];\n    }\n    if (rowKey(r) === soonKey) return [{cls:'tp-booster-soon', text:(L.tag_leaving_soon || '\ud83d\udd34 Leaving soon')}];\n    if (r.dep && r.arr && r.dep.toDateString() !== r.arr.toDateString()) return [{cls:'tp-booster-overnight', text:(L.tag_overnight || 'Overnight')}];\n\n    return [];\n  }\n\n  function boostersHtml(r, floating){\n    const tags = boostersFor(r);\n    if (!tags.length) return '';\n    const cls = floating ? 'tp-boosters tp-boosters-float' : 'tp-boosters';\n    return '<div class=\"'+cls+'\">' + tags.map(t => '<span class=\"tp-booster '+t.cls+'\">'+boosterLabelHtml(t.text)+'<\/span>').join('') + '<\/div>';\n  }\n\n  function boosterLabelHtml(text){\n    return String(text || '').replace(\/^(\ud83d\udd34|\ud83d\udfe2|\u26ab|\u2b50)\\s*\/, '<span class=\"tp-booster-emoji\">$1<\/span>');\n  }\n\n  function routeTicketHtml(label){\n    const raw = String(label || '').trim();\n    if (!raw) return '';\n    if (cfg.theme !== 'small_cards') return escAttr(raw);\n    const parts = raw.split(\/\\s*[\\-\u2013]\\s*\/, 2);\n    if (parts.length < 2) return escAttr(raw);\n    return escAttr(parts[0]) + '<br>' + escAttr(parts[1]);\n  }\n\n  function escAttr(v){\n    return String(v||'').replace(\/[&<>\"']\/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[m]));\n  }\n\n  function bindRowLinks(){\n    if (cfg.theme !== 'max_clickouts') return;\n    out.querySelectorAll('tr.tp-row-link').forEach((row)=>{\n      row.setAttribute('role','link');\n      row.setAttribute('tabindex','0');\n      row.addEventListener('click', (e)=>{\n        if (e.target && e.target.closest('a')) return;\n        const href = row.getAttribute('data-row-link');\n        if (!href) return;\n        window.open(href, '_blank', 'noopener,noreferrer');\n      });\n      row.addEventListener('keydown', (e)=>{\n        if (e.key !== 'Enter' && e.key !== ' ') return;\n        e.preventDefault();\n        row.click();\n      });\n    });\n  }\n\n  function routeLabel(rid){\n    return (cfg.routeLabels && (cfg.routeLabels[String(rid)] || cfg.routeLabels[rid])) || String(rid);\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,route:L.th_route,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.route+'<\/th>');\n    headCells.push('<th>'+th.op+'<\/th>');\n\n    let html = '<table class=\"tp-table\"><thead><tr>'+headCells.join('')+'<\/tr><\/thead><tbody>';\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const clickable = (cfg.theme === 'max_clickouts' && r.opLink);\n      const rowAttr = clickable ? (' class=\"tp-row-link\" data-row-link=\"'+escAttr(r.opLink)+'\"') : '';\n      const dateBoosters = (cfg.theme === 'max_clickouts') ? '' : boostersHtml(r, false);\n      const routeBoosters = (cfg.theme === 'max_clickouts') ? boostersHtml(r, false) : '';\n      const cells = [\n        '<td>'+dShort(r.dep)+' <span class=\"tp-day\">'+dow+'<\/span>'+dateBoosters+'<\/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>'+routeBoosters+'<div>'+routeLabel(r.routeId)+'<\/div><\/td>');\n      cells.push('<td>'+opCell(r)+'<\/td>');\n      html += '<tr'+rowAttr+'>'+cells.join('')+'<\/tr>';\n    });\n    html += '<\/tbody><\/table>';\n    return html;\n  }\n\n  function renderCards(rows){\n    if (cfg.theme === 'extended_cards') {\n      return renderExtendedCards(rows);\n    }\n\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\">'+boostersHtml(r, true)+\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) {\n        html +=\n        \/\/ Row 2: Ship\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+shipBadge(r.ship)+'<\/div>'+\n          '<div class=\"rhs\"><\/div>'+\n        '<\/div>';\n      }\n\n      html +=\n        \/\/ Row 3: Operator + route label\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+opCell(r)+'<\/div>'+\n          '<div class=\"rhs\"><span class=\"tp-route-ticket\">'+routeTicketHtml(routeLabel(r.routeId))+'<\/span><\/div>'+\n        '<\/div>'+\n      '<\/div>';\n    });\n    html += '<\/div>';\n    return html;\n  }\n\n  function renderExtendedCards(rows){\n    const ctaText = L.more_link || 'More sailings';\n    let html = '<div class=\"tp-cardlist\">';\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const link = r.opLink ? ('<a class=\"tp-ext-cta\" href=\"'+r.opLink+'\" target=\"_blank\" rel=\"nofollow noopener\">'+ctaText+' <span class=\"tp-ext-cta-arrow\">\u00bb<\/span><\/a>') : '<span class=\"tp-ext-cta\">'+ctaText+' <span class=\"tp-ext-cta-arrow\">\u00bb<\/span><\/span>';\n      html += '<div class=\"tp-ext-card\">'\n        + boostersHtml(r, true)\n        + '<div class=\"tp-ext-left\">'\n        +   '<div>'+opCell(r)+'<\/div>'\n        +   (showShip ? ('<div>'+shipBadge(r.ship)+'<\/div>') : '')\n        + '<\/div>'\n        + '<div class=\"tp-ext-mid\">'\n        +   '<div class=\"tp-ext-top\"><span class=\"tp-sub\">'+dShort(r.dep)+' <span class=\"tp-day\">'+dow+'<\/span><\/span><span class=\"tp-ext-dur\">'+dur(r.min)+'<\/span><\/div>'\n        +   '<div class=\"tp-ext-times\"><span class=\"tp-ext-time\">'+hhmm(r.dep)+'<\/span><span class=\"tp-ext-line\"><\/span><span class=\"tp-ext-dur\">'+dur(r.min)+'<\/span><span class=\"tp-ext-line\"><\/span><span class=\"tp-ext-time\">'+hhmm(r.arr)+'<\/span><\/div>'\n        +   '<div class=\"tp-ext-route\">'+routeLabel(r.routeId)+'<\/div>'\n        + '<\/div>'\n        + '<div class=\"tp-ext-right\">'+link+'<\/div>'\n        + '<\/div>';\n    });\n    html += '<\/div>';\n    return html;\n  }\n\n  \/\/ Safe fetch helper\n  async function fetchOne(routeId, from, to){\n    try{\n      const url = new URL(cfg.rest);\n      url.searchParams.set('route', String(routeId));\n      url.searchParams.set('from', from);\n      url.searchParams.set('to', to);\n      const res = await fetch(url.toString(), {credentials:'same-origin'});\n      if(!res.ok) return { rid: routeId, 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      return { rid: routeId, rows: list };\n    }catch(e){\n      return { rid: routeId, error: String(e && e.message ? e.message : e) };\n    }\n  }\n\n  async function load(){\n    btn.disabled = true; setLoader(true); status.textContent = L.status_loading;\n\n    const start = new Date(dateEl.value || new Date());\n    const days  = (daySetup === 'modern') ? 1 : Math.max(1, parseInt(daysEl.value,10)||1);\n    const end   = new Date(start); end.setDate(start.getDate()+days-1);\n    const fromIso = iso(start), toIso = iso(end);\n\n    try{\n      const results  = await Promise.all((cfg.routes||[]).map(rid=>fetchOne(rid, fromIso, toIso)));\n      const oks  = results.filter(r => !r.error);\n      const errs = results.filter(r =>  r.error);\n\n      const all = [];\n      oks.forEach(({rid, rows})=>{\n        rows.forEach(t=>{\n          all.push({\n            routeId: rid,\n            opId: t.operatorId || null,\n            opName: t.operatorName || '',\n            opLogo: t.operatorLogo || '',\n            opLink: t.operatorLink || '',\n            dep: toLocal(t.departureTime),\n            arr: toLocal(t.arrivalTime),\n            min: t.durationInMinutes || 0,\n            ship: t.shipName || ''\n          });\n        });\n      });\n\n      let rows = all;\n      if (Array.isArray(cfg.opFilter) && cfg.opFilter.length>0) {\n        rows = all.filter(r => r.opId && cfg.opFilter.includes(String(r.opId)));\n      }\n\n      rows.sort((a,b)=> a.dep - b.dep);\n\n      const todayStr = new Date().toDateString();\n      const nowMs = Date.now();\n      const arrivedToday = rows\n        .filter(r => (r.dep instanceof Date) && (r.arr instanceof Date) && r.dep.toDateString() === todayStr && r.arr.getTime() <= nowMs)\n        .sort((a,b) => b.arr - a.arr);\n      if (arrivedToday.length > 2) {\n        const keep = new Set(arrivedToday.slice(0,2).map(rowKey));\n        rows = rows.filter(r => {\n          const isArrivedToday = (r.dep instanceof Date) && (r.arr instanceof Date) && r.dep.toDateString() === todayStr && r.arr.getTime() <= nowMs;\n          return !isArrivedToday || keep.has(rowKey(r));\n        });\n      }\n\n      const total = rows.length;\n      if (total===0){\n        status.textContent = L.status_empty + (errs.length ? ' (some routes returned no data or errors)' : '');\n        clearStatusInline();\n        out.innerHTML='';\n        setLoader(false); btn.disabled=false; return;\n      }\n\n      const visibleRows = (limit10 && !revealAll && rows.length > 10) ? rows.slice(0,10) : rows;\n      const hiddenCount = rows.length - visibleRows.length;\n      soonKey = pickSoonKey(visibleRows);\n      featuredKey = pickFeaturedKey(visibleRows, soonKey);\n\n      status.textContent = errs.length ? ('Skipped '+errs.length+' route'+(errs.length>1?'s':'')) : '';\n      setStatusInline('<strong>'+total+'<\/strong> sailings found');\n      out.innerHTML = renderTable(visibleRows) + renderCards(visibleRows) + moreButtonHtml(hiddenCount);\n      bindRowLinks();\n      if (navHost) navHost.innerHTML = navHtml();\n\n    } catch(e){\n      status.textContent = 'Failed to load data';\n      out.innerHTML = '<pre>'+String(e.message||e)+'<\/pre>';\n    } finally {\n      setLoader(false);\n      btn.disabled = false;\n    }\n  }\n\n  wrap.addEventListener('click', function(e){\n    const nav = e.target.closest('.tp-day-nav a[data-shift]');\n    if (nav) {\n      e.preventDefault();\n      shiftDate(parseInt(nav.getAttribute('data-shift'),10) || 0);\n      return;\n    }\n    const more = e.target.closest('.tp-more-btn');\n    if (more) {\n      e.preventDefault();\n      revealAll = true;\n      load();\n    }\n  });\n\n  btn.addEventListener('click', load);\n  if (navHost) navHost.innerHTML = navHtml();\n  renderLocalTimes();\n  setInterval(renderLocalTimes, 30000);\n  if (cfg.autoload) load();\n})();\n<\/script>\n\n\n\n\n<p class=\"wp-block-paragraph\">Zo snel mogelijk oversteken? Dan is de <a href=\"https:\/\/hirtshals-kristiansand.com\/nl\/fjord-fstr\/\" type=\"page\" id=\"469\">FSTR<\/a> van Fjord Line de snelste optie. Deze ferry, ook bekend als de Fjord Line catamaran, doet ongeveer 2 uur en 15 minuten over de overtocht.<\/p>\n\n\n    <div class=\"fh-drive-tool\">\r\n        <h3>Bereken hoe laat je aankomt in de haven van Hirtshals vanuit Nederland<\/h3>\r\n        <p class=\"fh-text\">\r\n            Het kan slim zijn om gebruik te maken van onderstaand tooltje. Vul simpelweg in waar je vandaan vertrekt, op welke dag en hoe laat, en zie hoe laat je waarschijnlijk ongeveer aankomt in de haven van Hirtshals. Op die manier kun je een ticket kiezen dat goed aansluit op jouw planning.\r\n        <\/p>\r\n\r\n        <div class=\"fh-field\">\r\n            <label for=\"fh_place\">Vertrekplaats<\/label>\r\n            <select id=\"fh_place\">\r\n                <option value=\"groningen\">Groningen<\/option>\r\n                <option value=\"zwolle\">Zwolle<\/option>\r\n                <option value=\"utrecht\" selected>Utrecht<\/option>\r\n                <option value=\"amsterdam\">Amsterdam<\/option>\r\n                <option value=\"rotterdam\">Rotterdam<\/option>\r\n                <option value=\"breda\">Breda<\/option>\r\n                <option value=\"arnhem\">Arnhem<\/option>\r\n                <option value=\"eindhoven\">Eindhoven<\/option>\r\n            <\/select>\r\n        <\/div>\r\n\r\n        <div class=\"fh-row\">\r\n            <div class=\"fh-field\">\r\n                <label for=\"fh_day\">Vertrekdag<\/label>\r\n                <select id=\"fh_day\">\r\n                    <option value=\"1\">Maandag<\/option>\r\n                    <option value=\"2\">Dinsdag<\/option>\r\n                    <option value=\"3\">Woensdag<\/option>\r\n                    <option value=\"4\">Donderdag<\/option>\r\n                    <option value=\"5\">Vrijdag<\/option>\r\n                    <option value=\"6\">Zaterdag<\/option>\r\n                    <option value=\"0\">Zondag<\/option>\r\n                <\/select>\r\n            <\/div>\r\n\r\n            <div class=\"fh-field\">\r\n                <label for=\"fh_time\">Vertrektijd<\/label>\r\n                <input type=\"time\" id=\"fh_time\" value=\"06:30\">\r\n            <\/div>\r\n        <\/div>\r\n\r\n        <div class=\"fh-field\">\r\n            <button type=\"button\" id=\"fh_calc\">Bereken aankomsttijd<\/button>\r\n        <\/div>\r\n\r\n        <div id=\"fh_result\"><\/div>\r\n    <\/div>\r\n\r\n    <style>\r\n    .fh-drive-tool {\r\n        background: #f0f5fa;\r\n        padding: 26px 28px; \/* \ud83d\udc48 meer ruimte links\/rechts *\/\r\n        border-radius: 10px;\r\n    }\r\n\r\n    .fh-drive-tool h3 {\r\n        margin-bottom: 10px;\r\n    }\r\n\r\n    .fh-drive-tool .fh-text {\r\n        font-size: 0.95em;\r\n        margin-bottom: 20px;\r\n        line-height: 1.6;\r\n    }\r\n\r\n    .fh-drive-tool .fh-field {\r\n        margin-bottom: 18px;\r\n    }\r\n\r\n    .fh-drive-tool .fh-row {\r\n        display: flex;\r\n        gap: 14px;\r\n        align-items: flex-end;\r\n    }\r\n\r\n    .fh-drive-tool .fh-row .fh-field {\r\n        width: 50%;\r\n        display: flex;\r\n        flex-direction: column;\r\n    }\r\n\r\n    .fh-drive-tool button {\r\n        width: 100%;\r\n        padding: 16px 0;\r\n    }\r\n\r\n    .fh-drive-tool #fh_result {\r\n        margin-top: 20px;\r\n        font-size: 0.95em; \/* \ud83d\udc48 zelfde als intro *\/\r\n        line-height: 1.6;\r\n    }\r\n\r\n    .fh-drive-tool #fh_result strong {\r\n        font-size: 1.05em; \/* klein accent op tijd *\/\r\n    }\r\n\r\n    @media (max-width: 767px) {\r\n        .fh-drive-tool .fh-row {\r\n            display: block;\r\n        }\r\n\r\n        .fh-drive-tool .fh-row .fh-field {\r\n            width: 100%;\r\n        }\r\n    }\r\n    <\/style>\r\n\r\n    <script>\r\n    document.addEventListener('DOMContentLoaded', function () {\r\n        var placeEl = document.getElementById('fh_place');\r\n        var dayEl = document.getElementById('fh_day');\r\n        var timeEl = document.getElementById('fh_time');\r\n        var calcEl = document.getElementById('fh_calc');\r\n        var resultEl = document.getElementById('fh_result');\r\n\r\n        if (!placeEl) return;\r\n\r\n        var baseMinutes = {\r\n            groningen: 425,\r\n            zwolle: 490,\r\n            utrecht: 537,\r\n            amsterdam: 552,\r\n            rotterdam: 573,\r\n            breda: 585,\r\n            arnhem: 515,\r\n            eindhoven: 555\r\n        };\r\n\r\n        var labels = {\r\n            groningen: 'Groningen',\r\n            zwolle: 'Zwolle',\r\n            utrecht: 'Utrecht',\r\n            amsterdam: 'Amsterdam',\r\n            rotterdam: 'Rotterdam',\r\n            breda: 'Breda',\r\n            arnhem: 'Arnhem',\r\n            eindhoven: 'Eindhoven'\r\n        };\r\n\r\n        function pad(n){ return String(n).padStart(2,'0'); }\r\n\r\n        function minutesToText(total){\r\n            var h = Math.floor(total \/ 60);\r\n            var m = total % 60;\r\n            return m === 0 ? h + ' uur' : h + ' uur en ' + m + ' minuten';\r\n        }\r\n\r\n        function getTrafficExtra(day, minutes, place){\r\n            var extra = 0;\r\n            var isWeekday = day >= 1 && day <= 5;\r\n\r\n            if (isWeekday){\r\n                if (minutes >= 360 && minutes < 570) extra += 35;\r\n                if (minutes >= 930 && minutes < 1110) extra += 20;\r\n            }\r\n\r\n            if (['amsterdam','utrecht','rotterdam'].includes(place) && isWeekday && minutes < 600){\r\n                extra += 10;\r\n            }\r\n\r\n            return Math.max(0, extra);\r\n        }\r\n\r\n        function formatArrival(time, drive){\r\n            var parts = time.split(':');\r\n            var total = (parseInt(parts[0])*60 + parseInt(parts[1]) + drive) % 1440;\r\n            return pad(Math.floor(total\/60)) + ':' + pad(total%60);\r\n        }\r\n\r\n        calcEl.addEventListener('click', function(){\r\n            var place = placeEl.value;\r\n            var day = parseInt(dayEl.value,10);\r\n            var time = timeEl.value;\r\n\r\n            if (!time){\r\n                resultEl.innerHTML = 'Kies eerst een vertrektijd.';\r\n                return;\r\n            }\r\n\r\n            var minutes = (parseInt(time.split(':')[0])*60 + parseInt(time.split(':')[1]));\r\n            var total = baseMinutes[place] + getTrafficExtra(day, minutes, place);\r\n            var arrival = formatArrival(time, total);\r\n\r\n            resultEl.innerHTML =\r\n    '<p><strong>Verwachte aankomsttijd in de haven van Hirtshals: ' + arrival + ' uur<\/strong><br>' +\r\n    'De reistijd is ongeveer ' + minutesToText(total) + '. Deze berekening houdt rekening met verkeersdrukte, spitsmomenten, weekdagen en mogelijk oponthoud.<\/p>';\r\n        });\r\n    });\r\n    <\/script>\r\n    \n\n\n\n<h2 class=\"wp-block-heading\">Kristiansand Hirtshals vaartijden<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Voor de terugreis vertrek je vanuit de <a href=\"https:\/\/hirtshals-kristiansand.com\/nl\/kristiansand-haven\/\" type=\"page\" id=\"147\">haven van Kristiansand<\/a>. Ook op deze route zijn verspreid over de dag meerdere afvaarten beschikbaar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Color Line vertrekt op deze route vaak als eerste in de ochtend. Fjord Line biedt daarnaast meerdere afvaarten later op de dag. De actuele vaartijden staan hieronder.<\/p>\n\n\n<div class=\"tp-wrap tp-theme-extended_cards tp-day-setup-modern\" data-config=\"{&quot;rest&quot;:&quot;https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/wp-json\\\/timetables-pro\\\/v1\\\/timetables&quot;,&quot;routes&quot;:[172],&quot;days&quot;:3,&quot;autoload&quot;:true,&quot;theme&quot;:&quot;extended_cards&quot;,&quot;daySetup&quot;:&quot;modern&quot;,&quot;limit10&quot;:false,&quot;routeLabels&quot;:{&quot;172&quot;:&quot;Kristiansand naar Hirtshals&quot;},&quot;opFilter&quot;:[&quot;8&quot;,&quot;27&quot;],&quot;labels&quot;:{&quot;ui_date&quot;:&quot;Datum&quot;,&quot;ui_days&quot;:&quot;Dagen&quot;,&quot;ui_button&quot;:&quot;Afvaarten tonen&quot;,&quot;status_idle&quot;:&quot;Kies een datum en klik op Afvaarten tonen&quot;,&quot;status_loading&quot;:&quot;Laden...&quot;,&quot;status_empty&quot;:&quot;Geen afvaarten gevonden&quot;,&quot;status_found&quot;:&quot;%d afvaarten gevonden&quot;,&quot;th_date&quot;:&quot;Datum&quot;,&quot;th_dep&quot;:&quot;Vertrek&quot;,&quot;th_arr&quot;:&quot;Aankomst&quot;,&quot;th_dur&quot;:&quot;Duur&quot;,&quot;th_ship&quot;:&quot;Schip&quot;,&quot;th_op&quot;:&quot;Operator&quot;,&quot;th_route&quot;:&quot;Route&quot;,&quot;wd_sun&quot;:&quot;ZO&quot;,&quot;wd_mon&quot;:&quot;MA&quot;,&quot;wd_tue&quot;:&quot;DI&quot;,&quot;wd_wed&quot;:&quot;WO&quot;,&quot;wd_thu&quot;:&quot;DO&quot;,&quot;wd_fri&quot;:&quot;VR&quot;,&quot;wd_sat&quot;:&quot;ZA&quot;,&quot;summary_footer_one&quot;:&quot;\\u00bb Bekijk alle %s afvaarten in onze dienstregeling&quot;,&quot;summary_footer_two&quot;:&quot;\\u00bb Bekijk meer %1$s en %2$s afvaarten in onze dienstregeling&quot;,&quot;summary_footer_generic&quot;:&quot;Bekijk onze dienstregeling voor meer afvaarten&quot;,&quot;summary_header&quot;:&quot;De volgende twee afvaarten:&quot;,&quot;summary_no_upcoming&quot;:&quot;Geen aankomende afvaarten gevonden&quot;,&quot;more_link&quot;:&quot;Meer afvaarten&quot;,&quot;local_time_pair&quot;:&quot;Lokale tijd vertrekhaven: %1$s \\\/ Lokale tijd aankomsthaven: %2$s&quot;,&quot;local_time_combined&quot;:&quot;Lokale tijd vertrek- en aankomsthaven: %s&quot;,&quot;tag_often_booked&quot;:&quot;\\u2b50 Vaak geboekt&quot;,&quot;tag_leaving_soon&quot;:&quot;\\ud83d\\udd34 Vertrekt binnenkort&quot;,&quot;tag_currently_sailing&quot;:&quot;\\ud83d\\udfe2 Nu onderweg&quot;,&quot;tag_arrived&quot;:&quot;\\u26ab Aangekomen&quot;,&quot;tag_overnight&quot;:&quot;Nachtvaart&quot;},&quot;showLogo&quot;:true,&quot;activityBoosters&quot;:true,&quot;title&quot;:&quot;Alle overtochten&quot;,&quot;shipMap&quot;:{&quot;bf&quot;:&quot;MS Bergensfjord&quot;,&quot;ff&quot;:&quot;FSTR (Snelste!\\ud83d\\udd25)&quot;,&quot;nb87&quot;:&quot;MS Stavangerfjord&quot;},&quot;shipLinks&quot;:{&quot;bf&quot;:&quot;https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/ms-bergensfjord\\\/&quot;,&quot;superspeed 1&quot;:&quot;https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/color-line-superspeed-1\\\/&quot;,&quot;ff&quot;:&quot;https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/fjord-fstr\\\/&quot;,&quot;nb87&quot;:&quot;https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/ms-stavangerfjord\\\/&quot;},&quot;showShip&quot;:true,&quot;departurePort&quot;:&quot;Kristiansand&quot;,&quot;arrivalPort&quot;:&quot;Hirtshals&quot;,&quot;departureTimezone&quot;:&quot;Europe\\\/Oslo&quot;,&quot;arrivalTimezone&quot;:&quot;Europe\\\/Copenhagen&quot;}\">\n  <div class=\"tp-controls\" role=\"group\" aria-label=\"Timetable controls\">\n    <div>\n      <label for=\"tp-date-ops\">Datum<\/label><br>\n      <input id=\"tp-date-ops\" class=\"tp-date\" type=\"date\" aria-label=\"Datum\">\n    <\/div>\n    <div class=\"tp-day-field\">\n      <label for=\"tp-range-ops\">Dagen<\/label><br>\n      <select id=\"tp-range-ops\" class=\"tp-range\" aria-label=\"Dagen\">\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-ops\" class=\"tp-btn\">Afvaarten tonen<\/button><\/div>\n  <\/div>\n\n  <div class=\"tp-local-nav-row\">\n    <div class=\"tp-local-times\" id=\"tp-local-times-ops\" aria-live=\"polite\"><\/div>\n    <div class=\"tp-day-nav-host\"><\/div>\n  <\/div>\n\n      <h3 style=\"margin:8px 0 12px;color:#0f172a;\">Alle overtochten<\/h3>\n  \n  <div id=\"tp-status-ops\" class=\"tp-muted\" aria-live=\"polite\">Kies een datum en klik op Afvaarten tonen<\/div>\n\n  <div id=\"tp-ops-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\">Laden...<\/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-ops');\n  const daysEl = wrap.querySelector('#tp-range-ops');\n  const btn    = wrap.querySelector('#tp-load-ops');\n  const status = wrap.querySelector('#tp-status-ops');\n  const out    = wrap.querySelector('#tp-ops-results');\n  const loader = wrap.querySelector('.tp-loader');\n  const navHost = wrap.querySelector('.tp-day-nav-host');\n  const localTimesEl = wrap.querySelector('#tp-local-times-ops');\n\n  const showShip = (cfg.showShip !== false && cfg.showShip !== 0 && cfg.showShip !== '0');\n  const activityBoosters = !!cfg.activityBoosters;\n  const daySetup = (cfg.daySetup === 'modern') ? 'modern' : 'classic';\n  const limit10 = !!cfg.limit10;\n  let featuredKey = '';\n  let soonKey = '';\n  let revealAll = false;\n\n  if (daySetup === 'modern') {\n    const dayField = wrap.querySelector('.tp-day-field');\n    if (dayField) dayField.style.display = 'none';\n  }\n\n  dateEl.valueAsDate = new Date();\n  dateEl.min = new Date().toISOString().split('T')[0];\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'}); }\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 formatZoneTime(tz){\n    if (!tz) return '--:--';\n    try {\n      return new Intl.DateTimeFormat([], {hour:'2-digit', minute:'2-digit', hour12:false, timeZone: tz}).format(new Date());\n    } catch (e) {\n      return '--:--';\n    }\n  }\n\n  function renderLocalTimes(){\n    if (!localTimesEl) return;\n    if (!cfg.departureTimezone || !cfg.arrivalTimezone) {\n      localTimesEl.textContent = '';\n      return;\n    }\n    const depTime = formatZoneTime(cfg.departureTimezone);\n    const arrTime = formatZoneTime(cfg.arrivalTimezone);\n    const pairTpl = L.local_time_pair || 'Departure Port Local Time: %1$s \/ Arrival Port Local Time: %2$s';\n    const combinedTpl = L.local_time_combined || 'Departure & Arrival Port Local Time: %s';\n    if (depTime === arrTime) {\n      localTimesEl.textContent = combinedTpl.replace('%s', depTime);\n      return;\n    }\n    localTimesEl.textContent = pairTpl.replace('%1$s', depTime).replace('%2$s', arrTime);\n  }\n\n  function setStatusInline(html){\n    let box = wrap.querySelector('.tp-controls-status');\n    if (!box) {\n      box = document.createElement('div');\n      box.className = 'tp-controls-status';\n      wrap.querySelector('.tp-controls').appendChild(box);\n    }\n    box.innerHTML = html || '';\n  }\n\n  function clearStatusInline(){\n    const box = wrap.querySelector('.tp-controls-status');\n    if (box) box.innerHTML = '';\n  }\n\n  function navHtml(){\n    if (daySetup !== 'modern') return '';\n    const base = new Date(dateEl.value || new Date());\n    const today = new Date();\n    const tomorrow = new Date(today);\n    tomorrow.setDate(today.getDate() + 1);\n    const isToday = base.toDateString() === today.toDateString();\n    let prevLabel = 'Show previous day';\n    let nextLabel = 'Show next day';\n    if (isToday) nextLabel = 'Show tomorrow';\n    if (base.toDateString() === tomorrow.toDateString()) prevLabel = 'Show today';\n    return '<div class=\"tp-day-nav\">'\n      + (isToday ? '' : '<a href=\"#\" data-shift=\"-1\">'+prevLabel+'<\/a>')\n      + '<a href=\"#\" data-shift=\"1\">'+nextLabel+'<\/a>'\n      + '<\/div>';\n  }\n\n  function shiftDate(delta){\n    const base = new Date(dateEl.value || new Date());\n    base.setDate(base.getDate() + delta);\n    dateEl.value = iso(base);\n    load();\n  }\n\n  function moreButtonHtml(hiddenCount){\n    if (!limit10 || revealAll || hiddenCount <= 0) return '';\n    return '<div style=\"margin-top:12px;text-align:center\">'\n      + '<button type=\"button\" class=\"tp-btn tp-more-btn\">Show more departures ('+hiddenCount+')<\/button>'\n      + '<\/div>';\n  }\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 rowKey(r){\n    return [String(r.routeId||''), String(r.opId||''), String(+r.dep||0), String(+r.arr||0)].join('|');\n  }\n\n  function pickFeaturedKey(rows, avoidKey){\n    if (!activityBoosters || !rows.length) return '';\n    const routesSum = Array.isArray(cfg.routes) ? cfg.routes.reduce((acc, n)=>acc + (parseInt(n,10)||0), 0) : 0;\n    const dateSeed = parseInt(String(dateEl.value || '').replace(\/-\/g,''), 10) || 0;\n\n    let pool = rows;\n    if (avoidKey) {\n      const soonIdx = rows.findIndex(r => rowKey(r) === avoidKey);\n      if (soonIdx >= 0) {\n        const later = rows.slice(soonIdx + 1).filter(r => rowKey(r) !== avoidKey);\n        pool = later.length ? later : rows.filter(r => rowKey(r) !== avoidKey);\n      } else {\n        pool = rows.filter(r => rowKey(r) !== avoidKey);\n      }\n    }\n\n    if (!pool.length) return '';\n    const idx = Math.abs((routesSum + dateSeed + rows.length) % pool.length);\n    return rowKey(pool[idx]);\n  }\n\n  function pickSoonKey(rows){\n    if (!activityBoosters || !rows.length) return '';\n    const now = Date.now();\n    const todayStr = new Date(now).toDateString();\n    const firstUpcoming = rows.find(r => (r.dep instanceof Date) && r.dep.getTime() > now && r.dep.toDateString() === todayStr);\n    return firstUpcoming ? rowKey(firstUpcoming) : '';\n  }\n\n  function boostersFor(r){\n    if (!activityBoosters) return [];\n    const now = Date.now();\n    const depMs = (r.dep instanceof Date) ? r.dep.getTime() : 0;\n    const arrMs = (r.arr instanceof Date) ? r.arr.getTime() : 0;\n    const depIsToday = (r.dep instanceof Date) && (new Date(now).toDateString() === r.dep.toDateString());\n\n    if (depMs > 0 && arrMs > 0 && depMs <= now && arrMs > now) {\n      return [{cls:'tp-booster-sailing', text:(L.tag_currently_sailing || '\ud83d\udfe2 Currently sailing')}];\n    }\n    if (depIsToday && arrMs > 0 && arrMs <= now) {\n      return [{cls:'tp-booster-arrived', text:(L.tag_arrived || '\u26ab Arrived')}];\n    }\n    if (rowKey(r) === soonKey) return [{cls:'tp-booster-soon', text:(L.tag_leaving_soon || '\ud83d\udd34 Leaving soon')}];\n    if (r.dep && r.arr && r.dep.toDateString() !== r.arr.toDateString()) return [{cls:'tp-booster-overnight', text:(L.tag_overnight || 'Overnight')}];\n\n    return [];\n  }\n\n  function boostersHtml(r, floating){\n    const tags = boostersFor(r);\n    if (!tags.length) return '';\n    const cls = floating ? 'tp-boosters tp-boosters-float' : 'tp-boosters';\n    return '<div class=\"'+cls+'\">' + tags.map(t => '<span class=\"tp-booster '+t.cls+'\">'+boosterLabelHtml(t.text)+'<\/span>').join('') + '<\/div>';\n  }\n\n  function boosterLabelHtml(text){\n    return String(text || '').replace(\/^(\ud83d\udd34|\ud83d\udfe2|\u26ab|\u2b50)\\s*\/, '<span class=\"tp-booster-emoji\">$1<\/span>');\n  }\n\n  function routeTicketHtml(label){\n    const raw = String(label || '').trim();\n    if (!raw) return '';\n    if (cfg.theme !== 'small_cards') return escAttr(raw);\n    const parts = raw.split(\/\\s*[\\-\u2013]\\s*\/, 2);\n    if (parts.length < 2) return escAttr(raw);\n    return escAttr(parts[0]) + '<br>' + escAttr(parts[1]);\n  }\n\n  function escAttr(v){\n    return String(v||'').replace(\/[&<>\"']\/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[m]));\n  }\n\n  function bindRowLinks(){\n    if (cfg.theme !== 'max_clickouts') return;\n    out.querySelectorAll('tr.tp-row-link').forEach((row)=>{\n      row.setAttribute('role','link');\n      row.setAttribute('tabindex','0');\n      row.addEventListener('click', (e)=>{\n        if (e.target && e.target.closest('a')) return;\n        const href = row.getAttribute('data-row-link');\n        if (!href) return;\n        window.open(href, '_blank', 'noopener,noreferrer');\n      });\n      row.addEventListener('keydown', (e)=>{\n        if (e.key !== 'Enter' && e.key !== ' ') return;\n        e.preventDefault();\n        row.click();\n      });\n    });\n  }\n\n  function routeLabel(rid){\n    return (cfg.routeLabels && (cfg.routeLabels[String(rid)] || cfg.routeLabels[rid])) || String(rid);\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,route:L.th_route,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.route+'<\/th>');\n    headCells.push('<th>'+th.op+'<\/th>');\n\n    let html = '<table class=\"tp-table\"><thead><tr>'+headCells.join('')+'<\/tr><\/thead><tbody>';\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const clickable = (cfg.theme === 'max_clickouts' && r.opLink);\n      const rowAttr = clickable ? (' class=\"tp-row-link\" data-row-link=\"'+escAttr(r.opLink)+'\"') : '';\n      const dateBoosters = (cfg.theme === 'max_clickouts') ? '' : boostersHtml(r, false);\n      const routeBoosters = (cfg.theme === 'max_clickouts') ? boostersHtml(r, false) : '';\n      const cells = [\n        '<td>'+dShort(r.dep)+' <span class=\"tp-day\">'+dow+'<\/span>'+dateBoosters+'<\/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>'+routeBoosters+'<div>'+routeLabel(r.routeId)+'<\/div><\/td>');\n      cells.push('<td>'+opCell(r)+'<\/td>');\n      html += '<tr'+rowAttr+'>'+cells.join('')+'<\/tr>';\n    });\n    html += '<\/tbody><\/table>';\n    return html;\n  }\n\n  function renderCards(rows){\n    if (cfg.theme === 'extended_cards') {\n      return renderExtendedCards(rows);\n    }\n\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\">'+boostersHtml(r, true)+\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) {\n        html +=\n        \/\/ Row 2: Ship\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+shipBadge(r.ship)+'<\/div>'+\n          '<div class=\"rhs\"><\/div>'+\n        '<\/div>';\n      }\n\n      html +=\n        \/\/ Row 3: Operator + route label\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+opCell(r)+'<\/div>'+\n          '<div class=\"rhs\"><span class=\"tp-route-ticket\">'+routeTicketHtml(routeLabel(r.routeId))+'<\/span><\/div>'+\n        '<\/div>'+\n      '<\/div>';\n    });\n    html += '<\/div>';\n    return html;\n  }\n\n  function renderExtendedCards(rows){\n    const ctaText = L.more_link || 'More sailings';\n    let html = '<div class=\"tp-cardlist\">';\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const link = r.opLink ? ('<a class=\"tp-ext-cta\" href=\"'+r.opLink+'\" target=\"_blank\" rel=\"nofollow noopener\">'+ctaText+' <span class=\"tp-ext-cta-arrow\">\u00bb<\/span><\/a>') : '<span class=\"tp-ext-cta\">'+ctaText+' <span class=\"tp-ext-cta-arrow\">\u00bb<\/span><\/span>';\n      html += '<div class=\"tp-ext-card\">'\n        + boostersHtml(r, true)\n        + '<div class=\"tp-ext-left\">'\n        +   '<div>'+opCell(r)+'<\/div>'\n        +   (showShip ? ('<div>'+shipBadge(r.ship)+'<\/div>') : '')\n        + '<\/div>'\n        + '<div class=\"tp-ext-mid\">'\n        +   '<div class=\"tp-ext-top\"><span class=\"tp-sub\">'+dShort(r.dep)+' <span class=\"tp-day\">'+dow+'<\/span><\/span><span class=\"tp-ext-dur\">'+dur(r.min)+'<\/span><\/div>'\n        +   '<div class=\"tp-ext-times\"><span class=\"tp-ext-time\">'+hhmm(r.dep)+'<\/span><span class=\"tp-ext-line\"><\/span><span class=\"tp-ext-dur\">'+dur(r.min)+'<\/span><span class=\"tp-ext-line\"><\/span><span class=\"tp-ext-time\">'+hhmm(r.arr)+'<\/span><\/div>'\n        +   '<div class=\"tp-ext-route\">'+routeLabel(r.routeId)+'<\/div>'\n        + '<\/div>'\n        + '<div class=\"tp-ext-right\">'+link+'<\/div>'\n        + '<\/div>';\n    });\n    html += '<\/div>';\n    return html;\n  }\n\n  \/\/ Safe fetch helper\n  async function fetchOne(routeId, from, to){\n    try{\n      const url = new URL(cfg.rest);\n      url.searchParams.set('route', String(routeId));\n      url.searchParams.set('from', from);\n      url.searchParams.set('to', to);\n      const res = await fetch(url.toString(), {credentials:'same-origin'});\n      if(!res.ok) return { rid: routeId, 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      return { rid: routeId, rows: list };\n    }catch(e){\n      return { rid: routeId, error: String(e && e.message ? e.message : e) };\n    }\n  }\n\n  async function load(){\n    btn.disabled = true; setLoader(true); status.textContent = L.status_loading;\n\n    const start = new Date(dateEl.value || new Date());\n    const days  = (daySetup === 'modern') ? 1 : Math.max(1, parseInt(daysEl.value,10)||1);\n    const end   = new Date(start); end.setDate(start.getDate()+days-1);\n    const fromIso = iso(start), toIso = iso(end);\n\n    try{\n      const results  = await Promise.all((cfg.routes||[]).map(rid=>fetchOne(rid, fromIso, toIso)));\n      const oks  = results.filter(r => !r.error);\n      const errs = results.filter(r =>  r.error);\n\n      const all = [];\n      oks.forEach(({rid, rows})=>{\n        rows.forEach(t=>{\n          all.push({\n            routeId: rid,\n            opId: t.operatorId || null,\n            opName: t.operatorName || '',\n            opLogo: t.operatorLogo || '',\n            opLink: t.operatorLink || '',\n            dep: toLocal(t.departureTime),\n            arr: toLocal(t.arrivalTime),\n            min: t.durationInMinutes || 0,\n            ship: t.shipName || ''\n          });\n        });\n      });\n\n      let rows = all;\n      if (Array.isArray(cfg.opFilter) && cfg.opFilter.length>0) {\n        rows = all.filter(r => r.opId && cfg.opFilter.includes(String(r.opId)));\n      }\n\n      rows.sort((a,b)=> a.dep - b.dep);\n\n      const todayStr = new Date().toDateString();\n      const nowMs = Date.now();\n      const arrivedToday = rows\n        .filter(r => (r.dep instanceof Date) && (r.arr instanceof Date) && r.dep.toDateString() === todayStr && r.arr.getTime() <= nowMs)\n        .sort((a,b) => b.arr - a.arr);\n      if (arrivedToday.length > 2) {\n        const keep = new Set(arrivedToday.slice(0,2).map(rowKey));\n        rows = rows.filter(r => {\n          const isArrivedToday = (r.dep instanceof Date) && (r.arr instanceof Date) && r.dep.toDateString() === todayStr && r.arr.getTime() <= nowMs;\n          return !isArrivedToday || keep.has(rowKey(r));\n        });\n      }\n\n      const total = rows.length;\n      if (total===0){\n        status.textContent = L.status_empty + (errs.length ? ' (some routes returned no data or errors)' : '');\n        clearStatusInline();\n        out.innerHTML='';\n        setLoader(false); btn.disabled=false; return;\n      }\n\n      const visibleRows = (limit10 && !revealAll && rows.length > 10) ? rows.slice(0,10) : rows;\n      const hiddenCount = rows.length - visibleRows.length;\n      soonKey = pickSoonKey(visibleRows);\n      featuredKey = pickFeaturedKey(visibleRows, soonKey);\n\n      status.textContent = errs.length ? ('Skipped '+errs.length+' route'+(errs.length>1?'s':'')) : '';\n      setStatusInline('<strong>'+total+'<\/strong> sailings found');\n      out.innerHTML = renderTable(visibleRows) + renderCards(visibleRows) + moreButtonHtml(hiddenCount);\n      bindRowLinks();\n      if (navHost) navHost.innerHTML = navHtml();\n\n    } catch(e){\n      status.textContent = 'Failed to load data';\n      out.innerHTML = '<pre>'+String(e.message||e)+'<\/pre>';\n    } finally {\n      setLoader(false);\n      btn.disabled = false;\n    }\n  }\n\n  wrap.addEventListener('click', function(e){\n    const nav = e.target.closest('.tp-day-nav a[data-shift]');\n    if (nav) {\n      e.preventDefault();\n      shiftDate(parseInt(nav.getAttribute('data-shift'),10) || 0);\n      return;\n    }\n    const more = e.target.closest('.tp-more-btn');\n    if (more) {\n      e.preventDefault();\n      revealAll = true;\n      load();\n    }\n  });\n\n  btn.addEventListener('click', load);\n  if (navHost) navHost.innerHTML = navHtml();\n  renderLocalTimes();\n  setInterval(renderLocalTimes, 30000);\n  if (cfg.autoload) load();\n})();\n<\/script>\n\n\n\n\n<p class=\"has-ast-global-color-4-background-color has-background wp-block-paragraph\">Maak je een rondreis en vaar je later terug via dezelfde route? Dan is een retourticket vaak goedkoper. <a href=\"https:\/\/hirtshals-kristiansand.com\/nl\/prijzen\/\" type=\"page\" id=\"590\">Bekijk hier meer informatie over de prijzen voor de veerboottickets<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Op de route tussen Hirtshals en Kristiansand kun je kiezen uit twee rederijen: Fjord Line met meerdere afvaarten per dag [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":587,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"inline_featured_image":false,"_uag_custom_page_level_css":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","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":"","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-267","page","type-page","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Vaartijden Hirtshals Kristiansand (live dienstregeling)<\/title>\n<meta name=\"description\" content=\"Check de vaartijden van de veerboten van Hirtshals naar Kristiansand (Color Line &amp; Fjord Line). Live dienstregeling.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/\" \/>\n<meta property=\"og:locale\" content=\"nl_NL\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Vaartijden Hirtshals Kristiansand (live dienstregeling)\" \/>\n<meta property=\"og:description\" content=\"Check de vaartijden van de veerboten van Hirtshals naar Kristiansand (Color Line &amp; Fjord Line). Live dienstregeling.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/\" \/>\n<meta property=\"og:site_name\" content=\"Hirtshals Kristiansand Ferry Gids\" \/>\n<meta property=\"article:modified_time\" content=\"2026-05-20T07:57:17+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"800\" \/>\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=\"Geschatte leestijd\" \/>\n\t<meta name=\"twitter:data1\" content=\"2 minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/dienstregeling\\\/\",\"url\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/dienstregeling\\\/\",\"name\":\"Vaartijden Hirtshals Kristiansand (live dienstregeling)\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/dienstregeling\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/dienstregeling\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2024\\\/03\\\/hirtshals-kristiansand-ferry-2.jpg\",\"datePublished\":\"2024-02-28T16:00:48+00:00\",\"dateModified\":\"2026-05-20T07:57:17+00:00\",\"description\":\"Check de vaartijden van de veerboten van Hirtshals naar Kristiansand (Color Line & Fjord Line). Live dienstregeling.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/dienstregeling\\\/#breadcrumb\"},\"inLanguage\":\"nl-NL\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/dienstregeling\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"nl-NL\",\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/dienstregeling\\\/#primaryimage\",\"url\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2024\\\/03\\\/hirtshals-kristiansand-ferry-2.jpg\",\"contentUrl\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2024\\\/03\\\/hirtshals-kristiansand-ferry-2.jpg\",\"width\":1200,\"height\":800,\"caption\":\"dienstregeling\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/dienstregeling\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Hirtshals Kristiansand\",\"item\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Vaartijden Hirtshals Kristiansand (live dienstregeling)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/#website\",\"url\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/\",\"name\":\"Hirtshals Kristiansand Ferry Gids\",\"description\":\"Alles over de Hirtshals Kristiansand Ferry Overtocht\",\"publisher\":{\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"nl-NL\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/#organization\",\"name\":\"Hirtshals Kristiansand Ferry Gids\",\"url\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"nl-NL\",\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/05\\\/hirtshals-kristiansand.svg\",\"contentUrl\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/05\\\/hirtshals-kristiansand.svg\",\"width\":800,\"height\":256,\"caption\":\"Hirtshals Kristiansand Ferry Gids\"},\"image\":{\"@id\":\"https:\\\/\\\/hirtshals-kristiansand.com\\\/nl\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Vaartijden Hirtshals Kristiansand (live dienstregeling)","description":"Check de vaartijden van de veerboten van Hirtshals naar Kristiansand (Color Line & Fjord Line). Live dienstregeling.","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:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/","og_locale":"nl_NL","og_type":"article","og_title":"Vaartijden Hirtshals Kristiansand (live dienstregeling)","og_description":"Check de vaartijden van de veerboten van Hirtshals naar Kristiansand (Color Line & Fjord Line). Live dienstregeling.","og_url":"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/","og_site_name":"Hirtshals Kristiansand Ferry Gids","article_modified_time":"2026-05-20T07:57:17+00:00","og_image":[{"width":1200,"height":800,"url":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Geschatte leestijd":"2 minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/","url":"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/","name":"Vaartijden Hirtshals Kristiansand (live dienstregeling)","isPartOf":{"@id":"https:\/\/hirtshals-kristiansand.com\/nl\/#website"},"primaryImageOfPage":{"@id":"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/#primaryimage"},"image":{"@id":"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/#primaryimage"},"thumbnailUrl":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2.jpg","datePublished":"2024-02-28T16:00:48+00:00","dateModified":"2026-05-20T07:57:17+00:00","description":"Check de vaartijden van de veerboten van Hirtshals naar Kristiansand (Color Line & Fjord Line). Live dienstregeling.","breadcrumb":{"@id":"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/#breadcrumb"},"inLanguage":"nl-NL","potentialAction":[{"@type":"ReadAction","target":["https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/"]}]},{"@type":"ImageObject","inLanguage":"nl-NL","@id":"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/#primaryimage","url":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2.jpg","contentUrl":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2.jpg","width":1200,"height":800,"caption":"dienstregeling"},{"@type":"BreadcrumbList","@id":"https:\/\/hirtshals-kristiansand.com\/nl\/dienstregeling\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Hirtshals Kristiansand","item":"https:\/\/hirtshals-kristiansand.com\/nl\/"},{"@type":"ListItem","position":2,"name":"Vaartijden Hirtshals Kristiansand (live dienstregeling)"}]},{"@type":"WebSite","@id":"https:\/\/hirtshals-kristiansand.com\/nl\/#website","url":"https:\/\/hirtshals-kristiansand.com\/nl\/","name":"Hirtshals Kristiansand Ferry Gids","description":"Alles over de Hirtshals Kristiansand Ferry Overtocht","publisher":{"@id":"https:\/\/hirtshals-kristiansand.com\/nl\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/hirtshals-kristiansand.com\/nl\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"nl-NL"},{"@type":"Organization","@id":"https:\/\/hirtshals-kristiansand.com\/nl\/#organization","name":"Hirtshals Kristiansand Ferry Gids","url":"https:\/\/hirtshals-kristiansand.com\/nl\/","logo":{"@type":"ImageObject","inLanguage":"nl-NL","@id":"https:\/\/hirtshals-kristiansand.com\/nl\/#\/schema\/logo\/image\/","url":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2025\/05\/hirtshals-kristiansand.svg","contentUrl":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2025\/05\/hirtshals-kristiansand.svg","width":800,"height":256,"caption":"Hirtshals Kristiansand Ferry Gids"},"image":{"@id":"https:\/\/hirtshals-kristiansand.com\/nl\/#\/schema\/logo\/image\/"}}]}},"uagb_featured_image_src":{"full":["https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2.jpg",1200,800,false],"thumbnail":["https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2-150x150.jpg",150,150,true],"medium":["https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2-300x200.jpg",300,200,true],"medium_large":["https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2-768x512.jpg",768,512,true],"large":["https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2-1024x683.jpg",1024,683,true],"1536x1536":["https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2.jpg",1200,800,false],"2048x2048":["https:\/\/hirtshals-kristiansand.com\/nl\/wp-content\/uploads\/sites\/2\/2024\/03\/hirtshals-kristiansand-ferry-2.jpg",1200,800,false]},"uagb_author_info":{"display_name":"mail@robberttigchelaar.com","author_link":"https:\/\/hirtshals-kristiansand.com\/nl\/author\/mailrobberttigchelaar-com\/"},"uagb_comment_info":0,"uagb_excerpt":"Op de route tussen Hirtshals en Kristiansand kun je kiezen uit twee rederijen: Fjord Line met meerdere afvaarten per dag [&hellip;]","_links":{"self":[{"href":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-json\/wp\/v2\/pages\/267","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-json\/wp\/v2\/comments?post=267"}],"version-history":[{"count":65,"href":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-json\/wp\/v2\/pages\/267\/revisions"}],"predecessor-version":[{"id":1302,"href":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-json\/wp\/v2\/pages\/267\/revisions\/1302"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-json\/wp\/v2\/media\/587"}],"wp:attachment":[{"href":"https:\/\/hirtshals-kristiansand.com\/nl\/wp-json\/wp\/v2\/media?parent=267"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}