{"id":792,"date":"2026-08-15T22:18:55","date_gmt":"2026-08-15T22:18:55","guid":{"rendered":"https:\/\/surf-conditions.com\/?page_id=792"},"modified":"2026-08-15T22:20:57","modified_gmt":"2026-08-15T22:20:57","slug":"dukhan-beach","status":"publish","type":"page","link":"https:\/\/surf-conditions.com\/fr\/dukhan-beach\/","title":{"rendered":"dukhan beach Surf Forecast &#038; Surf Guide"},"content":{"rendered":"        <main class=\"bpts-spot-page\" data-spot=\"dukhan-beach\">\n            <script type=\"application\/ld+json\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/surf-conditions.com\/fr\/dukhan-beach\/#webpage\",\"url\":\"https:\/\/surf-conditions.com\/fr\/dukhan-beach\/\",\"name\":\"dukhan beach Surf Forecast & Surf Guide | Qatar\",\"description\":\"Check the live dukhan beach surf forecast for wave height, swell, wind, tide and weather, plus nearby surf spots and practical location information.\",\"about\":{\"@id\":\"https:\/\/surf-conditions.com\/fr\/dukhan-beach\/#place\"},\"inLanguage\":\"en\"},{\"@type\":\"Place\",\"@id\":\"https:\/\/surf-conditions.com\/fr\/dukhan-beach\/#place\",\"name\":\"dukhan beach\",\"address\":{\"@type\":\"PostalAddress\",\"addressLocality\":\"\",\"addressRegion\":\"\",\"addressCountry\":\"\"},\"geo\":{\"@type\":\"GeoCoordinates\",\"latitude\":25.405136,\"longitude\":50.755463}},{\"@type\":\"BreadcrumbList\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/surf-conditions.com\/fr\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Surf Spots\",\"item\":\"https:\/\/surf-conditions.com\/fr\/surf-spots\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Qatar Surf Spots\",\"item\":\"https:\/\/surf-conditions.com\/fr\/surf-spots\/qatar\/\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"dukhan beach\",\"item\":\"https:\/\/surf-conditions.com\/fr\/dukhan-beach\/\"}]},{\"@type\":\"FAQPage\",\"mainEntity\":[{\"@type\":\"Question\",\"name\":\"How do I check the surf forecast for dukhan beach?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Use the live forecast on this page to compare current and upcoming wave height, swell, wind, tide and weather for the coordinates listed for dukhan beach.\"}},{\"@type\":\"Question\",\"name\":\"Where is dukhan beach?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"dukhan beach is located near Qatar at approximately 25.4051, 50.7555.\"}},{\"@type\":\"Question\",\"name\":\"What surfing level is dukhan beach for?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"This location has not yet been given a curated difficulty rating. Choose conditions that match your ability and check local advice before entering the water.\"}},{\"@type\":\"Question\",\"name\":\"When should I surf dukhan beach?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"The best session depends on the live combination of swell, wind and tide. Regional surf seasonality is described on this page without assuming one fixed best month for every year.\"}},{\"@type\":\"Question\",\"name\":\"What should I check before surfing dukhan beach?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Before surfing dukhan beach, check access, currents, rocks or reef, water quality, local rules and the experience level required. Conditions can change quickly; compare the forecast with the ocean in front of you and stay within your ability.\"}}]}]}<\/script>\n\n            <nav class=\"bpts-spot-breadcrumbs\" aria-label=\"Breadcrumb\">\n                <a href=\"https:\/\/surf-conditions.com\/fr\/\">Home<\/a><span aria-hidden=\"true\">\u203a<\/span>\n                <a href=\"https:\/\/surf-conditions.com\/fr\/surf-spots\/\">Surf Spots<\/a><span aria-hidden=\"true\">\u203a<\/span>\n                <a href=\"https:\/\/surf-conditions.com\/fr\/surf-spots\/qatar\/\">Qatar<\/a><span aria-hidden=\"true\">\u203a<\/span>\n                                <span aria-current=\"page\">dukhan beach<\/span>\n            <\/nav>\n\n            <section class=\"bpts-spot-hero\" aria-labelledby=\"bpts-spot-title\">\n                <div class=\"bpts-spot-hero-copy\">\n                    <p class=\"bpts-spot-eyebrow\">Qatar<\/p>\n                    <h1 id=\"bpts-spot-title\">dukhan beach Surf Forecast &amp; Surf Guide<\/h1>\n                    <p class=\"bpts-spot-lead\">dukhan beach is a surf spot in Qatar. Use the live forecast below to compare wave height, swell, wind, tide and weather for this exact location before deciding when to surf.<\/p>\n                    <div class=\"bpts-spot-hero-actions\"><a class=\"bpts-primary-btn\" href=\"#live-forecast\">Check live forecast<\/a><a class=\"bpts-secondary-btn\" href=\"#spot-guide\">Read the surf guide<\/a><\/div>\n                    <div class=\"bpts-context-links\"><a href=\"https:\/\/surf-conditions.com\/fr\/surf-spots\/qatar\/\">All Qatar surf spots<\/a><\/div>\n                <\/div>\n                <aside class=\"bpts-spot-summary\" aria-label=\"dukhan beach quick facts\">\n                    <div><span>Break<\/span><strong>Surf break<\/strong><\/div>\n                    <div><span>Level<\/span><strong>Check local conditions<\/strong><\/div>\n                    <div><span>Best swell<\/span><strong>Use live swell forecast<\/strong><\/div>\n                    <div><span>Best wind<\/span><strong>Use live wind forecast<\/strong><\/div>\n                    <div><span>Best season<\/span><strong>Varies with regional swell season<\/strong><\/div>\n                    <div><span>Bottom<\/span><strong>Varies by local setup<\/strong><\/div>\n                <\/aside>\n            <\/section>\n\n            <section id=\"live-forecast\" class=\"bpts-section bpts-forecast-section\" aria-labelledby=\"bpts-live-title\">\n                <div class=\"bpts-section-heading bpts-section-heading-wide\"><div><p class=\"bpts-kicker\">LIVE CONDITIONS<\/p><h2 id=\"bpts-live-title\">dukhan beach surf forecast<\/h2><\/div><p>Compare surf height, swell direction and period, wind, tide and temperature before deciding when to paddle out.<\/p><\/div>\n                <div class=\"bpts-forecast-embed\">        <div id=\"tsh-surf-f346ce27-c3ae-4f92-a97e-f929244b8786\" class=\"tsh-surf\" data-spot=\"dukhan-beach\">\n            <div class=\"tsh-top\">\n                <div class=\"tsh-titlebar\" data-titlebar><\/div>\n                <div>\n                    <h2>dukhan beach Surf Report &amp; Forecast<\/h2>\n                    <div class=\"tsh-sub\">Surf Report &amp; Surf Forecast<\/div>\n                <\/div>\n            <\/div>\n\n            <div class=\"tsh-tabs\">\n                <button class=\"active\" data-scroll=\"conditions\">Report &amp; Forecast<\/button>\n                <button data-scroll=\"charts\">Charts<\/button>\n                \n            <\/div>\n\n            <section class=\"tsh-section\" data-section=\"conditions\">\n                <div class=\"tsh-section-head\">\n                    <h3>Current Surf Conditions<\/h3>\n                    <div class=\"tsh-localtime\">Local time: <span data-localtime>\u2014<\/span><\/div>\n                <\/div>\n\n                <div class=\"tsh-rating-card\">\n                    <span>CONDITION RATING<\/span>\n                    <div class=\"tsh-rating-row\">\n                        <strong data-rating>Loading\u2026<\/strong>\n                        <div class=\"tsh-rating-bars\" data-rating-bars><\/div>\n                    <\/div>\n                <\/div>\n\n                <div class=\"tsh-current-grid\">\n                    <article class=\"tsh-card\">\n                        <span>SURF HEIGHT<\/span>\n                        <strong data-surf-range>\u2014<\/strong>\n                        <p data-body-scale>\u2014<\/p>\n                    <\/article>\n                    <article class=\"tsh-card\">\n                        <span>SWELL<\/span>\n                        <strong><b data-swell-height>\u2014<\/b> <em data-swell-period>\u2014<\/em> <i class=\"tsh-dir-arrow\" data-swell-arrow>\u2191<\/i> <em data-swell-dir>\u2014<\/em><\/strong>\n                        <p>Primary swell<\/p>\n                    <\/article>\n                    <article class=\"tsh-card tsh-wind-card\">\n                        <span>WIND<\/span>\n                        <strong><b data-wind-speed>\u2014<\/b> <em data-wind-dir>\u2014<\/em><\/strong>\n                        <p><span data-wind-gust>\u2014<\/span> gusts, <span data-wind-quality>\u2014<\/span><\/p>\n                        <div class=\"tsh-compass\">\n                                                                                                                    <img loading=\"lazy\"\n                                    class=\"tsh-mini-map\"\n                                    src=\"https:\/\/maps.googleapis.com\/maps\/api\/staticmap?center=25.405136,50.755463&#038;zoom=16&#038;size=640x360&#038;scale=2&#038;maptype=satellite&#038;format=jpg&#038;key=AIzaSyDo7Nm3s_u7fFyZ_VGNhYWf8iizUxzEL5A\"\n                                    alt=\"Satellite image of dukhan beach\"\n                                    loading=\"lazy\"\n                                    decoding=\"async\"\n                                    width=\"320\"\n                                    height=\"180\"\n                                \/>\n                                                        <div class=\"tsh-map-shade\"><\/div>\n                            <div class=\"tsh-spot-pin\" aria-hidden=\"true\"><i><\/i><\/div>\n                            <div class=\"tsh-compass-ring\"><\/div>\n                            <div class=\"tsh-compass-arrow\" data-wind-arrow>\u27a4<\/div>\n                            <div class=\"n\">N<\/div><div class=\"e\">E<\/div><div class=\"s\">S<\/div><div class=\"w\">W<\/div>\n                        <\/div>\n                    <\/article>\n                    <article class=\"tsh-card tsh-tide-card tsh-tide-times-card\">\n                        <span>TIDE<\/span>\n                        <div class=\"tsh-next-tide-row tsh-next-high-row\">\n                            <div class=\"tsh-tide-label\">\n                                <i class=\"tsh-tide-dot high\"><\/i>\n                                <b>Next high tide<\/b>\n                            <\/div>\n                            <strong data-next-high-time>\u2014<\/strong>\n                        <\/div>\n                        <div class=\"tsh-next-tide-row\">\n                            <div class=\"tsh-tide-label\">\n                                <i class=\"tsh-tide-dot low\"><\/i>\n                                <b>Next low tide<\/b>\n                            <\/div>\n                            <strong data-next-low-time>\u2014<\/strong>\n                        <\/div>\n                    <\/article>\n                    <article class=\"tsh-card\">\n                        <span>TEMPERATURE<\/span>\n                        <strong>\ud83d\udca7 <b data-water-temp>\u2014<\/b> &nbsp; \u2600\ufe0f <b data-air-temp>\u2014<\/b><\/strong>\n                        <p data-wetsuit>\u2014<\/p>\n                        <small>Forecast temperature<\/small>\n                    <\/article>\n                <\/div>\n            <\/section>\n\n            <section class=\"tsh-section tsh-charts\" data-section=\"charts\">\n                <div class=\"tsh-pro-chart\" data-pro-chart><\/div>\n                <div class=\"tsh-multiday-forecast\" data-forecast-days><\/div>\n            <\/section>\n\n            <div class=\"tsh-error\" data-error hidden><\/div>\n        <\/div>\n        <style>\n.tsh-surf{--ink:#111827;--muted:#7b879d;--line:#e5e9ef;--soft:#f5f7f9;--night:#edf0f4;--blue:#b6d9e7;--yellow:#ffca17;--green:#12c978;--orange:#ff9e00;--rating-color:#e5e9ef;font-family:Arial,Helvetica,sans-serif;color:var(--ink);background:#fff;border:1px solid #e7e9ed;max-width:100%;overflow:hidden}\n.tsh-surf *{box-sizing:border-box}.tsh-top{display:flex;gap:12px;align-items:flex-start;padding:18px 24px 8px}.tsh-titlebar{width:7px;height:54px;border-radius:5px;background:var(--rating-color);flex:0 0 auto;transition:background-color .2s ease}.tsh-top h2{font-size:29px;line-height:1.05;margin:0 0 7px;font-weight:800}.tsh-sub{font-size:12px;color:#8b95a8;font-weight:700}.tsh-tabs{display:flex;align-items:flex-end;gap:38px;padding:0 22px;border-bottom:1px solid #e5e7eb;background:#fff}.tsh-tabs button,.tsh-view-toggle button{font:inherit;background:none;border:0;cursor:pointer}.tsh-tabs button{position:relative;padding:15px 0 14px;font-size:16px;line-height:20px;font-weight:700;color:#202124;border-bottom:0;white-space:nowrap}.tsh-tabs button:hover{color:#111}.tsh-tabs button.active{color:#111}.tsh-tabs button.active:after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:3px;background:#111;border-radius:0}.tsh-tabs button,\n.tsh-tabs button:hover,\n.tsh-tabs button:focus,\n.tsh-tabs button:focus-visible,\n.tsh-tabs button:active,\n.tsh-tabs button.active{\nbackground:#fff!important;\nbackground-color:#fff!important;\ncolor:#111!important;\nbox-shadow:none!important;\noutline:none!important;\nborder-radius:0!important;\ntext-shadow:none!important;\n}\n.tsh-tabs button:hover:before,\n.tsh-tabs button:focus:before,\n.tsh-tabs button:active:before,\n.tsh-tabs button.active:before{background:transparent!important;content:none!important}\n.tsh-section{padding:20px 8px 10px}.tsh-section-head h3,.tsh-forecast-head h3,.tsh-guide h3{font-size:18px;margin:0 0 4px}.tsh-localtime,.tsh-forecast-head p{color:#8b95a8;font-size:14px;margin:0}.tsh-updated{margin-top:3px;color:#8b95a8;font-size:12px;font-weight:700}.tsh-updated.is-stale{color:#b7791f}.tsh-rating-card{margin-top:20px;background:#fff;border:1px solid #edf0f3;border-radius:8px;padding:16px;box-shadow:0 3px 12px rgba(18,28,45,.05)}.tsh-rating-card>span,.tsh-card>span{font-size:10px;color:#8c98ad;font-weight:800}.tsh-rating-row{display:flex;align-items:center;gap:10px;margin-top:11px}.tsh-rating-row strong{font-size:16px;color:var(--rating-color)}.tsh-rating-bars{display:flex;gap:3px}.tsh-rating-bars i{display:block;width:23px;height:7px;background:#e5e9ef}.tsh-rating-bars i.on{background:var(--rating-color,#f6b900)}.tsh-current-grid{display:grid;grid-template-columns:1fr 1fr 1.55fr 1fr 1fr;gap:8px;margin-top:10px}.tsh-card{min-height:152px;border:1px solid #edf0f3;border-radius:7px;padding:17px 15px;position:relative;box-shadow:0 3px 12px rgba(18,28,45,.05);overflow:hidden}.tsh-card strong{display:block;font-size:25px;margin-top:10px;font-style:normal}.tsh-card strong em{font-size:13px;font-style:normal}.tsh-card p{font-size:12px;font-weight:700;margin:4px 0}.tsh-card small{position:absolute;bottom:13px;color:#9aa5b6;font-size:10px}.tsh-dir-arrow{display:inline-block;font-style:normal;font-size:15px}.tsh-wind-card{padding-right:48%}.tsh-compass{position:absolute;right:10px;top:34px;bottom:10px;width:46%;height:auto;min-height:108px;background:#132d32;border-radius:4px;color:#fff;overflow:hidden}.tsh-mini-map{position:absolute!important;inset:0!important;display:block!important;width:100%!important;max-width:none!important;height:100%!important;min-height:100%!important;object-fit:cover!important;object-position:center center!important;border:0!important;margin:0!important;padding:0!important;filter:saturate(1.03) contrast(1.05);pointer-events:none}.tsh-map-missing{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:10px;text-align:center;background:#132d32;color:#fff;font-size:10px;font-weight:700;z-index:1}.tsh-map-missing[hidden]{display:none!important}.tsh-map-shade{position:absolute;inset:0;background:rgba(8,28,32,.08);pointer-events:none;z-index:2}.tsh-spot-pin{position:absolute;left:50%;top:38%;width:18px;height:18px;border-radius:50% 50% 50% 0;background:#d93025;transform:translate(-50%,-50%) rotate(-45deg);z-index:3;box-shadow:0 1px 4px rgba(0,0,0,.45);pointer-events:none}.tsh-spot-pin i{position:absolute;width:6px;height:6px;border-radius:50%;background:#8c1d16;left:6px;top:6px}.tsh-compass-ring{position:absolute;z-index:4;width:78px;height:78px;border:1px solid rgba(255,255,255,.65);border-radius:50%;left:50%;top:50%;transform:translate(-50%,-50%);box-shadow:0 0 0 1px rgba(9,31,35,.06)}.tsh-compass-arrow{position:absolute;z-index:5;left:50%;top:50%;font-size:35px;transform-origin:50% 50%;margin-left:-17px;margin-top:-19px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.55)}.tsh-compass .n,.tsh-compass .e,.tsh-compass .s,.tsh-compass .w{position:absolute;z-index:5;font-size:9px;font-weight:700;opacity:.95;text-shadow:0 1px 2px rgba(0,0,0,.8)}.tsh-compass .n{left:50%;top:6px;transform:translateX(-50%)}.tsh-compass .s{left:50%;bottom:22px;transform:translateX(-50%)}.tsh-compass .e{right:6px;top:50%;transform:translateY(-50%)}.tsh-compass .w{left:6px;top:50%;transform:translateY(-50%)}.tsh-tide-card canvas{width:100%!important;height:50px!important;margin-top:8px}\n.tsh-tide-times-card{padding:17px 15px!important}\n.tsh-tide-times-card>span{display:block;margin-bottom:9px}\n.tsh-next-tide-row{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:46px}\n.tsh-next-high-row{border-bottom:1px solid #edf0f3}\n.tsh-tide-label{display:flex;align-items:center;gap:7px;min-width:0}\n.tsh-tide-label b{font-size:12px;line-height:1.2;white-space:nowrap}\n.tsh-tide-dot{display:inline-block;width:7px;height:7px;border-radius:50%;flex:0 0 7px}\n.tsh-tide-dot.high{background:#1e9ed1}\n.tsh-tide-dot.low{background:#9aa9bc}\n.tsh-next-tide-row strong{font-size:15px!important;line-height:1!important;margin:0!important;white-space:nowrap}.tsh-forecast-head{display:flex;justify-content:space-between;align-items:center;padding:4px 18px 15px}.tsh-view-toggle{display:flex;background:#edf0f5;border-radius:24px;padding:3px}.tsh-view-toggle button{padding:8px 15px;border-radius:18px;font-size:12px;font-weight:700}.tsh-view-toggle button.active{background:#fff;box-shadow:0 1px 4px rgba(0,0,0,.08)}.tsh-day-selector-shell{display:flex;align-items:center;justify-content:center;gap:9px;margin:2px auto 0;max-width:980px;padding:0 4px}\n.tsh-day-selector-viewport{position:relative;overflow:hidden;flex:1;min-width:0;height:112px;background:#fff;border-radius:9px}\n.tsh-days{position:relative;z-index:3;display:flex;height:112px;overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;scrollbar-width:none}\n.tsh-days::-webkit-scrollbar{display:none}\n.tsh-day{position:relative;flex:0 0 82px;height:112px;padding:13px 7px 8px;border:0;border-right:1px solid rgba(229,233,239,.35);cursor:pointer;background:transparent;text-align:center;color:#171717;z-index:3}\n.tsh-day:last-child{border-right:0}\n.tsh-day.inactive{color:#aeb5bf}\n.tsh-day b{font-size:11px;line-height:15px;display:block;white-space:nowrap;font-weight:700}\n.tsh-day strong{font-size:14px;line-height:18px;display:block;margin:5px 0 10px;font-weight:800;white-space:nowrap}\n.tsh-day .arrows{height:18px;display:flex;align-items:center;justify-content:space-around;font-size:13px;white-space:nowrap;color:#171717}\n.tsh-day.inactive .arrows{color:#7f8791}\n.tsh-day .quality{display:flex;gap:4px;position:absolute;left:7px;right:7px;bottom:8px;height:5px}\n.tsh-day .quality i{height:5px;flex:1;border-radius:3px;background:var(--yellow)}\n.tsh-day .quality i.fair{background:#18c987}\n.tsh-day .quality i.poor{background:#ff9e00}\n.tsh-day-range-window{position:absolute;z-index:4;pointer-events:none;left:0;top:0;width:328px;height:112px;border:1.5px solid #171717;border-radius:10px}\n.tsh-day-wave-bg{position:absolute;z-index:1;left:0;right:0;bottom:27px;height:28px;opacity:.75;pointer-events:none;background-repeat:repeat-x;background-size:164px 28px;background-image:url(\"data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' width='164' height='28' viewBox='0 0 164 28'%3E%3Cpath d='M0 21 C14 21 16 9 30 9 C43 9 43 23 57 23 C70 23 72 15 85 15 C100 15 100 6 114 6 C128 6 128 21 143 21 C151 21 156 17 164 17 L164 28 L0 28Z' fill='%23c9e1eb'\/%3E%3C\/svg%3E\")}\n.tsh-day-nav{width:30px;height:30px;border:1px solid #dfe4ea;border-radius:50%;background:#fff;color:#171717;font-size:22px;line-height:25px;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 30px}\n.tsh-day-nav:disabled{opacity:.3;cursor:default}.tsh-dayline{display:grid;grid-template-columns:repeat(10,minmax(110px,1fr));margin-top:26px;border-bottom:1px solid var(--line);overflow-x:auto}.tsh-dayline div{font-weight:700;font-size:13px;padding:12px 18px 16px;border-right:1px solid #c8ccd2}.tsh-chart-block{padding:18px 20px 6px}.tsh-chart-title{font-size:13px;font-weight:800;margin-bottom:8px}.tsh-chart-title small{font-weight:400;color:#8c98ad;margin-left:4px}.tsh-chart-wrap{position:relative;min-width:650px}.tsh-chart-wrap canvas{width:100%!important}.tsh-tide-extremes{display:flex;gap:20px;overflow:auto;font-size:11px;padding:7px 0;color:#445064}.tsh-tide-extremes b{color:#111}.tsh-table-view{padding:20px}.tsh-table-scroll{overflow:auto}.tsh-table-view table{border-collapse:collapse;width:100%;font-size:12px}.tsh-table-view th,.tsh-table-view td{padding:10px;border-bottom:1px solid #e7e9ed;text-align:left}.tsh-error{margin:16px 20px;padding:12px;border-radius:6px;background:#fff1f0;color:#a61b16;font-size:13px;white-space:pre-wrap}.tsh-data-credit{padding:8px 16px 14px;text-align:right;font-size:9px;color:#98a2b3}\n@media(max-width:1000px){.tsh-current-grid{grid-template-columns:repeat(2,1fr)}.tsh-wind-card{grid-column:span 2}.tsh-card:last-child{grid-column:span 2}.tsh-top h2{font-size:24px}}\n@media(max-width:650px){.tsh-top{padding:15px 14px 7px}.tsh-top h2{font-size:22px}.tsh-tabs{gap:26px;padding:0 16px;overflow:auto}.tsh-current-grid{grid-template-columns:1fr}.tsh-wind-card,.tsh-card:last-child{grid-column:auto}.tsh-wind-card{padding-right:44%}.tsh-day-selector-shell{gap:4px}.tsh-day-selector-viewport{height:112px}.tsh-day{flex-basis:80px}.tsh-day-range-window{width:320px}.tsh-day-nav{width:26px;height:26px;flex-basis:26px;font-size:18px}.tsh-dayline{display:flex;overflow:auto}.tsh-dayline div{min-width:150px}.tsh-forecast-head{align-items:flex-start;gap:10px}.tsh-view-toggle{flex:0 0 auto}.tsh-chart-block{overflow:auto;padding-left:10px;padding-right:10px}}\n\n\/* === Detailed surf chart (v2.5.4) === *\/\n.tsh-pro-chart{margin:18px 0 28px;border:0;border-radius:10px;background:#fff;overflow:hidden}\n.tsh-pro-chart-head{display:flex;align-items:center;justify-content:center;padding:15px 18px 13px;border-bottom:1px solid #e7e9ee}\n.tsh-pro-chart-title,.tsh-pro-chart-updated{display:none!important}\n.tsh-pro-chart-toggles{display:flex;align-items:center;justify-content:center;gap:24px;flex-wrap:wrap;margin:0 auto;width:100%}\n.tsh-pro-toggle{display:inline-flex;align-items:center;gap:7px;color:#8c8ca9;font-size:10px;font-weight:700;letter-spacing:.02em;cursor:pointer;user-select:none}\n.tsh-pro-toggle input{position:absolute;opacity:0;pointer-events:none}\n.tsh-pro-switch{width:42px;height:24px;border-radius:15px;background:#dce3ed;position:relative;transition:.2s}\n.tsh-pro-switch:after{content:\"\";position:absolute;width:18px;height:18px;border-radius:50%;left:3px;top:3px;background:#fff;box-shadow:0 1px 4px rgba(0,0,0,.25);transition:.2s}\n.tsh-pro-toggle input:checked + .tsh-pro-switch{background:#5c86df}\n.tsh-pro-toggle input:checked + .tsh-pro-switch:after{transform:translateX(18px)}\n.tsh-pro-chart-scroll{overflow-x:auto;overflow-y:hidden;background:#fff;scrollbar-width:thin;overscroll-behavior-x:contain}\n@media(min-width:900px){.tsh-pro-chart-scroll{overflow-x:hidden;scrollbar-width:none}.tsh-pro-chart-scroll::-webkit-scrollbar{display:none}}\n.tsh-pro-chart-inner{min-width:max-content;color:#12203c}\n.tsh-pro-grid{display:grid;align-items:stretch;min-width:max-content}\n.tsh-pro-label{position:sticky;left:0;z-index:8;background:rgba(255,255,255,.97);display:flex;align-items:center;justify-content:flex-end;text-align:right;padding:0 8px;border-right:0;color:#233151;font-size:10px;line-height:1.08;font-weight:700;white-space:nowrap}\n.tsh-pro-label small{font-size:8px;color:#3977c6;margin-left:2px}\n.tsh-pro-day-row{height:34px;border-bottom:1px solid #e5e8ee}\n.tsh-pro-day-spacer{position:sticky;left:0;z-index:9;background:#fff;border-right:0}\n.tsh-pro-day{display:flex;align-items:center;justify-content:center;border-right:1px solid #c6cbd3;font-size:10px;font-weight:700;text-transform:uppercase;background:#fff}\n.tsh-pro-time-row{height:40px;border-bottom:1px solid #edf0f4}\n.tsh-pro-time-cell{display:flex;position:relative;align-items:center;justify-content:center;border-right:1px solid #eef0f4;font-size:10px;color:#263452;background:#fff}\n.tsh-sunmark{display:none;position:absolute;top:1px;left:50%;transform:translateX(-50%);font-size:9px}\n.tsh-show-sun .tsh-sunmark{display:block}\n.tsh-pro-tide-wrap{height:118px;border-bottom:0}\n.tsh-pro-tide-stage{position:relative;height:118px;overflow:hidden;background:#fff}\n.tsh-pro-tide-stage:before{content:none!important;display:none!important}\n.tsh-pro-tide-svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}\n.tsh-pro-tide-fill{fill:rgba(255,255,255,.96)}\n.tsh-pro-tide-line{fill:none;stroke:#0a84ff;stroke-width:2.4}\n.tsh-pro-tide-dot{fill:#fff;stroke:#0a84ff;stroke-width:2.2}\n.tsh-pro-tide-text{fill:#111827;font-size:9px;font-weight:800;text-anchor:middle;paint-order:normal;stroke:none}\n.tsh-pro-tide-height{display:none}\n.tsh-hide-tides .tsh-pro-tide-wrap{display:none}\n.tsh-pro-wave-row{height:160px;border-bottom:1px solid #e5e8ee}\n.tsh-pro-wave-cell{position:relative;border-right:1px solid #eef0f4;display:flex;align-items:flex-end;justify-content:center;background:#fff}\n.tsh-pro-wave-num{position:absolute;bottom:calc(var(--bar-h) + 6px);left:0;right:0;text-align:center;font-size:9px;font-weight:800;color:#07111f}\n.tsh-pro-wave-bar{width:calc(100% - 2px);height:var(--bar-h);min-height:8px;background:#5e88df;border-radius:4px 4px 0 0}\n.tsh-pro-value-row{min-height:31px;border-bottom:1px solid #eef0f4}\n.tsh-pro-value-cell{display:flex;align-items:center;justify-content:center;border-right:1px solid #eef0f4;font-size:9px;font-weight:800;color:#111827;background:#fff}\n.tsh-pro-period-cell{background:#5e88df;color:#fff}\n.tsh-pro-dir-cell.wave{background:#5e88df;color:#fff}\n.tsh-pro-arrow{display:inline-block;font-size:18px;line-height:1;transform-origin:50% 50%}\n.tsh-pro-wind-speed{font-size:10px}\n.tsh-pro-gust{color:#7f8aa1}\n.tsh-pro-temp-cell{border-radius:3px;margin:2px 1px;color:#0d1320}\n.tsh-pro-cloud-cell{border-radius:3px;margin:2px 1px}\n.tsh-pro-rain-cell{color:#0b77c7}\n.tsh-hide-period .tsh-pro-period-row{display:none}\n.tsh-pro-chart-note{padding:8px 12px;color:#8b96a9;font-size:9px;border-top:1px solid #edf0f4}\n.tsh-pro-chart + .tsh-multiday-forecast{border-top:0;padding-top:10px}\n\/* v2.5.4: remove the remaining seams highlighted around the chart *\/\n.tsh-charts{border-top:0!important}\n.tsh-pro-chart,.tsh-pro-chart-scroll,.tsh-pro-chart-inner,.tsh-pro-grid,.tsh-pro-day-spacer,.tsh-pro-label,.tsh-pro-tide-wrap,.tsh-pro-wave-row{border-left:0!important;border-top:0!important;outline:0!important;box-shadow:none!important}\n.tsh-pro-grid>:nth-child(2){border-left:0!important}\n.tsh-pro-tide-stage{border:0!important;outline:0!important;box-shadow:none!important;margin-bottom:-1px}\n.tsh-pro-wave-row{border-top:0!important;margin-top:-1px}\n.tsh-pro-tide-wrap + .tsh-pro-wave-row{border-top:0!important}\n\/* v2.5.5: remove all vertical column divider lines from the detailed chart *\/\n.tsh-pro-day,.tsh-pro-time-cell,.tsh-pro-wave-cell,.tsh-pro-value-cell{border-right:0!important;border-left:0!important}\n.tsh-pro-grid>*{border-left:0!important}\n\n\/* v2.5.6: Surfline-style hover cursor for the detailed forecast chart *\/\n.tsh-pro-chart-inner{position:relative}\n.tsh-pro-hover-line{position:absolute;top:34px;bottom:0;width:1px;background:#263247;z-index:30;pointer-events:none;opacity:0;transition:opacity .08s linear;transform:translateX(-.5px);box-shadow:0 0 0 1px rgba(255,255,255,.12)}\n.tsh-pro-hover-line.is-visible{opacity:.82}\n.tsh-pro-hover-line:before{content:\"\";position:absolute;top:39px;left:50%;width:10px;height:10px;border:3px solid #263247;background:#fff;border-radius:50%;transform:translate(-50%,-50%);box-shadow:0 1px 3px rgba(15,23,42,.18)}\n.tsh-pro-hover-tip{position:absolute;top:36px;z-index:31;min-width:250px;max-width:300px;padding:12px 14px;border:1px solid #d9dee8;border-radius:10px;background:#fff;color:#111827;box-shadow:0 10px 28px rgba(15,23,42,.18);font-size:11px;line-height:1.45;pointer-events:none;opacity:0;transform:translateX(-50%);transition:opacity .08s linear;white-space:normal}\n.tsh-pro-hover-tip.is-visible{opacity:1}\n.tsh-pro-hover-tip b{display:block;font-size:13px;margin-bottom:5px;color:#111827}\n.tsh-pro-hover-tip span{display:block;color:#111827}\n.tsh-pro-hover-col{background-color:rgba(37,99,235,.055)!important}\n.tsh-pro-time-cell.tsh-pro-hover-col{background-color:rgba(37,99,235,.09)!important;font-weight:800}\n@media(max-width:650px){\n .tsh-pro-chart{margin-left:-4px;margin-right:-4px;border-radius:7px}\n .tsh-pro-chart-head{padding:12px 12px 10px}\n .tsh-pro-chart-toggles{justify-content:center;margin:0 auto;gap:12px;width:100%}\n .tsh-pro-switch{width:36px;height:21px}\n .tsh-pro-switch:after{width:15px;height:15px}\n .tsh-pro-toggle input:checked + .tsh-pro-switch:after{transform:translateX(15px)}\n}\n<\/style>\n\n<style>\n\/* v1.3 Surfline-style forecast table *\/\n.tsh-charts{padding:20px 18px 30px!important;border-top:1px solid #e5e9ef}\n.tsh-table-day-heading{margin:0 0 18px}\n.tsh-table-day-heading{margin:0 0 18px!important}\n.tsh-day-toggle,\n.tsh-day-toggle:hover,\n.tsh-day-toggle:focus,\n.tsh-day-toggle:focus-visible,\n.tsh-day-toggle:active{\n    display:inline-flex!important;\n    align-items:center!important;\n    gap:7px!important;\n    border:0!important;\n    background:#fff!important;\n    background-color:#fff!important;\n    color:#111!important;\n    padding:0!important;\n    margin:0!important;\n    font:inherit!important;\n    font-size:13px!important;\n    font-weight:800!important;\n    line-height:18px!important;\n    border-radius:0!important;\n    box-shadow:none!important;\n    outline:none!important;\n    text-shadow:none!important;\n    cursor:default!important;\n}\n.tsh-day-toggle:before,\n.tsh-day-toggle:after,\n.tsh-day-toggle:hover:before,\n.tsh-day-toggle:hover:after,\n.tsh-day-toggle:focus:before,\n.tsh-day-toggle:focus:after,\n.tsh-day-toggle:active:before,\n.tsh-day-toggle:active:after{\n    content:none!important;\n    background:transparent!important;\n}\n\n.tsh-forecast-table-wrap.is-collapsed tbody tr{display:none}\n.tsh-forecast-table-wrap.is-collapsed tbody tr:nth-child(3),\n.tsh-forecast-table-wrap.is-collapsed tbody tr:nth-child(5),\n.tsh-forecast-table-wrap.is-collapsed tbody tr:nth-child(7){display:table-row}\n.tsh-day-toggle{cursor:pointer!important}\n.tsh-day-toggle{\n    cursor:pointer!important;\n    user-select:none!important;\n    -webkit-user-select:none!important;\n    pointer-events:auto!important;\n}\n.tsh-day-toggle [data-table-day-title],\n.tsh-day-toggle .tsh-chevron{\n    pointer-events:none!important;\n}\n.tsh-day-toggle [data-table-day-title]{\n    cursor:pointer!important;\n}\n\n.tsh-day-toggle .tsh-chevron{\n    display:inline-block!important;\n    width:0!important;\n    height:0!important;\n    margin-left:6px!important;\n    border-top:6px solid transparent!important;\n    border-bottom:6px solid transparent!important;\n    border-right:11px solid #111!important;\n    border-left:0!important;\n    background:transparent!important;\n    transform:rotate(90deg)!important;\n    transform-origin:center!important;\n    transition:transform .15s ease!important;\n    flex:0 0 auto!important;\n}\n.tsh-day-toggle .tsh-chevron:before,\n.tsh-day-toggle .tsh-chevron:after{\n    content:none!important;\n    display:none!important;\n}\n\/* Expanded: same solid triangle points down *\/\n.tsh-day-toggle.is-expanded .tsh-chevron{\n    transform:rotate(180deg)!important;\n}\n\n\n\n.tsh-day-toggle \n.tsh-day-toggle.is-expanded \n\n\n.tsh-day-toggle{display:flex;align-items:center;gap:7px;border:0;background:none;padding:0;font:inherit;font-size:13px;font-weight:800;color:#171717;cursor:pointer}\n\n\n\n\n\n\n.tsh-forecast-table-wrap{width:100%;overflow-x:auto}\n.tsh-forecast-table{width:100%;min-width:1160px;border-collapse:collapse;table-layout:fixed;font-size:12px;color:#171717}\n.tsh-forecast-table th{height:52px;padding:0 12px;text-align:center;font-size:10px;font-weight:700;border-bottom:1px solid #dfe4e9;white-space:nowrap}\n.tsh-forecast-table th.time-col{width:54px}\n.tsh-forecast-table td{height:48px;padding:5px 8px;text-align:center;border-bottom:1px solid #dfe4e9;border-right:1px solid #e3e7eb;vertical-align:middle}\n.tsh-forecast-table td:last-child,.tsh-forecast-table th:last-child{border-right:0}\n.tsh-forecast-table .time-cell{width:54px;text-align:left;color:#98a2b3;font-weight:700;border-right:0}\n.tsh-forecast-table .time-cell .tsh-now-label{display:block;color:#0c9364;font-weight:900;line-height:1.05;text-transform:uppercase}\n.tsh-forecast-table .time-cell .tsh-now-time{display:block;margin-top:3px;color:#667085;font-size:10px;font-weight:700;white-space:nowrap}\n.tsh-forecast-table tr:has(.tsh-now-label){background:#fbfefd}\n.tsh-surf-cell{position:relative;background:rgba(185,211,223,.11);border-radius:5px;padding:7px 7px!important;font-size:16px;font-weight:800}\n.tsh-surf-cell:before{content:\"\";position:absolute;left:-8px;top:4px;bottom:4px;width:3px;border-radius:2px;background:var(--surf-rating-color,#ffc814)}\n.tsh-swell-cell{display:flex;align-items:center;justify-content:center;gap:9px;white-space:nowrap}\n.tsh-swell-cell b{font-size:14px}.tsh-swell-cell small{font-size:10px;font-weight:700}\n.tsh-table-arrow{display:inline-block;font-size:18px;line-height:1;color:#171717}\n.tsh-wind-table-cell{display:flex;align-items:center;justify-content:center;gap:9px}\n.tsh-wind-speed{display:flex;align-items:flex-start;gap:2px}\n.tsh-wind-speed b{font-size:17px;line-height:18px}.tsh-wind-speed small{font-size:9px;line-height:10px}\n.tsh-wind-arrow-box{width:38px;height:38px;border-radius:4px;background:rgba(185,211,223,.2);display:flex;align-items:center;justify-content:center}\n.tsh-wind-arrow-box span{font-size:21px;display:inline-block}\n.tsh-energy b,.tsh-consistency b,.tsh-weather b,.tsh-pressure b,.tsh-row-tide b{font-size:14px}\n.tsh-energy small,.tsh-consistency small,.tsh-weather small,.tsh-pressure small,.tsh-row-tide small{font-size:9px}\n.tsh-weather{display:flex;align-items:center;justify-content:center;gap:8px;min-width:112px}\n.tsh-weather-icon{font-size:21px;line-height:1;flex:0 0 auto}\n.tsh-weather-copy{display:flex;flex-direction:column;align-items:flex-start;line-height:1.05;min-width:72px}\n.tsh-weather-copy b{font-size:12px!important;font-weight:800;white-space:nowrap}\n.tsh-weather-copy small{margin-top:4px;font-size:10px!important;color:#7b8798;font-weight:700;white-space:nowrap}\n.tsh-row-tide{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;line-height:1.05}\n.tsh-row-tide .tide-type{display:flex;align-items:center;gap:5px;font-weight:800}\n.tsh-row-tide .tide-dot{width:7px;height:7px;border-radius:50%;display:inline-block}\n.tsh-row-tide.high .tide-dot{background:#1e9ed1}\n.tsh-row-tide.low .tide-dot{background:#94a3b8}\n.tsh-row-tide .tide-meta{font-size:9px;color:#778396;white-space:nowrap}\n.tsh-table-tide{padding-top:26px}\n.tsh-modern-tide-card{background:#fff;border:1px solid #e6ebf0;border-radius:18px;padding:20px 20px 14px;box-shadow:0 10px 28px rgba(15,23,42,.06)}\n.tsh-modern-tide-head{display:flex;justify-content:space-between;gap:20px;align-items:flex-start;margin-bottom:15px}\n.tsh-modern-tide-kicker{font-size:9px;letter-spacing:.12em;font-weight:800;color:#93a0b2;margin-bottom:5px}\n.tsh-modern-tide-title-row{display:flex;align-items:baseline;gap:10px}\n.tsh-modern-tide-title-row h4{font-size:18px;line-height:1;margin:0;color:#111827}\n.tsh-modern-tide-title-row h4 span{font-size:12px;color:#8a96a8;font-weight:700}\n.tsh-modern-location{font-size:11px;color:#7b8798}\n.tsh-modern-current{min-width:122px;text-align:right}\n.tsh-modern-current small{display:block;font-size:9px;color:#8b95a8;font-weight:700;text-transform:uppercase;letter-spacing:.04em}\n.tsh-modern-current strong{display:block;font-size:24px;line-height:1.05;margin:2px 0;color:#111827}\n.tsh-modern-current em{font-style:normal;font-size:10px;font-weight:700;color:#16a36a}\n.tsh-modern-tide-wrap{position:relative;overflow:visible}\n.tsh-modern-tide-surface{position:relative;height:150px;border-radius:12px 12px 4px 4px;overflow:visible;background:linear-gradient(180deg,#fbfdff 0%,#ffffff 100%)}\n.tsh-modern-night{position:absolute;top:0;bottom:0;background:rgba(226,232,240,.72);z-index:0;pointer-events:none}\n.tsh-modern-night.night-left{left:0;width:28%}\n.tsh-modern-night.night-right{right:0;width:15%}\n.tsh-tide-svg{position:absolute;inset:0;width:100%;height:150px;overflow:visible;z-index:2}\n.tsh-tide-area{fill:url(#tshTideGradient)}\n.tsh-tide-line-path{fill:none;stroke:#172033;stroke-width:1.7;vector-effect:non-scaling-stroke}\n.tsh-tide-extreme-line{stroke:#64748b;stroke-width:1;opacity:.28;vector-effect:non-scaling-stroke;stroke-dasharray:2 3}\n.tsh-tide-extreme-time{font-size:9px;font-weight:800;fill:#172033;text-anchor:middle}\n.tsh-tide-extreme-height{font-size:9px;font-weight:700;fill:#64748b;text-anchor:middle}\n.tsh-tide-extreme-dot{fill:#fff;stroke:#172033;stroke-width:1.6;vector-effect:non-scaling-stroke}\n.tsh-tide-timeline{display:grid;grid-template-columns:repeat(24,1fr);height:30px}\n.tsh-tide-hour{position:relative;height:30px;border-left:1px solid rgba(100,116,139,.09)}\n.tsh-tide-hour.major:before{content:\"\";position:absolute;left:-1px;top:0;height:6px;border-left:1px solid rgba(71,85,105,.28)}\n.tsh-tide-hour span{position:absolute;top:9px;left:50%;transform:translateX(-50%);font-size:9px;color:#8a96a8;white-space:nowrap}\n.tsh-current-tide-line{position:absolute;top:0;bottom:0;width:1px;background:#111827;z-index:5;pointer-events:none}\n.tsh-current-tide-line:after{content:\"\";position:absolute;width:7px;height:7px;background:#111827;border:2px solid #fff;border-radius:50%;left:50%;top:48%;transform:translate(-50%,-50%);box-shadow:0 0 0 1px rgba(17,24,39,.15)}\n.tsh-current-tide-line span{position:absolute;top:-19px;right:4px;font-size:9px;font-weight:800;color:#111827;white-space:nowrap}\n.tsh-current-tide-tooltip{position:absolute;z-index:6;top:-34px;transform:translateX(-50%);background:#111827;color:#fff;border-radius:8px;padding:5px 8px;font-size:10px;box-shadow:0 8px 18px rgba(15,23,42,.18);white-space:nowrap}\n.tsh-current-tide-tooltip:after{content:\"\";position:absolute;left:50%;bottom:-4px;width:8px;height:8px;background:#111827;transform:translateX(-50%) rotate(45deg)}\n.tsh-current-tide-tooltip strong{font-size:12px}.tsh-current-tide-tooltip span{font-size:9px;margin-left:2px}\n.tsh-modern-tide-footer{display:flex;justify-content:space-between;gap:16px;align-items:center;padding-top:10px;border-top:1px solid #eef2f6}\n.tsh-next-events{display:flex;gap:8px;flex-wrap:wrap}\n.tsh-next-tide-chip{display:flex;align-items:center;gap:7px;padding:7px 10px;background:#f8fafc;border:1px solid #e7edf3;border-radius:999px;font-size:10px;color:#596579}\n.tsh-next-tide-chip b{font-size:10px;color:#111827}\n.tsh-next-tide-chip i{width:7px;height:7px;border-radius:50%;display:inline-block}\n.tsh-next-tide-chip.high i{background:#1e9ed1}.tsh-next-tide-chip.low i{background:#94a3b8}\n.tsh-tide-legend{display:flex;gap:12px;white-space:nowrap;font-size:9px;color:#8a96a8}\n.tsh-tide-legend span{display:flex;align-items:center;gap:5px}\n.tsh-tide-legend i{display:inline-block;width:11px;height:7px;border-radius:3px}\n.legend-night{background:#e2e8f0}.legend-day{background:#fff;border:1px solid #e5e7eb}\n@media(max-width:800px){\n .tsh-modern-tide-card{padding:16px 12px 12px;border-radius:14px}\n .tsh-modern-tide-head{align-items:flex-start}\n .tsh-modern-current{min-width:105px}\n .tsh-modern-current strong{font-size:20px}\n .tsh-modern-tide-wrap{overflow-x:auto}\n .tsh-modern-tide-surface,.tsh-tide-timeline{min-width:720px}\n .tsh-modern-tide-footer{align-items:flex-start;flex-direction:column}\n\n \n .tsh-tide-table-grid{grid-template-columns:1fr}\n .tsh-modern-tide-wrap{min-width:0}\n .tsh-daylight{border-left:0;padding-left:0}\n}\n<\/style>\n\n\n<style>\n\/* v1.7.0 multi-day expandable forecast *\/\n.tsh-multiday-forecast{width:100%}\n.tsh-forecast-day{border-bottom:1px solid #dfe4e9;padding:0 0 10px;margin:0 0 10px}\n.tsh-forecast-day:last-child{margin-bottom:0}\n.tsh-forecast-day .tsh-table-day-heading{margin:0 0 12px!important;padding:0 4px}\n.tsh-forecast-day .tsh-day-toggle,\n.tsh-forecast-day .tsh-day-toggle:hover,\n.tsh-forecast-day .tsh-day-toggle:focus,\n.tsh-forecast-day .tsh-day-toggle:active{\n display:inline-flex!important;align-items:center!important;gap:7px!important;\n padding:0!important;margin:0!important;border:0!important;\n background:#fff!important;background-color:#fff!important;color:#111!important;\n font:inherit!important;font-size:13px!important;line-height:18px!important;font-weight:800!important;\n box-shadow:none!important;outline:none!important;border-radius:0!important;cursor:pointer!important;\n}\n.tsh-forecast-day .tsh-day-toggle span{pointer-events:none}\n.tsh-forecast-day .tsh-chevron{\n display:inline-block!important;width:0!important;height:0!important;margin-left:5px!important;\n border-top:6px solid transparent!important;border-bottom:6px solid transparent!important;\n border-right:10px solid #111!important;border-left:0!important;background:transparent!important;\n transform:rotate(90deg)!important;transform-origin:center!important;transition:transform .15s ease!important;\n}\n.tsh-forecast-day .tsh-day-toggle.is-expanded .tsh-chevron{transform:rotate(180deg)!important}\n.tsh-forecast-day .tsh-chevron:before,.tsh-forecast-day .tsh-chevron:after{content:none!important;display:none!important}\n.tsh-forecast-day .tsh-forecast-table-wrap{width:100%;overflow-x:auto}\n.tsh-forecast-day .tsh-forecast-table-wrap.is-collapsed tbody tr{display:none}\n.tsh-forecast-day .tsh-forecast-table-wrap.is-collapsed tbody tr[data-hour=\"6\"],\n.tsh-forecast-day .tsh-forecast-table-wrap.is-collapsed tbody tr[data-hour=\"12\"],\n.tsh-forecast-day .tsh-forecast-table-wrap.is-collapsed tbody tr[data-hour=\"18\"]{display:table-row}\n.tsh-forecast-day:not(:first-child) .tsh-forecast-table thead{display:none}\n@media(max-width:800px){.tsh-forecast-day{margin-bottom:16px;padding-bottom:15px}}\n\/* v2.6.7: Today stays fully open. Later days show a compact 6 AM \/ Noon \/ 6 PM preview while collapsed. *\/\n\n\n\/* v1.7.1 spacing fix *\/\n.tsh-multiday-forecast{\n    padding-top:28px!important;\n}\n.tsh-forecast-day:first-child .tsh-table-day-heading{\n    margin-top:6px!important;\n}\n@media(max-width:800px){\n    .tsh-multiday-forecast{\n        padding-top:22px!important;\n    }\n}\n\n\n\/* v1.7.2 next-day layout cleanup *\/\n.tsh-forecast-day:not(:first-child) .tsh-forecast-table thead{\n    display:none!important;\n}\n.tsh-forecast-day:not(:first-child) .tsh-forecast-table{\n    margin-top:0!important;\n}\n.tsh-forecast-day:not(:first-child) .tsh-forecast-table-wrap{\n    margin-top:0!important;\n    padding-top:0!important;\n}\n\n\/* Keep next-day date close to its rows, like Surfline *\/\n.tsh-forecast-day:not(:first-child){\n    padding-top:0!important;\n    margin-top:0!important;\n}\n.tsh-forecast-day:not(:first-child) .tsh-table-day-heading{\n    margin:0 0 10px!important;\n    padding:0 4px!important;\n}\n.tsh-forecast-day:not(:first-child) tbody tr:first-child td{\n    border-top:0!important;\n}\n\n\/* Avoid the large blank header-height gap on later days *\/\n.tsh-forecast-day:not(:first-child) .tsh-forecast-table tbody{\n    border-top:0!important;\n}\n\n\n\/* v1.7.3 restore large spacing between forecast days *\/\n.tsh-forecast-day{\n    margin-bottom:32px!important;\n    padding-bottom:24px!important;\n}\n.tsh-forecast-day:not(:first-child){\n    margin-top:28px!important;\n}\n.tsh-forecast-day:not(:first-child) .tsh-table-day-heading{\n    margin-bottom:16px!important;\n}\n\n\n\/* v1.7.4 guaranteed space before Tomorrow and later days *\/\n.tsh-forecast-day:not(:first-child){\n    margin-top:0!important;\n    padding-top:34px!important;\n}\n.tsh-forecast-day:not(:first-child) .tsh-table-day-heading{\n    margin-top:0!important;\n    margin-bottom:16px!important;\n}\n\n\n\/* v1.7.5 guaranteed visible gap between forecast days *\/\n.tsh-day-spacer{\n    display:block!important;\n    width:100%!important;\n    height:40px!important;\n    min-height:40px!important;\n    flex:0 0 40px!important;\n    clear:both!important;\n    background:#fff!important;\n    visibility:visible!important;\n    opacity:1!important;\n}\n.tsh-forecast-day:not(:first-child){\n    padding-top:0!important;\n    margin-top:0!important;\n}\n\n\n<\/style>\n\n<style>\n\/* v1.7.6 definitive day spacing *\/\n.tsh-surf .tsh-forecast-day + .tsh-forecast-day{\n    padding-top:20px!important;\n    margin-top:0!important;\n}\n.tsh-surf .tsh-day-spacer{\n    display:block!important;\n    height:20px!important;\n    min-height:20px!important;\n    width:100%!important;\n    margin:0!important;\n    padding:0!important;\n    background:#fff!important;\n}\n.tsh-surf .tsh-forecast-day:not(:first-child) .tsh-table-day-heading{\n    margin:0 0 16px!important;\n    padding:0 4px!important;\n}\n<\/style>\n\n<style>\n\/* v1.7.8 exact inter-day spacing fix *\/\n.tsh-surf .tsh-forecast-day{\n    margin:0!important;\n    padding:0!important;\n}\n.tsh-surf .tsh-forecast-day + .tsh-forecast-day{\n    margin:0!important;\n    padding:0!important;\n}\n.tsh-surf .tsh-day-spacer{\n    display:block!important;\n    width:100%!important;\n    height:20px!important;\n    min-height:20px!important;\n    margin:0!important;\n    padding:0!important;\n    background:#fff!important;\n}\n.tsh-surf .tsh-forecast-day .tsh-table-day-heading{\n    margin:0 0 10px!important;\n    padding:0 4px!important;\n}\n.tsh-surf .tsh-forecast-day:first-child .tsh-table-day-heading{\n    margin-top:0!important;\n}\n<\/style>\n<script>\n(function(){\nconst REST_BASE = \"https:\\\/\\\/surf-conditions.com\\\/fr\\\/wp-json\\\/bpts-surf\\\/v1\\\/forecast\";\nconst q=(r,s)=>r.querySelector(s), qa=(r,s)=>Array.from(r.querySelectorAll(s));\nconst clamp=(n,a,b)=>Math.max(a,Math.min(b,n));\nconst fmt=(n,d=1)=>Number.isFinite(n)?n.toFixed(d):'\u2014';\nconst degCard=(deg)=>{if(!Number.isFinite(deg))return '\u2014'; const d=['N','NE','E','SE','S','SW','W','NW'];return d[Math.round(((deg%360)+360)%360\/45)%8]};\nconst kmh=ms=>Number.isFinite(ms)?ms*3.6:null;\nlet ACTIVE_TZ='UTC';\nconst localDate=(iso,opt={})=>new Intl.DateTimeFormat('en-GB',{timeZone:ACTIVE_TZ,...opt}).format(new Date(iso));\nconst sameDay=(a,b)=>localDate(a,{year:'numeric',month:'2-digit',day:'2-digit'})===localDate(b,{year:'numeric',month:'2-digit',day:'2-digit'});\nfunction relativeAge(iso){\n const ts=+new Date(iso);\n if(!Number.isFinite(ts))return 'unknown';\n const diff=Math.max(0,Date.now()-ts);\n const mins=Math.floor(diff\/60000);\n if(mins<1)return 'just now';\n if(mins<60)return `${mins} minute${mins===1?'':'s'} ago`;\n const hours=Math.floor(mins\/60);\n if(hours<24)return `${hours} hour${hours===1?'':'s'} ago`;\n const days=Math.floor(hours\/24);\n return `${days} day${days===1?'':'s'} ago`;\n}\nfunction nearest(hours){const now=Date.now();return hours.reduce((best,h)=>Math.abs(new Date(h.time)-now)<Math.abs(new Date(best.time)-now)?h:best,hours[0]);}\nfunction interpolateDirection(a,b,t){\n if(!Number.isFinite(a))return Number.isFinite(b)?b:null;\n if(!Number.isFinite(b))return a;\n const diff=((((b-a)+540)%360)-180);\n return ((a+(diff*t))%360+360)%360;\n}\nfunction currentForecastHour(hours){\n if(!Array.isArray(hours)||!hours.length)return null;\n const sorted=hours.slice().sort((a,b)=>+new Date(a.time)-+new Date(b.time));\n const now=Date.now();\n let before=sorted[0],after=sorted[sorted.length-1];\n if(now<=+new Date(sorted[0].time)){\n   return {...sorted[0],time:new Date(now).toISOString(),_isNow:true};\n }\n if(now>=+new Date(sorted[sorted.length-1].time)){\n   return {...sorted[sorted.length-1],time:new Date(now).toISOString(),_isNow:true};\n }\n for(let i=0;i<sorted.length-1;i++){\n   const a=+new Date(sorted[i].time),b=+new Date(sorted[i+1].time);\n   if(now>=a&&now<=b){before=sorted[i];after=sorted[i+1];break;}\n }\n const ta=+new Date(before.time),tb=+new Date(after.time);\n const t=tb>ta?clamp((now-ta)\/(tb-ta),0,1):0;\n const out={...before,time:new Date(now).toISOString(),_isNow:true};\n const directionKeys=new Set(['waveDirection','swellDirection','secondarySwellDirection','windDirection','oceanCurrentDirection']);\n const steppedKeys=new Set(['weatherCode']);\n const keys=new Set([...Object.keys(before),...Object.keys(after)]);\n keys.forEach(k=>{\n   if(k==='time'||k==='_isNow')return;\n   const a=before[k],b=after[k];\n   if(directionKeys.has(k)){out[k]=interpolateDirection(a,b,t);return;}\n   if(steppedKeys.has(k)){out[k]=t<.5?a:b;return;}\n   if(Number.isFinite(a)&&Number.isFinite(b)){out[k]=a+(b-a)*t;}\n   else if(Number.isFinite(b)&&!Number.isFinite(a)){out[k]=b;}\n });\n return out;\n}\nfunction surfRange(h){if(!Number.isFinite(h))return [null,null];return [Math.max(0,h*.72),h*1.08];}\nfunction bodyScale(m){if(!Number.isFinite(m))return '\u2014';if(m<.35)return 'Ankle to knee';if(m<.65)return 'Knee to thigh';if(m<.95)return 'Waist to chest';if(m<1.35)return 'Chest to head';if(m<1.8)return 'Head high to overhead';return 'Overhead+'}\nfunction angleDiff(a,b){\n if(!Number.isFinite(a)||!Number.isFinite(b))return 180;\n const d=Math.abs((((a-b)+540)%360)-180);\n return d;\n}\nfunction waveSuitability(h,p){\n if(!Number.isFinite(h))return 35;\n const min=Number(p.waveMin??0.3), idealMin=Number(p.waveIdealMin??0.6), idealMax=Number(p.waveIdealMax??1.6), max=Number(p.waveMax??2.5);\n if(h<min)return clamp(10+(h\/Math.max(min,.01))*35,10,45);\n if(h<idealMin)return 50+((h-min)\/Math.max(idealMin-min,.01))*50;\n if(h<=idealMax)return 100;\n if(h<=max)return 100-((h-idealMax)\/Math.max(max-idealMax,.01))*50;\n return clamp(50-((h-max)\/Math.max(max,.01))*35,15,50);\n}\nfunction periodSuitability(period,p){\n if(!Number.isFinite(period))return 45;\n const good=Number(p.periodGood??10), great=Number(p.periodGreat??13);\n if(period<6)return 20;\n if(period<8)return 35+((period-6)\/2)*20;\n if(period<good)return 55+((period-8)\/Math.max(good-8,.1))*20;\n if(period<great)return 75+((period-good)\/Math.max(great-good,.1))*25;\n return 100;\n}\nfunction swellDirectionSuitability(dir,p){\n if(!Number.isFinite(dir))return 45;\n const target=Number(p.swellDir??300), good=Number(p.swellGoodTol??40), okay=Number(p.swellOkayTol??75);\n const diff=angleDiff(dir,target);\n if(diff<=20)return 100;\n if(diff<=good)return 100-((diff-20)\/Math.max(good-20,1))*15;\n if(diff<=okay)return 85-((diff-good)\/Math.max(okay-good,1))*35;\n return clamp(50-(diff-okay)*.45,15,50);\n}\nfunction windSuitability(dir,speedMs,p){\n const speed=kmh(speedMs);\n const wind=Number.isFinite(speed)?speed:18;\n let speedScore=100;\n if(wind<=8)speedScore=100;\n else if(wind<=15)speedScore=100-(wind-8)*1.7;\n else if(wind<=22)speedScore=88-(wind-15)*2.8;\n else if(wind<=30)speedScore=68-(wind-22)*4;\n else speedScore=clamp(36-(wind-30)*2,10,36);\n\n let dirScore=55;\n if(Number.isFinite(dir)){\n   const target=Number(p.offshoreDir??80), good=Number(p.offshoreGoodTol??45), okay=Number(p.offshoreOkayTol??90);\n   const diff=angleDiff(dir,target);\n   if(diff<=good)dirScore=100;\n   else if(diff<=okay)dirScore=100-((diff-good)\/Math.max(okay-good,1))*40;\n   else dirScore=clamp(45-(diff-okay)*.35,15,45);\n }\n\n let score=speedScore*.58+dirScore*.42;\n const outsideOffshore=Number.isFinite(dir)&&angleDiff(dir,Number(p.offshoreDir??80))>Number(p.offshoreOkayTol??90);\n if(outsideOffshore&&wind>10)score=Math.min(score,48);\n if(outsideOffshore&&wind>20)score=Math.min(score,28);\n return clamp(score,10,100);\n}\nfunction rating(h,profile={}){\n const period=Number.isFinite(h.swellPeriod)?h.swellPeriod:h.wavePeriod;\n const swellDir=Number.isFinite(h.swellDirection)?h.swellDirection:h.waveDirection;\n const waveScore=waveSuitability(h.waveHeight,profile);\n const periodScore=periodSuitability(period,profile);\n const swellScore=swellDirectionSuitability(swellDir,profile);\n const windScore=windSuitability(h.windDirection,h.windSpeed,profile);\n const total=waveScore*.35+periodScore*.20+swellScore*.20+windScore*.25;\n let score=1;\n if(total>=82)score=5;\n else if(total>=68)score=4;\n else if(total>=52)score=3;\n else if(total>=35)score=2;\n const labels=['VERY POOR','POOR','POOR TO FAIR','FAIR','FAIR TO GOOD'];\n const colors=['#d84a3a','#f07825','#f2b90b','#22a879','#0c9364'];\n return {score,label:labels[score-1],color:colors[score-1],total:Math.round(total)};\n}\nfunction windQuality(dir,speedMs,profile={}){\n if(!Number.isFinite(dir))return '\u2014';\n const wind=kmh(speedMs)||0;\n const target=Number(profile.offshoreDir??80), good=Number(profile.offshoreGoodTol??45), okay=Number(profile.offshoreOkayTol??90);\n const diff=angleDiff(dir,target);\n if(diff<=good)return wind>25?'Strong offshore':'Offshore';\n if(diff<=okay)return wind>25?'Strong cross-shore':'Cross-offshore';\n return wind<=8?'Light onshore':wind>25?'Strong onshore':'Onshore';\n}\nfunction chooseDaySamples(hours){\n const groups={};\n hours.forEach(h=>{const k=localDate(h.time,{year:'numeric',month:'2-digit',day:'2-digit'});(groups[k]??=[]).push(h)});\n return Object.values(groups).slice(0,10).map(arr=>{\n   const mid=arr.reduce((best,h)=>Math.abs(new Date(h.time).getHours()-12)<Math.abs(new Date(best.time).getHours()-12)?h:best,arr[0]);\n   const vals=arr.map(x=>x.waveHeight).filter(Number.isFinite);\n   return {arr,mid,min:vals.length?Math.min(...vals):null,max:vals.length?Math.max(...vals):null};\n });\n}\nfunction estimateTide(tides, timeMs){\n const pts=tides.filter(t=>Number.isFinite(t.height)).sort((a,b)=>new Date(a.time)-new Date(b.time));\n if(!pts.length)return null;\n let a=pts[0], b=pts[pts.length-1];\n for(let i=0;i<pts.length-1;i++){const ta=+new Date(pts[i].time),tb=+new Date(pts[i+1].time);if(timeMs>=ta&&timeMs<=tb){a=pts[i];b=pts[i+1];break}}\n const ta=+new Date(a.time),tb=+new Date(b.time); if(tb<=ta)return a.height;\n const x=clamp((timeMs-ta)\/(tb-ta),0,1);\n const eased=(1-Math.cos(Math.PI*x))\/2;\n return a.height+(b.height-a.height)*eased;\n}\nfunction setupCanvas(canvas, cssHeight){\n const dpr=window.devicePixelRatio||1, rect=canvas.getBoundingClientRect(), w=Math.max(rect.width,650);\n canvas.width=w*dpr;canvas.height=cssHeight*dpr;canvas.style.height=cssHeight+'px';\n const c=canvas.getContext('2d');c.setTransform(dpr,0,0,dpr,0,0);return {c,w,h:cssHeight};\n}\nfunction drawBars(canvas,data,key,arrowKey){\n const {c,w,h}=setupCanvas(canvas,170);c.clearRect(0,0,w,h);\n const vals=data.map(x=>x[key]).filter(Number.isFinite), max=Math.max(...vals,1), bw=w\/data.length;\n c.fillStyle='#eef1f5';\n data.forEach((x,i)=>{if(new Date(x.time).getHours()<7||new Date(x.time).getHours()>19)c.fillRect(i*bw,0,bw,h-25)});\n c.fillStyle='#b6d9e7';\n data.forEach((x,i)=>{if(!Number.isFinite(x[key]))return;const bh=(x[key]\/max)*(h-55);c.fillRect(i*bw+bw*.16,h-28-bh,bw*.68,bh);\n   if(i%3===0){c.fillStyle='#526072';c.font='10px Arial';c.textAlign='center';c.fillText(localDate(x.time,{hour:'2-digit',hour12:false}),i*bw+bw\/2,h-8);c.fillStyle='#b6d9e7'}\n });\n if(arrowKey){\n  c.fillStyle='#111';c.font='14px Arial';data.forEach((x,i)=>{if(i%3!==0||!Number.isFinite(x[arrowKey]))return;c.save();c.translate(i*bw+bw\/2,18);c.rotate((x[arrowKey]+180)*Math.PI\/180);c.fillText('\u2191',0,0);c.restore()});\n }\n}\nfunction drawLine(canvas,points,mini=false){\n const H=mini?50:175,{c,w,h}=setupCanvas(canvas,H);c.clearRect(0,0,w,h);\n if(points.length<2)return;\n const ys=points.map(p=>p.y).filter(Number.isFinite),min=Math.min(...ys)-.15,max=Math.max(...ys)+.15,span=max-min||1;\n c.fillStyle=mini?'#eef4f7':'#f4f7f9';c.fillRect(0,0,w,h);\n c.beginPath();\n points.forEach((p,i)=>{const x=i\/(points.length-1)*w,y=h-12-((p.y-min)\/span)*(h-28);i?c.lineTo(x,y):c.moveTo(x,y)});\n c.strokeStyle='#20252b';c.lineWidth=1.5;c.stroke();\n if(!mini){c.font='10px Arial';c.fillStyle='#6d788a';c.textAlign='center';points.forEach((p,i)=>{if(i%6===0)c.fillText(localDate(p.t,{hour:'2-digit',hour12:false}),i\/(points.length-1)*w,h-2)})}\n}\nfunction tideSeries(tides,start,end,step=60*60*1000){\n const out=[];for(let t=start;t<=end;t+=step){const y=estimateTide(tides,t);if(Number.isFinite(y))out.push({t:new Date(t).toISOString(),y})}return out;\n}\n\nfunction tableHourLabel(iso){\n const d=new Date(iso);\n const h=Number(new Intl.DateTimeFormat('en-GB',{timeZone:ACTIVE_TZ,hour:'2-digit',hour12:false}).format(d));\n if(h===0)return '12am';\n if(h===12)return 'Noon';\n if(h<12)return h+'am';\n return (h-12)+'pm';\n}\nfunction waveEnergyKJ(h){\n const H=Number.isFinite(h.waveHeight)?h.waveHeight:0;\n const T=Number.isFinite(h.wavePeriod)?h.wavePeriod:(Number.isFinite(h.swellPeriod)?h.swellPeriod:0);\n if(!H||!T)return null;\n return Math.round(10.05*H*H*T);\n}\nfunction consistencyScore(h){\n const p=Number.isFinite(h.swellPeriod)?h.swellPeriod:0;\n const w=Number.isFinite(h.windSpeed)?kmh(h.windSpeed):30;\n const score=Math.round(clamp((p\/14)*70 + (1-clamp(w\/35,0,1))*30,0,100));\n return score;\n}\nfunction weatherCondition(h){\n const code=Number.isFinite(h.weatherCode)?Math.round(h.weatherCode):null;\n const precip=Number.isFinite(h.precipitation)?h.precipitation:0;\n const cloud=Number.isFinite(h.cloudCover)?h.cloudCover:null;\n const hr=Number(new Intl.DateTimeFormat('en-GB',{timeZone:ACTIVE_TZ,hour:'2-digit',hour12:false}).format(new Date(h.time)));\n const isDay=hr>=7&&hr<20;\n\n \/\/ WMO weather interpretation codes returned by Open-Meteo.\n if(code===0) return {icon:isDay?'\u2600\ufe0f':'\ud83c\udf19',label:'Clear'};\n if(code===1) return {icon:isDay?'\ud83c\udf24\ufe0f':'\ud83c\udf19',label:'Mostly clear'};\n if(code===2) return {icon:'\u26c5',label:'Partly cloudy'};\n if(code===3) return {icon:'\u2601\ufe0f',label:'Cloudy'};\n if(code===45||code===48) return {icon:'\ud83c\udf2b\ufe0f',label:'Fog'};\n if([51,53,55,56,57].includes(code)) return {icon:'\ud83c\udf26\ufe0f',label:'Drizzle'};\n if([61,63,65,66,67].includes(code)) return {icon:'\ud83c\udf27\ufe0f',label:'Rain'};\n if([71,73,75,77].includes(code)) return {icon:'\u2744\ufe0f',label:'Snow'};\n if([80,81,82].includes(code)) return {icon:'\ud83c\udf26\ufe0f',label:'Showers'};\n if([85,86].includes(code)) return {icon:'\ud83c\udf28\ufe0f',label:'Snow showers'};\n if([95,96,99].includes(code)) return {icon:'\u26c8\ufe0f',label:'Thunderstorm'};\n\n \/\/ Fallbacks in case the weather code is unavailable.\n if(precip>=0.2) return {icon:'\ud83c\udf27\ufe0f',label:'Rain'};\n if(Number.isFinite(cloud)){\n   if(cloud>=80) return {icon:'\u2601\ufe0f',label:'Cloudy'};\n   if(cloud>=35) return {icon:'\u26c5',label:'Partly cloudy'};\n }\n return {icon:isDay?'\u2600\ufe0f':'\ud83c\udf19',label:isDay?'Sunny':'Clear'};\n}\nfunction weatherIcon(h){ return weatherCondition(h).icon; }\nfunction nearestTideExtreme(tides,timeIso){\n if(!Array.isArray(tides)||!tides.length)return null;\n const target=+new Date(timeIso);\n const sorted=tides\n   .filter(t=>t&&t.time&&Number.isFinite(t.height))\n   .map(t=>({...t,_delta:Math.abs(+new Date(t.time)-target)}))\n   .sort((a,b)=>a._delta-b._delta);\n return sorted[0]||null;\n}\nfunction tideTypeLabel(type){\n const s=String(type||'').toLowerCase();\n if(s.includes('high'))return 'High';\n if(s.includes('low'))return 'Low';\n return s ? s.charAt(0).toUpperCase()+s.slice(1) : '\u2014';\n}\nfunction displayPressure(h,index){\n if(Number.isFinite(h.pressure))return Math.round(h.pressure);\n \/\/ Stable fallback rather than changing on every refresh.\n const seed=(new Date(h.time).getUTCHours()+index)%5;\n return 1011+seed;\n}\nfunction forecastDayKey(iso){\n return localDate(iso,{year:'numeric',month:'2-digit',day:'2-digit'});\n}\n\nfunction tableColumns(){\n return `<thead>\n   <tr>\n     <th class=\"time-col\"><\/th>\n     <th>Surf (m)<\/th>\n     <th>Primary Swell<\/th>\n     <th>Secondary Swell<\/th>\n     <th>Wind<\/th>\n     <th>Wave Energy<\/th>\n     <th>Consistency<\/th>\n     <th>Weather<\/th>\n     <th>Pressure<\/th>\n     <th>Tide<\/th>\n   <\/tr>\n <\/thead>`;\n}\n\nfunction tableRowHtml(h,rowIndex,tides,profile){\n const rr=surfRange(h.waveHeight);\n const r=rating(h,profile);\n const swellArrow=Number.isFinite(h.swellDirection)?`<span class=\"tsh-table-arrow\" style=\"transform:rotate(${h.swellDirection+180}deg)\">\u25b2<\/span>`:'';\n const secondaryArrow=Number.isFinite(h.secondarySwellDirection)?`<span class=\"tsh-table-arrow\" style=\"transform:rotate(${h.secondarySwellDirection+180}deg)\">\u25b2<\/span>`:'';\n const windArrow=Number.isFinite(h.windDirection)?`<span style=\"transform:rotate(${h.windDirection+180}deg)\">\u2191<\/span>`:'\u2014';\n const energy=waveEnergyKJ(h);\n const consistency=consistencyScore(h);\n const hour=Number(new Intl.DateTimeFormat('en-GB',{timeZone:ACTIVE_TZ,hour:'2-digit',hour12:false}).format(new Date(h.time)));\n const t=nearestTideExtreme(tides,h.time);\n const wx=weatherCondition(h);\n\n let tideHtml='<div class=\"tsh-row-tide\"><b>\u2014<\/b><\/div>';\n if(t){\n   const label=tideTypeLabel(t.type);\n   const cls=label==='High'?'high':label==='Low'?'low':'';\n   tideHtml=`<div class=\"tsh-row-tide ${cls}\">\n     <div class=\"tide-type\"><span class=\"tide-dot\"><\/span><b>${label}<\/b><\/div>\n     <div class=\"tide-meta\">${localDate(t.time,{hour:'numeric',minute:'2-digit'})}<\/div>\n   <\/div>`;\n }\n\n return `<tr data-hour=\"${hour}\">\n   <td class=\"time-cell\">${h._isNow?`<span class=\"tsh-now-label\">Now<\/span><small class=\"tsh-now-time\">${localDate(h.time,{hour:'numeric',minute:'2-digit',hour12:true}).replace(\/\\s\/g,'').toLowerCase()}<\/small>`:tableHourLabel(h.time)}<\/td>\n   <td><div class=\"tsh-surf-cell\" style=\"--surf-rating-color:${r.color}\">${Number.isFinite(rr[0])?fmt(rr[0])+'-'+fmt(rr[1]):'\u2014'}<\/div><\/td>\n   <td><div class=\"tsh-swell-cell\"><b>${fmt(h.swellHeight)}<small>m<\/small><\/b><b>${fmt(h.swellPeriod,0)}<small>s<\/small><\/b>${swellArrow}<\/div><\/td>\n   <td><div class=\"tsh-swell-cell\">${Number.isFinite(h.secondarySwellHeight)?`<b>${fmt(h.secondarySwellHeight)}<small>m<\/small><\/b><b>${fmt(h.secondarySwellPeriod,0)}<small>s<\/small><\/b>${secondaryArrow}`:'\u2014'}<\/div><\/td>\n   <td><div class=\"tsh-wind-table-cell\"><div class=\"tsh-wind-speed\"><b>${Number.isFinite(h.windSpeed)?Math.round(kmh(h.windSpeed)):'\u2014'}<\/b><small>${Number.isFinite(h.windGust)?Math.round(kmh(h.windGust))+'<br>kph':'kph'}<\/small><\/div><div class=\"tsh-wind-arrow-box\">${windArrow}<\/div><\/div><\/td>\n   <td><div class=\"tsh-energy\"><b>${Number.isFinite(energy)?energy:'\u2014'}<\/b><small>kJ<\/small><\/div><\/td>\n   <td><div class=\"tsh-consistency\"><b>${Number.isFinite(consistency)?consistency:'\u2014'}<\/b><small>\/100<\/small><\/div><\/td>\n   <td><div class=\"tsh-weather\"><span class=\"tsh-weather-icon\" aria-hidden=\"true\">${wx.icon}<\/span><div class=\"tsh-weather-copy\"><b>${wx.label}<\/b><small>${fmt(h.airTemperature,0)}\u00b0C<\/small><\/div><\/div><\/td>\n   <td><div class=\"tsh-pressure\"><b>${displayPressure(h,rowIndex)}<\/b><small>mb<\/small><\/div><\/td>\n   <td>${tideHtml}<\/td>\n <\/tr>`;\n}\n\n\nfunction chartGridStyle(count,cellW=38,labelW=110){\n return `grid-template-columns:${labelW}px repeat(${count},${cellW}px)`;\n}\n\nfunction chartHour(iso){\n return localDate(iso,{hour:'2-digit',minute:'2-digit'}).replace(':00','h');\n}\n\nfunction chartDateKey(iso){\n return localDate(iso,{year:'numeric',month:'2-digit',day:'2-digit'});\n}\n\nfunction chartDayLabel(iso){\n return localDate(iso,{weekday:'long'}).toUpperCase();\n}\n\nfunction chartTempColor(v){\n if(!Number.isFinite(v)) return '#f3f4f6';\n if(v>=32) return '#ff334d';\n if(v>=28) return '#ff6843';\n if(v>=24) return '#ff9d2d';\n if(v>=20) return '#ffc132';\n if(v>=16) return '#f5d766';\n return '#c8dfee';\n}\n\nfunction chartCloudColor(v){\n if(!Number.isFinite(v)) return '#fff';\n const p=Math.max(0,Math.min(100,v));\n const c=Math.round(245-(p*1.2));\n return `rgb(${c},${c},${c})`;\n}\n\nfunction smoothSvgPath(points){\n if(!points.length) return '';\n if(points.length===1) return `M${points[0][0]},${points[0][1]}`;\n let d=`M${points[0][0]},${points[0][1]}`;\n for(let i=1;i<points.length-1;i++){\n   const mx=(points[i][0]+points[i+1][0])\/2;\n   const my=(points[i][1]+points[i+1][1])\/2;\n   d+=` Q${points[i][0]},${points[i][1]} ${mx},${my}`;\n }\n const last=points[points.length-1];\n d+=` T${last[0]},${last[1]}`;\n return d;\n}\n\nfunction nearestSunEvent(iso,sun){\n if(!Array.isArray(sun)||!sun.length) return '';\n const ts=+new Date(iso);\n let best=null,bestDiff=Infinity,bestType='';\n sun.forEach(d=>{\n   [['sunrise','\u2600'],['sunset','\u25d0']].forEach(pair=>{\n     const ev=d[pair[0]];\n     if(!ev)return;\n     const diff=Math.abs(+new Date(ev)-ts);\n     if(diff<bestDiff){bestDiff=diff;best=ev;bestType=pair[1]}\n   });\n });\n return bestDiff<=60*60*1000 ? bestType : '';\n}\n\nfunction renderDetailedChart(root,hours,tides,sun,data){\n const box=q(root,'[data-pro-chart]');\n if(!box)return;\n\n const now=Date.now();\n let future=hours.filter(h=>+new Date(h.time)>=now-(60*60*1000));\n if(!future.length) future=hours.slice();\n\n const firstFutureTs=+new Date(future[0].time);\n const cutoff65=firstFutureTs+(65*60*60*1000);\n const start=future.filter(h=>+new Date(h.time)<=cutoff65).slice(0,66);\n \/\/ Keep the regular 2-hour columns and include the final hour so the chart reaches the full 65-hour horizon.\n const sampled=start.filter((_,i)=>i%2===0 || i===start.length-1).slice(0,34);\n if(!sampled.length){box.innerHTML='';return}\n\n const count=sampled.length;\n const labelW=110;\n \/\/ On desktop, size every forecast column so the complete 65-hour chart fits\n \/\/ inside the available page width with no horizontal scrollbar. On smaller\n \/\/ screens keep the original 38px columns and allow horizontal scrolling.\n const availableW=Math.max(0,Math.floor(box.getBoundingClientRect().width||0));\n const fitCellW=availableW>labelW?((availableW-labelW)\/count):38;\n const cellW=availableW>=900?Math.max(24,fitCellW):38;\n const plotW=count*cellW;\n const startTs=+new Date(sampled[0].time);\n const endTs=+new Date(sampled[count-1].time);\n const grid=chartGridStyle(count,cellW,labelW);\n\n const groups=[];\n sampled.forEach((h,i)=>{\n   const key=chartDateKey(h.time);\n   let g=groups[groups.length-1];\n   if(!g||g.key!==key){\n     g={key,start:i,count:0,label:chartDayLabel(h.time)};\n     groups.push(g);\n   }\n   g.count++;\n });\n\n const timeCells=sampled.map(h=>{\n   const mark=nearestSunEvent(h.time,sun);\n   return `<div class=\"tsh-pro-time-cell\">${mark?`<span class=\"tsh-sunmark\">${mark}<\/span>`:''}${chartHour(h.time)}<\/div>`;\n }).join('');\n\n const dayCells=groups.map(g=>`<div class=\"tsh-pro-day\" style=\"grid-column:span ${g.count}\">${g.label}<\/div>`).join('');\n\n const levels=sampled.map(h=>Number(h.seaLevel)).filter(Number.isFinite);\n const minLevel=levels.length?Math.min(...levels):0;\n const maxLevel=levels.length?Math.max(...levels):1;\n const levelRange=Math.max(.05,maxLevel-minLevel);\n const pts=sampled.map((h,i)=>{\n   const v=Number.isFinite(h.seaLevel)?h.seaLevel:minLevel;\n   const x=(i\/(Math.max(1,count-1)))*plotW;\n   const y=18+((maxLevel-v)\/levelRange)*70;\n   return [x,y];\n });\n const tideLine=smoothSvgPath(pts);\n const tideFill=tideLine?`${tideLine} L${plotW},118 L0,118 Z`:'';\n\n const visibleTides=(Array.isArray(tides)?tides:[]).filter(t=>{\n   const ts=+new Date(t.time);\n   return ts>=startTs && ts<=endTs;\n }).slice(0,12);\n\n const tideMarkers=visibleTides.map(t=>{\n   const ts=+new Date(t.time);\n   const x=((ts-startTs)\/Math.max(1,endTs-startTs))*plotW;\n   const nearestPoint=pts.reduce((best,p)=>Math.abs(p[0]-x)<Math.abs(best[0]-x)?p:best,pts[0]||[x,55]);\n   const y=nearestPoint[1];\n   const hh=localDate(t.time,{hour:'2-digit',minute:'2-digit'});\n   const tideType=String(t.type||'').toLowerCase().includes('high')?'HIGH':String(t.type||'').toLowerCase().includes('low')?'LOW':'';\n   const tideLabel=tideType?`${tideType} ${hh}`:hh;\n   return `<g>\n     <circle class=\"tsh-pro-tide-dot\" cx=\"${x.toFixed(1)}\" cy=\"${y.toFixed(1)}\" r=\"4\"><\/circle>\n     <text class=\"tsh-pro-tide-text\" x=\"${x.toFixed(1)}\" y=\"${Math.max(11,y-10).toFixed(1)}\">${tideLabel}<\/text>\n   <\/g>`;\n }).join('');\n\n const maxWave=Math.max(1,...sampled.map(h=>Number.isFinite(h.waveHeight)?h.waveHeight:0));\n const waveCells=sampled.map(h=>{\n   const v=Number.isFinite(h.waveHeight)?h.waveHeight:null;\n   const bar=Math.max(6,Math.round(((v||0)\/maxWave)*126));\n   return `<div class=\"tsh-pro-wave-cell\" style=\"--bar-h:${bar}px\">\n     <span class=\"tsh-pro-wave-num\">${v===null?'\u2014':fmt(v)}<\/span>\n     <i class=\"tsh-pro-wave-bar\"><\/i>\n   <\/div>`;\n }).join('');\n\n const periodCells=sampled.map(h=>`<div class=\"tsh-pro-value-cell tsh-pro-period-cell\">${Number.isFinite(h.wavePeriod)?Math.round(h.wavePeriod):'\u2014'}<\/div>`).join('');\n const waveDirCells=sampled.map(h=>`<div class=\"tsh-pro-value-cell tsh-pro-dir-cell wave\"><span class=\"tsh-pro-arrow\" style=\"transform:rotate(${Number.isFinite(h.waveDirection)?h.waveDirection+180:0}deg)\">\u2191<\/span><\/div>`).join('');\n const windDirCells=sampled.map(h=>`<div class=\"tsh-pro-value-cell\"><span class=\"tsh-pro-arrow\" style=\"transform:rotate(${Number.isFinite(h.windDirection)?h.windDirection+180:0}deg)\">\u2191<\/span><\/div>`).join('');\n const windSpeedCells=sampled.map(h=>`<div class=\"tsh-pro-value-cell tsh-pro-wind-speed\">${Number.isFinite(h.windSpeed)?Math.round(kmh(h.windSpeed)):'\u2014'}<\/div>`).join('');\n const gustCells=sampled.map(h=>`<div class=\"tsh-pro-value-cell tsh-pro-gust\">${Number.isFinite(h.windGust)?Math.round(kmh(h.windGust)):'\u2014'}<\/div>`).join('');\n const tempCells=sampled.map(h=>`<div class=\"tsh-pro-value-cell tsh-pro-temp-cell\" style=\"background:${chartTempColor(h.airTemperature)}\">${Number.isFinite(h.airTemperature)?Math.round(h.airTemperature):'\u2014'}<\/div>`).join('');\n const cloudCells=sampled.map(h=>`<div class=\"tsh-pro-value-cell tsh-pro-cloud-cell\" style=\"background:${chartCloudColor(h.cloudCover)}\">${Number.isFinite(h.cloudCover)?Math.round(h.cloudCover):'\u2014'}<\/div>`).join('');\n\n box.innerHTML=`\n   <div class=\"tsh-pro-chart-head\">\n     <div class=\"tsh-pro-chart-toggles\">\n       <label class=\"tsh-pro-toggle\"><input type=\"checkbox\" data-chart-tides checked><span class=\"tsh-pro-switch\"><\/span><span>TIDES<\/span><\/label>\n       <label class=\"tsh-pro-toggle\"><input type=\"checkbox\" data-chart-period checked><span class=\"tsh-pro-switch\"><\/span><span>PERIOD<\/span><\/label>\n       <label class=\"tsh-pro-toggle\"><input type=\"checkbox\" data-chart-sun><span class=\"tsh-pro-switch\"><\/span><span>SUNRISE \/ SUNSET<\/span><\/label>\n     <\/div>\n   <\/div>\n   <div class=\"tsh-pro-chart-scroll\">\n     <div class=\"tsh-pro-chart-inner\">\n       <div class=\"tsh-pro-grid tsh-pro-day-row\" style=\"${grid}\">\n         <div class=\"tsh-pro-day-spacer\"><\/div>${dayCells}\n       <\/div>\n       <div class=\"tsh-pro-grid tsh-pro-time-row\" style=\"${grid}\">\n         <div class=\"tsh-pro-label\"><\/div>${timeCells}\n       <\/div>\n       <div class=\"tsh-pro-grid tsh-pro-tide-wrap\" style=\"grid-template-columns:${labelW}px ${plotW}px\">\n         <div class=\"tsh-pro-label\">TIDES<\/div>\n         <div class=\"tsh-pro-tide-stage\">\n           <svg class=\"tsh-pro-tide-svg\" width=\"${plotW}\" height=\"118\" viewBox=\"0 0 ${plotW} 118\" preserveAspectRatio=\"none\">\n             <path class=\"tsh-pro-tide-fill\" d=\"${tideFill}\"><\/path>\n             <path class=\"tsh-pro-tide-line\" d=\"${tideLine}\"><\/path>\n             ${tideMarkers}\n           <\/svg>\n         <\/div>\n       <\/div>\n       <div class=\"tsh-pro-grid tsh-pro-wave-row\" style=\"${grid}\">\n         <div class=\"tsh-pro-label\">WAVE HEIGHT <small>(M)<\/small><\/div>${waveCells}\n       <\/div>\n       <div class=\"tsh-pro-grid tsh-pro-value-row tsh-pro-period-row\" style=\"${grid}\">\n         <div class=\"tsh-pro-label\">PERIOD<\/div>${periodCells}\n       <\/div>\n       <div class=\"tsh-pro-grid tsh-pro-value-row\" style=\"${grid}\">\n         <div class=\"tsh-pro-label\">WAVE DIRECTION<\/div>${waveDirCells}\n       <\/div>\n       <div class=\"tsh-pro-grid tsh-pro-value-row\" style=\"${grid}\">\n         <div class=\"tsh-pro-label\">WIND DIRECTION<\/div>${windDirCells}\n       <\/div>\n       <div class=\"tsh-pro-grid tsh-pro-value-row\" style=\"${grid}\">\n         <div class=\"tsh-pro-label\">WIND SPEED <small>(KPH)<\/small><\/div>${windSpeedCells}\n       <\/div>\n       <div class=\"tsh-pro-grid tsh-pro-value-row\" style=\"${grid}\">\n         <div class=\"tsh-pro-label\">WIND GUST <small>(KPH)<\/small><\/div>${gustCells}\n       <\/div>\n       <div class=\"tsh-pro-grid tsh-pro-value-row\" style=\"${grid}\">\n         <div class=\"tsh-pro-label\">AIR TEMP <small>(\u00b0C)<\/small><\/div>${tempCells}\n       <\/div>\n       <div class=\"tsh-pro-grid tsh-pro-value-row\" style=\"${grid}\">\n         <div class=\"tsh-pro-label\">CLOUD COVER <small>(%)<\/small><\/div>${cloudCells}\n       <\/div>\n     <\/div>\n   <\/div>`;\n\n const tideToggle=q(box,'[data-chart-tides]');\n const periodToggle=q(box,'[data-chart-period]');\n const sunToggle=q(box,'[data-chart-sun]');\n if(tideToggle)tideToggle.addEventListener('change',()=>box.classList.toggle('tsh-hide-tides',!tideToggle.checked));\n if(periodToggle)periodToggle.addEventListener('change',()=>box.classList.toggle('tsh-hide-period',!periodToggle.checked));\n if(sunToggle)sunToggle.addEventListener('change',()=>box.classList.toggle('tsh-show-sun',sunToggle.checked));\n\n \/\/ Surfline-style hover cursor: snap to the nearest forecast column and show that hour's values.\n const scrollEl=q(box,'.tsh-pro-chart-scroll');\n const innerEl=q(box,'.tsh-pro-chart-inner');\n if(scrollEl&&innerEl){\n   const hoverLine=document.createElement('div');\n   hoverLine.className='tsh-pro-hover-line';\n   const hoverTip=document.createElement('div');\n   hoverTip.className='tsh-pro-hover-tip';\n   innerEl.appendChild(hoverLine);\n   innerEl.appendChild(hoverTip);\n\n   let hoverIndex=-1;\n   const hoverRows=()=>qa(innerEl,'.tsh-pro-time-row,.tsh-pro-wave-row,.tsh-pro-value-row');\n   const clearHoverCells=()=>qa(innerEl,'.tsh-pro-hover-col').forEach(el=>el.classList.remove('tsh-pro-hover-col'));\n\n   const setHoverIndex=(idx)=>{\n     idx=clamp(idx,0,count-1);\n     if(idx===hoverIndex&&hoverLine.classList.contains('is-visible'))return;\n     hoverIndex=idx;\n     const h=sampled[idx];\n     const x=labelW+(idx*cellW)+(cellW\/2);\n     hoverLine.style.left=x+'px';\n\n     const dateLabel=localDate(h.time,{weekday:'short',day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'});\n     const wave=Number.isFinite(h.waveHeight)?fmt(h.waveHeight)+' m':'\u2014';\n     const period=Number.isFinite(h.wavePeriod)?Math.round(h.wavePeriod)+' s':'\u2014';\n     const waveDir=Number.isFinite(h.waveDirection)?degCard(h.waveDirection)+' '+Math.round(h.waveDirection)+'\u00b0':'\u2014';\n     const wind=Number.isFinite(h.windSpeed)?Math.round(kmh(h.windSpeed))+' kph':'\u2014';\n     const windDir=Number.isFinite(h.windDirection)?degCard(h.windDirection):'\u2014';\n     const temp=Number.isFinite(h.airTemperature)?Math.round(h.airTemperature)+'\u00b0C':'\u2014';\n     hoverTip.innerHTML=`<b>${dateLabel}<\/b><span>Wave ${wave} \u00b7 ${period} \u00b7 ${waveDir}<\/span><span>Wind ${wind} ${windDir} \u00b7 Air ${temp}<\/span>`;\n     const minTipX=215;\n     const maxTipX=Math.max(minTipX,(110+plotW)-125);\n     hoverTip.style.left=clamp(x,minTipX,maxTipX)+'px';\n\n     clearHoverCells();\n     hoverRows().forEach(row=>{\n       const cell=row.children[idx+1];\n       if(cell)cell.classList.add('tsh-pro-hover-col');\n     });\n\n     hoverLine.classList.add('is-visible');\n     hoverTip.classList.add('is-visible');\n   };\n\n   const hideHover=()=>{\n     hoverIndex=-1;\n     hoverLine.classList.remove('is-visible');\n     hoverTip.classList.remove('is-visible');\n     clearHoverCells();\n   };\n\n   const moveHover=(ev)=>{\n     const rect=innerEl.getBoundingClientRect();\n     const x=ev.clientX-rect.left;\n     if(x<110||x>(110+plotW)){hideHover();return;}\n     const idx=Math.round((x-110-(cellW\/2))\/cellW);\n     setHoverIndex(idx);\n   };\n\n   innerEl.addEventListener('mousemove',moveHover);\n   innerEl.addEventListener('mouseleave',hideHover);\n   innerEl.addEventListener('pointerdown',ev=>{\n     if(ev.pointerType==='touch'||ev.pointerType==='pen')moveHover(ev);\n   });\n }\n}\n\nfunction renderForecastTable(root,hours,tides,profile){\n const container=q(root,'[data-forecast-days]');\n if(!container)return;\n\n const grouped={};\n hours.forEach(h=>{\n   const key=forecastDayKey(h.time);\n   (grouped[key]??=[]).push(h);\n });\n\n const dayGroups=Object.values(grouped)\n   .map(dayHours=>dayHours.sort((a,b)=>+new Date(a.time)-+new Date(b.time)))\n   .filter(dayHours=>dayHours.length)\n   .slice(0,7);\n\n if(!dayGroups.length)return;\n\n container.innerHTML=dayGroups.map((dayHours,dayIndex)=>{\n   const todayKey=forecastDayKey(new Date().toISOString());\n   const dayKey=forecastDayKey(dayHours[0].time);\n   const isToday=dayKey===todayKey;\n   const nowMs=Date.now();\n\n   let rows;\n   if(isToday){\n     \/\/ The first row always represents the forecast at the visitor's actual current time.\n     \/\/ All completed time slots are removed; only future 3-hour slots remain underneath.\n     const live=currentForecastHour(hours);\n     const futureRows=dayHours.filter(h=>{\n       const ts=+new Date(h.time);\n       const hour=Number(new Intl.DateTimeFormat('en-GB',{timeZone:ACTIVE_TZ,hour:'2-digit',hour12:false}).format(new Date(h.time)));\n       return ts>nowMs && hour%3===0;\n     }).slice(0,7);\n     rows=[...(live?[live]:[]),...futureRows].slice(0,8);\n   }else{\n     rows=dayHours.filter(h=>{\n       const hour=Number(new Intl.DateTimeFormat('en-GB',{timeZone:ACTIVE_TZ,hour:'2-digit',hour12:false}).format(new Date(h.time)));\n       return hour%3===0;\n     }).slice(0,8);\n\n     if(rows.length<3){\n       const step=Math.max(1,Math.floor(dayHours.length\/8));\n       rows=dayHours.filter((_,i)=>i%step===0).slice(0,8);\n     }\n   }\n\n   if(!rows.length)return '';\n   const rowDayKey=forecastDayKey(rows[0].time);\n   const tomorrowDate=new Date();\n   tomorrowDate.setDate(tomorrowDate.getDate()+1);\n   const tomorrowKey=forecastDayKey(tomorrowDate.toISOString());\n\n   const label=rowDayKey===todayKey\n     ? `Today, ${localDate(rows[0].time,{month:'numeric',day:'numeric'})}`\n     : rowDayKey===tomorrowKey\n       ? `Tomorrow, ${localDate(rows[0].time,{month:'numeric',day:'numeric'})}`\n       : `${localDate(rows[0].time,{weekday:'long'})}, ${localDate(rows[0].time,{month:'numeric',day:'numeric'})}`;\n\n   return `<div class=\"tsh-forecast-day\" data-forecast-day=\"${dayIndex}\">\n     ${dayIndex>0 ? '<div class=\"tsh-day-spacer\" aria-hidden=\"true\"><\/div>' : ''}\n     <div class=\"tsh-table-day-heading\">\n       <button type=\"button\" class=\"tsh-day-toggle${isToday?' is-expanded':''}\" data-day-toggle aria-expanded=\"${isToday?'true':'false'}\">\n         <span>${label}<\/span>\n         <span class=\"tsh-chevron\" aria-hidden=\"true\"><\/span>\n       <\/button>\n     <\/div>\n     <div class=\"tsh-forecast-table-wrap${isToday?'':' is-collapsed'}\" data-table-wrap>\n       <table class=\"tsh-forecast-table\">\n         ${dayIndex===0 ? tableColumns() : ''}\n         <tbody>${rows.map((h,i)=>tableRowHtml(h,i,tides,profile)).join('')}<\/tbody>\n       <\/table>\n     <\/div>\n   <\/div>`;\n }).join('');\n\n qa(container,'[data-day-toggle]').forEach(dayToggle=>{\n   const day=dayToggle.closest('[data-forecast-day]');\n   const tableWrap=q(day,'[data-table-wrap]');\n   if(!tableWrap)return;\n\n   const toggleForecastTable=()=>{\n     const expanding=tableWrap.classList.contains('is-collapsed');\n     tableWrap.classList.toggle('is-collapsed');\n     dayToggle.classList.toggle('is-expanded',expanding);\n     dayToggle.setAttribute('aria-expanded',expanding?'true':'false');\n   };\n\n   dayToggle.addEventListener('click',toggleForecastTable);\n   dayToggle.addEventListener('keydown',(e)=>{\n     if(e.key==='Enter'||e.key===' '){\n       e.preventDefault();\n       toggleForecastTable();\n     }\n   });\n });\n}\n\nfunction render(root,data){\n ACTIVE_TZ=data.timezone||'UTC';\n const hours=data.weather||[], tides=data.tides||[], profile=(data.spot&&data.spot.profile)||{}; if(!hours.length)throw new Error('No forecast hours returned.');\n const cur=nearest(hours),rr=surfRange(cur.waveHeight),rt=rating(cur,profile);\n\n \/\/ Keep the title accent, CONDITION RATING text and active bars the same color.\n root.style.setProperty('--rating-color',rt.color);\n\n q(root,'[data-localtime]').textContent=localDate(new Date().toISOString(),{weekday:'short',hour:'2-digit',minute:'2-digit'});\n\n const ratingEl=q(root,'[data-rating]');\n ratingEl.textContent=rt.label;\n ratingEl.style.color=rt.color;\n ratingEl.title=`Model score ${rt.total}\/100`;\n const titleBar=q(root,'[data-titlebar]');\n if(titleBar)titleBar.style.backgroundColor=rt.color;\n\n const bars=q(root,'[data-rating-bars]');\n bars.style.setProperty('--rating-color',rt.color);\n bars.innerHTML='';\n for(let i=1;i<=5;i++){\n   const el=document.createElement('i');\n   if(i<=rt.score){el.className='on';el.style.background=rt.color}\n   else{el.style.background='#e5e9ef'}\n   bars.appendChild(el);\n }\n q(root,'[data-surf-range]').textContent=Number.isFinite(rr[0])?`${fmt(rr[0])}-${fmt(rr[1])}m`:'\u2014';\n q(root,'[data-body-scale]').textContent=bodyScale(cur.waveHeight);\n q(root,'[data-swell-height]').textContent=Number.isFinite(cur.swellHeight)?fmt(cur.swellHeight)+'m':'\u2014';\n q(root,'[data-swell-period]').textContent=Number.isFinite(cur.swellPeriod)?Math.round(cur.swellPeriod)+'s':'\u2014';\n q(root,'[data-swell-dir]').textContent=Number.isFinite(cur.swellDirection)?`${degCard(cur.swellDirection)} ${Math.round(cur.swellDirection)}\u00b0`:'\u2014';\n q(root,'[data-swell-arrow]').style.transform=`rotate(${(cur.swellDirection||0)+180}deg)`;\n q(root,'[data-wind-speed]').textContent=Number.isFinite(cur.windSpeed)?Math.round(kmh(cur.windSpeed))+'kph':'\u2014';\n q(root,'[data-wind-dir]').textContent=degCard(cur.windDirection);\n q(root,'[data-wind-gust]').textContent=Number.isFinite(cur.windGust)?Math.round(kmh(cur.windGust))+'kph':'\u2014';\n q(root,'[data-wind-quality]').textContent=windQuality(cur.windDirection,cur.windSpeed,profile);\n q(root,'[data-wind-arrow]').style.transform=`rotate(${(cur.windDirection||0)+90}deg)`;\n q(root,'[data-water-temp]').textContent=Number.isFinite(cur.waterTemperature)?Math.round(cur.waterTemperature)+'\u00b0c':'\u2014';\n q(root,'[data-air-temp]').textContent=Number.isFinite(cur.airTemperature)?Math.round(cur.airTemperature)+'\u00b0c':'\u2014';\n const wt=cur.waterTemperature;q(root,'[data-wetsuit]').textContent=!Number.isFinite(wt)?'\u2014':wt>=23?'Boardshort \/ light top':wt>=20?'2mm wetsuit':wt>=17?'3\/2mm wetsuit':'4\/3mm wetsuit';\n\n const now=Date.now();\n const upcomingTides=tides\n   .filter(t=>+new Date(t.time)>now)\n   .sort((a,b)=>+new Date(a.time)-+new Date(b.time));\n\n const nextHigh=upcomingTides.find(t=>String(t.type||'').toLowerCase().includes('high'));\n const nextLow=upcomingTides.find(t=>String(t.type||'').toLowerCase().includes('low'));\n\n const highEl=q(root,'[data-next-high-time]');\n const lowEl=q(root,'[data-next-low-time]');\n\n if(highEl){\n   highEl.textContent=nextHigh\n     ? localDate(nextHigh.time,{hour:'numeric',minute:'2-digit'})\n     : '\u2014';\n }\n if(lowEl){\n   lowEl.textContent=nextLow\n     ? localDate(nextLow.time,{hour:'numeric',minute:'2-digit'})\n     : '\u2014';\n }\n\n renderDetailedChart(root,hours,tides,data.sun||[],data);\n renderForecastTable(root,hours,tides,profile);\n\n\n}\nasync function boot(root){\n try{\n\n   const url=new URL(REST_BASE);\n   url.searchParams.set('spot',root.dataset.spot);\n\n   const res=await fetch(url.toString(),{\n     credentials:'same-origin',\n     cache:'no-store'\n   });\n\n   const raw=await res.text();\n   let json={};\n\n   try{\n     json=raw?JSON.parse(raw):{};\n   }catch(parseError){\n     throw new Error(`WordPress REST returned HTTP ${res.status}, but the response was not valid JSON: ${raw.slice(0,300)}`);\n   }\n\n   if(!res.ok){\n     const message=json.message||json.code||'Forecast request failed';\n     throw new Error(`${message} (HTTP ${res.status})`);\n   }\n\n   render(root,json);\n\n   const forecastDays=q(root,'[data-forecast-days]');\n   if(forecastDays && !forecastDays.children.length){\n     throw new Error('Forecast API connected, but no daily forecast tables could be generated.');\n   }\n }catch(e){\n   const er=q(root,'[data-error]');\n   if(er){\n     er.hidden=false;\n     const message=String(e&&e.message?e.message:'Forecast temporarily unavailable');\n     er.textContent=\/quota exceeded|HTTP 402\/i.test(message)\n       ? 'Forecast is temporarily unavailable. The plugin will automatically use the last saved Open-Meteo forecast when possible.'\n       : message;\n   }\n\n }\n qa(root,'.tsh-tabs button').forEach(b=>b.addEventListener('click',()=>{qa(root,'.tsh-tabs button').forEach(x=>x.classList.remove('active'));b.classList.add('active');q(root,`[data-section=\"${b.dataset.scroll}\"]`)?.scrollIntoView({behavior:'smooth',block:'start'})}));\n\n\n}\ndocument.addEventListener('DOMContentLoaded',()=>document.querySelectorAll('.tsh-surf').forEach(boot));\n})();\n<\/script>\n        <\/div>\n            <\/section>\n\n            <section id=\"spot-guide\" class=\"bpts-section\" aria-labelledby=\"bpts-guide-title\"><div class=\"bpts-section-heading\"><p class=\"bpts-kicker\">SURF SPOT GUIDE<\/p><h2 id=\"bpts-guide-title\">About dukhan beach<\/h2><\/div><div class=\"bpts-reading-copy\"><p>This dukhan beach page combines location-based marine and weather forecast data with nearby surf spots and practical session-planning information. Local bathymetry, access and wave shape can vary, so use the forecast as a planning tool and confirm the setup when you arrive.<\/p><\/div><\/section>\n\n            <section class=\"bpts-section bpts-condition-section\" aria-labelledby=\"bpts-condition-title\">\n                <div class=\"bpts-section-heading\"><p class=\"bpts-kicker\">BEST CONDITIONS<\/p><h2 id=\"bpts-condition-title\">When does dukhan beach work best?<\/h2><\/div>\n                <div class=\"bpts-condition-grid\">\n                    <article class=\"bpts-condition-card\"><span class=\"bpts-condition-icon\" aria-hidden=\"true\">\u2198<\/span><h3>Swell<\/h3><strong>Use live swell forecast<\/strong><p>Use swell direction together with period and size to judge how much energy will reach the break.<\/p><\/article>\n                    <article class=\"bpts-condition-card\"><span class=\"bpts-condition-icon\" aria-hidden=\"true\">\u21e2<\/span><h3>Wind<\/h3><strong>Use live wind forecast<\/strong><p>Favourable wind can help keep the wave face cleaner and more organised.<\/p><\/article>\n                    <article class=\"bpts-condition-card\"><span class=\"bpts-condition-icon\" aria-hidden=\"true\">\u2248<\/span><h3>Tide<\/h3><strong>Check the local tide window<\/strong><p>Tide interacts with swell size and the bottom shape, so compare the live curve with the ocean.<\/p><\/article>\n                    <article class=\"bpts-condition-card\"><span class=\"bpts-condition-icon\" aria-hidden=\"true\">\u25f7<\/span><h3>Season<\/h3><strong>Varies with regional swell season<\/strong><p>Season tells you when the regional swell pattern is most likely to favour this spot.<\/p><\/article>\n                <\/div>\n                <div class=\"bpts-reading-copy bpts-copy-after-cards\"><p>For dukhan beach, compare live wave height, swell direction and period with wind and tide. The most useful window depends on the local coastline and seabed, so avoid assuming a single swell, wind or tide works every day.<\/p><\/div>\n            <\/section>\n\n            <section class=\"bpts-section bpts-two-column\" aria-label=\"Skill level and surf season\">\n                <article class=\"bpts-info-panel\"><p class=\"bpts-kicker\">SKILL LEVEL<\/p><h2>Who should surf dukhan beach?<\/h2><p>Choose a session that matches your experience and the conditions shown in the live forecast. If you do not know dukhan beach, watch the lineup first and ask a local surf school, lifeguard or experienced surfer about access, currents and hazards.<\/p><\/article>\n                <article class=\"bpts-info-panel\"><p class=\"bpts-kicker\">SURF SEASON<\/p><h2>Best time of year<\/h2><p>Surf seasonality around Qatar follows regional storm tracks, swell exposure and local wind patterns. Use the live forecast throughout the year and compare nearby spots when this location is too small, too windy or too exposed.<\/p><\/article>\n            <\/section>\n\n            <section class=\"bpts-section bpts-location-section\" aria-labelledby=\"bpts-location-title\">\n                <div class=\"bpts-section-heading\"><p class=\"bpts-kicker\">LOCATION<\/p><h2 id=\"bpts-location-title\">Where is dukhan beach?<\/h2><\/div>\n                <div class=\"bpts-location-card\"><div><p>dukhan beach is located near Qatar at approximately 25.4051, 50.7555.<\/p><dl class=\"bpts-location-data\"><div><dt>Latitude<\/dt><dd>25.4051<\/dd><\/div><div><dt>Longitude<\/dt><dd>50.7555<\/dd><\/div><\/dl><\/div><a class=\"bpts-map-btn\" href=\"https:\/\/www.google.com\/maps\/search\/?api=1&#038;query=25.405136%2C50.755463\" target=\"_blank\" rel=\"noopener noreferrer\">Open location in Google Maps \u2197<\/a><\/div>\n            <\/section>\n\n            <section class=\"bpts-section bpts-safety-section\" aria-labelledby=\"bpts-safety-title\"><div class=\"bpts-safety-symbol\" aria-hidden=\"true\">!<\/div><div><p class=\"bpts-kicker\">SAFETY &amp; LOCAL ETIQUETTE<\/p><h2 id=\"bpts-safety-title\">What to know before paddling out<\/h2><p>Before surfing dukhan beach, check access, currents, rocks or reef, water quality, local rules and the experience level required. Conditions can change quickly; compare the forecast with the ocean in front of you and stay within your ability.<\/p><\/div><\/section>\n\n                        <section class=\"bpts-section\" aria-labelledby=\"bpts-nearby-title\">\n                <div class=\"bpts-section-heading\"><p class=\"bpts-kicker\">NEARBY SURF<\/p><h2 id=\"bpts-nearby-title\">Other surf spots near dukhan beach<\/h2><\/div>\n                <div class=\"bpts-nearby-grid\">\n                                            <a class=\"bpts-nearby-card\" href=\"https:\/\/surf-conditions.com\/fr\/jebal-ali-lefts\/\">\n                            <span class=\"bpts-nearby-label\">Nearby spot<\/span><div class=\"bpts-nearby-card-row\"><strong class=\"bpts-nearby-name\">Jebal Ali Lefts<\/strong><div class=\"bpts-nearby-wave\" aria-label=\"Current wave height 0.2\u20130.4m\"><span class=\"bpts-wave-meter bpts-wave-level-1\" aria-hidden=\"true\"><i><\/i><i><\/i><i><\/i><\/span><strong>0.2\u20130.4m<\/strong><\/div><\/div>\n                        <\/a>\n                                            <a class=\"bpts-nearby-card\" href=\"https:\/\/surf-conditions.com\/fr\/dubai-wave-pool\/\">\n                            <span class=\"bpts-nearby-label\">Nearby spot<\/span><div class=\"bpts-nearby-card-row\"><strong class=\"bpts-nearby-name\">Dubai Wave Pool<\/strong><div class=\"bpts-nearby-wave\" aria-label=\"Current wave height 0.2\u20130.3m\"><span class=\"bpts-wave-meter bpts-wave-level-1\" aria-hidden=\"true\"><i><\/i><i><\/i><i><\/i><\/span><strong>0.2\u20130.3m<\/strong><\/div><\/div>\n                        <\/a>\n                                            <a class=\"bpts-nearby-card\" href=\"https:\/\/surf-conditions.com\/fr\/mangaf\/\">\n                            <span class=\"bpts-nearby-label\">Nearby spot<\/span><div class=\"bpts-nearby-card-row\"><strong class=\"bpts-nearby-name\">mangaf<\/strong><div class=\"bpts-nearby-wave\" aria-label=\"Current wave height 0.1\u20130.2m\"><span class=\"bpts-wave-meter bpts-wave-level-1\" aria-hidden=\"true\"><i><\/i><i><\/i><i><\/i><\/span><strong>0.1\u20130.2m<\/strong><\/div><\/div>\n                        <\/a>\n                                            <a class=\"bpts-nearby-card\" href=\"https:\/\/surf-conditions.com\/fr\/fujairah-port\/\">\n                            <span class=\"bpts-nearby-label\">Nearby spot<\/span><div class=\"bpts-nearby-card-row\"><strong class=\"bpts-nearby-name\">Fujairah Port<\/strong><div class=\"bpts-nearby-wave\" aria-label=\"Current wave height 0.4\u20130.5m\"><span class=\"bpts-wave-meter bpts-wave-level-2\" aria-hidden=\"true\"><i><\/i><i><\/i><i><\/i><\/span><strong>0.4\u20130.5m<\/strong><\/div><\/div>\n                        <\/a>\n                                            <a class=\"bpts-nearby-card\" href=\"https:\/\/surf-conditions.com\/fr\/bandar-al-saqlah\/\">\n                            <span class=\"bpts-nearby-label\">Nearby spot<\/span><div class=\"bpts-nearby-card-row\"><strong class=\"bpts-nearby-name\">Bandar al Saqlah<\/strong><div class=\"bpts-nearby-wave\" aria-label=\"Current wave height 1.5\u20132.2m\"><span class=\"bpts-wave-meter bpts-wave-level-3\" aria-hidden=\"true\"><i><\/i><i><\/i><i><\/i><\/span><strong>1.5\u20132.2m<\/strong><\/div><\/div>\n                        <\/a>\n                                    <\/div>\n            <\/section>\n            \n            <section class=\"bpts-section bpts-faq-section\" aria-labelledby=\"bpts-faq-title\"><div class=\"bpts-section-heading\"><p class=\"bpts-kicker\">DUKHAN BEACH FAQ<\/p><h2 id=\"bpts-faq-title\">Frequently asked questions<\/h2><\/div><div class=\"bpts-faq-list\"><details class=\"bpts-faq-item\" open><summary>How do I check the surf forecast for dukhan beach?<span aria-hidden=\"true\">+<\/span><\/summary><div class=\"bpts-faq-answer\"><p>Use the live forecast on this page to compare current and upcoming wave height, swell, wind, tide and weather for the coordinates listed for dukhan beach.<\/p><\/div><\/details><details class=\"bpts-faq-item\" ><summary>Where is dukhan beach?<span aria-hidden=\"true\">+<\/span><\/summary><div class=\"bpts-faq-answer\"><p>dukhan beach is located near Qatar at approximately 25.4051, 50.7555.<\/p><\/div><\/details><details class=\"bpts-faq-item\" ><summary>What surfing level is dukhan beach for?<span aria-hidden=\"true\">+<\/span><\/summary><div class=\"bpts-faq-answer\"><p>This location has not yet been given a curated difficulty rating. Choose conditions that match your ability and check local advice before entering the water.<\/p><\/div><\/details><details class=\"bpts-faq-item\" ><summary>When should I surf dukhan beach?<span aria-hidden=\"true\">+<\/span><\/summary><div class=\"bpts-faq-answer\"><p>The best session depends on the live combination of swell, wind and tide. Regional surf seasonality is described on this page without assuming one fixed best month for every year.<\/p><\/div><\/details><details class=\"bpts-faq-item\" ><summary>What should I check before surfing dukhan beach?<span aria-hidden=\"true\">+<\/span><\/summary><div class=\"bpts-faq-answer\"><p>Before surfing dukhan beach, check access, currents, rocks or reef, water quality, local rules and the experience level required. Conditions can change quickly; compare the forecast with the ocean in front of you and stay within your ability.<\/p><\/div><\/details><\/div><\/section>\n\n            <section class=\"bpts-section bpts-final-cta\" aria-labelledby=\"bpts-final-title\"><div><p class=\"bpts-kicker\">CHECK BEFORE YOU GO<\/p><h2 id=\"bpts-final-title\">Plan your next dukhan beach session<\/h2><p>Conditions change quickly. Recheck swell, wind and tide shortly before you surf and compare the forecast with the ocean in front of you.<\/p><\/div><a class=\"bpts-primary-btn\" href=\"#live-forecast\">Back to live forecast \u2191<\/a><\/section>\n        <\/main>\n        \n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-792","page","type-page","status-publish"],"_hostinger_reach_plugin_has_subscription_block":false,"_hostinger_reach_plugin_is_elementor":false,"_links":{"self":[{"href":"https:\/\/surf-conditions.com\/fr\/wp-json\/wp\/v2\/pages\/792","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/surf-conditions.com\/fr\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/surf-conditions.com\/fr\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/surf-conditions.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/surf-conditions.com\/fr\/wp-json\/wp\/v2\/comments?post=792"}],"version-history":[{"count":1,"href":"https:\/\/surf-conditions.com\/fr\/wp-json\/wp\/v2\/pages\/792\/revisions"}],"predecessor-version":[{"id":1170,"href":"https:\/\/surf-conditions.com\/fr\/wp-json\/wp\/v2\/pages\/792\/revisions\/1170"}],"wp:attachment":[{"href":"https:\/\/surf-conditions.com\/fr\/wp-json\/wp\/v2\/media?parent=792"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}