Files
boc/intelligence/competitor-watch/data/signals/2026-07-11/cyclomedia.txt
T
Bernt 1d80f13ce5 feat(intelligence): add competitor watch system
- Weekly automated scraping of 10 competitors
- Structured data storage (competitors.json)
- Validation and alerting scripts
- First weekly report (2026-07-11) with 4 critical findings:
  1. Premise Data acquired by Culmen (Aug 2025)
  2. Streetbees bankruptcy (Aug 2025)
  3. Moody's/Cape Analytics integration progressing
  4. Sweden Platform Work Act proposed (Dec 2026)
- Cron scheduled for Saturdays 07:00 UTC
2026-07-11 08:33:06 +00:00

1211 lines
40 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Cyclomedia | Trusted Data. Powerful Insights.!function(o,c){var n=c.documentElement,t=" w-mod-";n.className+=t+"js",("ontouchstart"in o||o.DocumentTouch&&c instanceof DocumentTouch)&&(n.className+=t+"touch")}(window,document);
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://cyclomedia.com/#organization",
"name": "Cyclomedia",
"url": "https://cyclomedia.com/",
"logo": "https://cdn.prod.website-files.com/69bd3518dd960ca00ae76dcc/69e1e2133ab75490c11f0f4e_logo%20dark2.svg",
"description": "Cyclomedia is a global leader in geospatial technology, transforming how organisations capture, visualise, and analyse the built environment. With decades of experience in street-level imagery and LiDAR data capture, Cyclomedia supports public authorities, infrastructure owners, and enterprises with a clear and consistent digital view of the outdoor environment.",
"inLanguage": "en",
"address": {
"@type": "PostalAddress",
"streetAddress": "Van Voordenpark 1b",
"addressLocality": "Zaltbommel",
"postalCode": "5301 KP",
"addressCountry": "NL"
},
"telephone": "+31-418-556100",
"email": "info@cyclomedia.com",
"sameAs": [
"https://cyclomedia.com/en-us/",
"https://cyclomedia.com/nl-nl/",
"https://cyclomedia.com/de-de/",
"https://cyclomedia.com/fr-fr/",
"https://cyclomedia.com/pl-pl/",
"https://www.linkedin.com/company/cyclomedia",
"https://www.youtube.com/@cyclomedia"
]
}
@media screen and (min-width: 992px) {
.home-benefit_list.swiper-wrapper {
flex-flow: row!important;
max-height:43.75rem!important;
}
.sticky-child {
height: 100vh!important;
}
.sticky-child .swiper {
height: 100%!important;
max-height: calc(100vh - 10rem)!important;
}
}
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-K2VSP89');
.loader-overlay {
display: flex;
}
.section.is-home-ind {
overflow: clip;
}
:root {
--font-from: 16;
--font-to: 18;
--vw-from: calc(1920 / 100);
--vw-to: calc(2400 / 100);
--coefficient: calc((var(--font-to) - var(--font-from)) / (var(--vw-to) - var(--vw-from)));
--base: calc((var(--font-from) - var(--vw-from) * var(--coefficient)) / 16);
}
html {
font-size: calc(var(--base) * 1rem + var(--coefficient) * 1vw);
}
@media screen and (max-width: 1920px) {
:root {
--font-from: 14;
--font-to: 16;
--vw-from: calc(1440 / 100);
--vw-to: calc(1920 / 100);
}
}
@media screen and (max-width: 1440px) {
:root {
--font-from: 12;
--font-to: 14;
--vw-from: calc(992 / 100);
--vw-to: calc(1440 / 100);
}
}
@media screen and (max-width: 992px) {
:root {
--font-from: 14;
--font-to: 16;
--vw-from: calc(479 / 100);
--vw-to: calc(992 / 100);
}
}
@media screen and (max-width: 479px) {
:root {
--font-from: 15;
--font-to: 16;
--vw-from: calc(1 / 100);
--vw-to: calc(479 / 100);
}
}
/* Make text look crisper and more legible in all browsers */
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
/* Focus state style for keyboard navigation for the focusable elements */
*[tabindex]:focus-visible,
input[type="file"]:focus-visible {
outline: 0.125rem solid #4d65ff;
outline-offset: 0.125rem;
}
/* Set color style to inherit */
.inherit-color * {
color: inherit;
}
/* Get rid of top margin on first element in any rich text element */
.w-richtext > :not(div):first-child, .w-richtext > div:first-child > :first-child {
margin-top: 0 !important;
}
/* Get rid of bottom margin on last element in any rich text element */
.w-richtext>:last-child, .w-richtext ol li:last-child, .w-richtext ul li:last-child {
margin-bottom: 0 !important;
}
/* Make sure containers never lose their center alignment */
.container-medium,.container-small, .container-large {
margin-right: auto !important;
margin-left: auto !important;
}
/*
Make the following elements inherit typography styles from the parent and not have hardcoded values.
Important: You will not be able to style for example "All Links" in Designer with this CSS applied.
Uncomment this CSS to use it in the project. Leave this message for future hand-off.
*/
/*
a,
.w-input,
.w-select,
.w-tab-link,
.w-nav-link,
.w-dropdown-btn,
.w-dropdown-toggle,
.w-dropdown-link {
color: inherit;
text-decoration: inherit;
font-size: inherit;
}
*/
/* Apply "..." after 3 lines of text */
.text-style-3lines {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
/* Apply "..." after 2 lines of text */
.text-style-2lines {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
/* These classes are never overwritten */
.hide {
display: none !important;
}
@media screen and (max-width: 991px) {
.hide, .hide-tablet {
display: none !important;
}
}
@media screen and (max-width: 767px) {
.hide-mobile-landscape{
display: none !important;
}
}
@media screen and (max-width: 479px) {
.hide-mobile{
display: none !important;
}
}
.margin-0 {
margin: 0rem !important;
}
.padding-0 {
padding: 0rem !important;
}
.spacing-clean {
padding: 0rem !important;
margin: 0rem !important;
}
.margin-top {
margin-right: 0rem !important;
margin-bottom: 0rem !important;
margin-left: 0rem !important;
}
.padding-top {
padding-right: 0rem !important;
padding-bottom: 0rem !important;
padding-left: 0rem !important;
}
.margin-right {
margin-top: 0rem !important;
margin-bottom: 0rem !important;
margin-left: 0rem !important;
}
.padding-right {
padding-top: 0rem !important;
padding-bottom: 0rem !important;
padding-left: 0rem !important;
}
.margin-bottom {
margin-top: 0rem !important;
margin-right: 0rem !important;
margin-left: 0rem !important;
}
.padding-bottom {
padding-top: 0rem !important;
padding-right: 0rem !important;
padding-left: 0rem !important;
}
.margin-left {
margin-top: 0rem !important;
margin-right: 0rem !important;
margin-bottom: 0rem !important;
}
.padding-left {
padding-top: 0rem !important;
padding-right: 0rem !important;
padding-bottom: 0rem !important;
}
.margin-horizontal {
margin-top: 0rem !important;
margin-bottom: 0rem !important;
}
.padding-horizontal {
padding-top: 0rem !important;
padding-bottom: 0rem !important;
}
.margin-vertical {
margin-right: 0rem !important;
margin-left: 0rem !important;
}
.padding-vertical {
padding-right: 0rem !important;
padding-left: 0rem !important;
}
/* BENEFIT CARD */
@media screen and (max-width: 991px) {
.benefit_grid .benefit_item,
.demo-benefits_grid .benefit_item {
width: 100%;
}
}
@media screen and (min-width: 992px) {
.benefit_grid .benefit_item {
min-height: 32.5rem;
}
}
/* BACKGROUDN COLOR TRANSITION */
.is-dark-mode .logo-carousel_logo {
filter: saturate(0%);
}
.bsg-color-wrapper .home-benefit_icon {
filter: hue-rotate(120deg) invert(100%) contrast(200%);
}
.bg-color-wrapper .home-benefit_icon {
filter: invert(1) hue-rotate(180deg) brightness(1.4);
}
.is-dark-mode .home-benefit_icon {
filter: hue-rotate(0deg) invert(0%) contrast(100%);
}
.bg-color-wrapper,
.bg-color-wrapper * {
transition:
color 0.6s ease,
padding 0.6s ease,
background-color 1s ease,
border-color 0.6s ease;
}
.bg-color-wrapper .image-fade-in_scale-parent {
transition: transform 0.3s ease !important;
}
.bg-color-wrapper .primary-button {
transition: padding 0.3s ease !important;
}
@media screen and (max-width: 991px) {
.is-mobile-swiper-component .swiper-wrapper {
transition: transform 0.6s ease !important;
}
}
/* SLIDER MOBILE */
@media screen and (max-width: 991px) {
.is-mobile-swiper-component .swiper-wrapper .home-products_item,
.is-mobile-swiper-component .swiper-wrapper .benefit_item {
width: 90% !important;
}
}
.is-image-hover-scale-parent:hover .image-fade-in_scale-parent {
transform: scale(1.05);
}
.arrow_component[bg-color="white"] {
background-color: white;
}
.is-arrow-hover-parent:hover .arrow_component {
background-color: #00b385;
color: white;
}
.primary-button:hover .button_icon:nth-child(1) {
opacity: 1;
transform: translate(0rem, 0px);
}
.support-faq_item-toggle {
cursor: pointer;
}
/* ANSWER */
.support-faq_item-answer {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.35s ease;
}
.support-faq_item-answer-inner {
overflow: hidden;
}
/* OPEN STATE */
.support-faq_item.is-open .support-faq_item-answer {
grid-template-rows: 1fr;
}
/* ICON */
.support-faq_icon-line {
transition:
transform 0.35s ease,
opacity 0.35s ease;
}
/* OPEN ICON STATE */
.support-faq_item.is-open .support-faq_icon-line.is-vertical {
transform: rotate(90deg);
}
/* Hero RTE */
.company-hero_rte.is-dark-green h1 {
color: #2d2d2e;
}
.company-hero_rte.is-dark-green h1 strong {
color: #00b385;
}
.company-hero_rte.is-dark-grey h1 {
color: #2d2d2e;
}
.company-hero_rte.is-dark-grey h1 strong {
color: #b3b5b6;
}
/* Video / Image Component */
.video-image_play-button {
display: none;
}
.video-image_lightbox.has-video .video-image_play-button {
display: block; /* or flex */
}
.video-image_lightbox {
cursor: default;
}
.video-image_lightbox.has-video {
cursor: pointer;
}
.video-image_lightbox:hover .video-image_play-button {
box-shadow: rgba(0, 0, 0, 0) 0px 0px 10px 10px;
transform: scale(0.9);
}
.nav.is-scroll .navbar_menu-divider,
.nav.is-dark .navbar_menu-divider {
background-image: linear-gradient(
90deg,
rgba(45, 45, 46, 0),
#2d2d2e 10%,
#2d2d2e 90%,
rgba(45, 45, 46, 0)
);
}
.dropdown_link-block {
background-position: 1.25rem 50%;
}
@media screen and (max-width: 991px) {
.dropdown_content-nav-left .section_title-wrapper {
display: none;
}
.dropdown_button .primary-button .is-text-16-aeonik {
font-size:0.75rem;
font-family: var(--_typography-xd---text-16-aeonik--font-family);
}
.navbar_menu-link-item.is-open .dropdown_arrow-nav {
color: white;
background-color: #00B385;
}
}
.is-scroll .navbar_logo.is-dark,
.is-dark .navbar_logo.is-dark {
opacity: 1;
}
.is-scroll .navbar_logo.is-light,
.is-dark .navbar_logo.is-light {
opacity: 0;
}
@media screen and (min-width: 992px) {
.navbar_menu-link-item.is-open .dropdown_toggle-nav {
color: #00b385;
}
.navbar_menu-link-item.is-open .dropdown_content-wrapper {
opacity: 1;
display: block;
transform: translateY(0rem);
}
}
.dropdown_link-block:hover {
opacity: 60%;
}
.navbar_menu-link-item.is-open .dropdown_arrow-nav {
transform: rotate(180deg);
}
.nav.is-scroll .dropdown_wrap-nav,
.nav.is-scroll .is-nav-login,
.nav.is-dark .dropdown_wrap-nav,
.nav.is-dark .is-nav-login {
color: #2d2d2e;
}
.nav.is-scroll .dropdown_wrap-nav:hover,
.nav.is-scroll .is-nav-login:hover,
.nav.is-dark .dropdown_wrap-nav:hover,
.nav.is-dark .is-nav-login:hover {
color: #00b385;
}
@media screen and (max-width: 991px) {
.dropdown_wrap-nav .section-title_wrapper {
display: none;
}
.is-open .dropdown_content-wrapper {
position: relative;
}
.dropdown_content-wrapper {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.35s ease;
overflow: hidden;
}
.dropdown_content-nav {
min-height: 0;
overflow: hidden;
}
.navbar_menu-link-item.is-open .dropdown_content-wrapper {
grid-template-rows: 1fr;
}
.is-open .dropdown_content-nav {
opacity: 1;
}
.is-scroll .nav_hamburger-line-after-scroll,
.is-dark .nav_hamburger-line-after-scroll {
opacity: 1;
}
}
@media screen and (min-width: 992px) {
.is-hidden .is-hidden-target {
opacity: 0;
height: 0;
overflow: hidden;
-webkit-transform: translateZ(0);
}
.is-hidden .navbar_menu-divider {
opacity: 0;
}
}
*/
Resources
ResourcesBrowse through our resourcesView all resources
Case StudiesWebinars & VideosNews & BlogEvent AgendaStreet SmartCompany
CompanyCyclomedia, leading the way in innovative workflow solutions and geospatial technology.View our company info
About UsCareersDriving SchedulePartnersSustainabilityLeadership TeamContactEU
LanguageChoose your languageEUUSNLDEFRPL
LoginRequest a demoIndustries
IndustriesDelivering tailored solutions across diverse industries with cutting-edge technology and industry expertise.View all industries
Construction & EngineeringGovernmentInsuranceInfrastructureUtilities & EnergyTelecommunicationsUse cases
Use casesPowerful, industry-specific solutions designed to solve your most complex challenges with cutting-edge technology.View all use cases
Asset ManagementPavement & SurfaceSmart CityTax AssessmentPedestrian SafetyRoad SafetyProducts & technologies
Products & technologiesDiscover our comprehensive suite of geospatial solutions designed to transform infrastructure management.View all our products & technologies
Captured DataAssetsStreet SmartIntegrations & APIs
<canvas id="sequence-canvas"
style="width: 100%; height: 100%; object-fit: cover; display: block;">
Capture. Insight.Intelligence.Vision.We capture the world in unmatched detail, turning reality into insights that saves time and cost and helps governments and companies operate smarter and safer.Trusted Data. Powerful Insights.Who we areCyclomedia is a global leader in geospatial data and intelligence, transforming how organisations capture, visualise, and analyse the built environment.Cyclomedia digitises the outside world at scale. Using highly accurate streetlevel imagery and LiDAR data and AI-powered analytics, we create reliable digital representations of cities and infrastructure that support planning, maintenance, and public safety. We empower governments and businesses to make smarter, data-driven decisions.Our data helps professionals assess assets remotely, reduce field visits, and make decisions based on consistent, measurable information rather than fragmented local surveys or outdated records.Read more about us
@media screen and (min-width: 992px) {
.home-benefit_list-item .is-text-32 {
margin-top: 3rem;
}
.sticky-child .home-products_item {
width: 34.875rem;
flex: 0 0 auto;
}
}
Our products + technologiesDiscover our comprehensive suite of geospatial solutions designed to support public space and infrastructure management.Cyclomedia provides a connected set of geospatial assets that help organisations understand, analyse, and manage the outdoor environment. From captured data to visualisation, insight, and system integration, each asset plays a distinct role in turning realworld conditions into reliable information for professional decisionmaking.Data
Highaccuracy streetlevel imagery (Cyclorama&#x27;s), LiDAR point clouds, and aerial data create a consistent and measurable digital representation of public space. These datasets form the foundation for inspection, measurement, comparison over time, and largescale analysis without repeated site visits.Insights
Cyclomedia transforms geospatial data into structured and usable insight. Automated analysis supports objective assessments, scalable asset inventories, and consistent decisionmaking across departments and regions.Street Smart
Street Smart is the central environment for viewing, measuring, and comparing geospatial data. It enables professionals to inspect assets, validate conditions, and analyse change directly from their desktop using reliable visual and spatial context.Integrations &amp; APIs
Open integrations and APIs connect Cyclomedia data with existing GIS, asset management, and enterprise systems. This ensures accurate and consistent information flows across tools, teams, and workflows.Industries we serveSupporting public sector and infrastructure organisations across EuropeAcross Europe, public authorities and infrastructure organisations rely on Cyclomedia to understand and manage realworld conditions. Our solutions support sectorspecific workflows while providing a shared and consistent foundation for collaboration, analysis, and longterm planning.Construction &amp; engineering
Construction and engineering teams use Cyclomedia to gain early insight into site conditions, surroundings, and constraints before work begins. Accurate spatial data and remote measurements support design validation, reduce rework, and improve coordination across planning, engineering, and delivery phases.Government
Public authorities use Cyclomedia to manage streets, buildings, and public assets more efficiently. Accurate geospatial intelligence supports asset management, inspections, permitting, and urban planning while reducing reliance on manual surveys and repeated site visits.Infrastructure
Infrastructure owners and operators rely on Cyclomedia to manage linear and areabased assets throughout long life cycles. A complete digital representation of the outdoor environment supports investment planning, coordination, and longterm asset strategies.Utilities &amp; energy
Utility and energy providers use Cyclomedia to plan, inspect, and manage networks more effectively. Reliable insight into assets and their surroundings supports maintenance planning and network expansion while reducing operational risk.Telecommunications
Telecommunications providers use Cyclomedia to assess locations, surroundings, and physical constraints during network planning and rollout. Remote insight enables faster evaluations and more confident deployment decisions.Insurance
Insurance organisations use Cyclomedia to support assessment and validation workflows. Objective visual evidence and spatial context improve transparency, consistency, and efficiency when reviewing locations, properties, and risk factors.Use cases we offerPractical geospatial solutions for critical infrastructure and operational challenges.Asset management: Centralise asset inventories using accurate street-level imagery, LiDAR, and AI-derived attributes to monitor condition, plan maintenance, and reduce site visits across the full asset lifecycle.More about Asset ManagementPavement &amp; surface:Automatically detect pavement defects, surface types, and deterioration using high-resolution imagery and AI to support maintenance prioritisation, budget planning, and network-level condition assessment.More about Pavement &amp; SurfaceSmart city:Create a shared digital representation of public space that supports cross-department collaboration, data-led planning, and consistent decision-making using objective, up-to-date geospatial data.More about Smart CityTax assessment:Support fair and defensible property valuations using current visual evidence and measurements, reducing reliance on site visits while improving accuracy, transparency, and review efficiency.More about Tax AssessmentPedestrian safety:Identify, measure, and assess footways, dropped kerbs, crossings, and streetscape assets to support accessibility compliance, risk reduction, and safer public spaces without extensive fieldwork.More about Pedestrian SafetyRoad safety:Analyse road environments, signage, markings, and surrounding context at scale to support safety audits, risk assessments, and targeted improvements based on consistent visual and spatial data.More about Road SafetyGet startedExperience Cyclomedias innovative solutions - request your free demoRequest a demoContactt: +31 (0) 418 - 55 61 00e: info@cyclomedia.nlVan Voordenpark 1b 5301 KP Zaltbommel (NL)
IndustriesConstruction &amp; EngineeringGovernmentInsuranceInfrastructureUtilities &amp; EnergyTelecommunicationsUse casesAsset ManagementPavement &amp; SurfaceSmart CityTax AssessmentPedestrian SafetyRoad SafetyProducts &amp; technologiesCaptured DataAssetsStreet SmartIntegrations &amp; APIsResourcesCase StudiesWebinars &amp; VideosNews &amp; BlogEvent AgendaSupportDeveloper PortalContact SupportStreet Smart SupportCompanyAbout UsCareersDriving SchedulePartnersSustainabilityLeadership Team© 2026 CyclomediaTerms &amp; conditionsPrivacyCookiesImpressum let yearElements = document.getElementsByClassName('year'); [].slice.call(yearElements).forEach(function (div) { div.innerHTML = new Date().getFullYear(); });
0%
// ============================================================================
// BACKGROUND COLOR CHANGE ON SCROLL
// Works with horizontal scroll pin — uses refreshPriority to recalc AFTER pin
// ============================================================================
window.Webflow ||= [];
window.Webflow.push(() => {
if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") {
console.warn("[BG Color Change] GSAP or ScrollTrigger not loaded");
return;
}
gsap.registerPlugin(ScrollTrigger);
let scrollTriggerInstance = null;
function initBgColorChange() {
if (scrollTriggerInstance) {
scrollTriggerInstance.kill();
scrollTriggerInstance = null;
}
const wrapper = document.querySelector(".bg-color-wrapper");
const trigger = document.querySelector(".bg-color-trigger");
if (!wrapper || !trigger) {
console.warn("[BG Color Change] Missing .bg-color-wrapper or .bg-color-trigger");
return;
}
scrollTriggerInstance = ScrollTrigger.create({
trigger: trigger,
start: "top bottom",
end: "bottom bottom",
toggleClass: {
targets: wrapper,
className: "is-dark-mode",
},
invalidateOnRefresh: true,
refreshPriority: -1, // recalc AFTER horizontal scroll pin
});
console.log("[BG Color Change] Initialized");
}
initBgColorChange();
// Refresh gdy wszystko (obrazki, fonty, layout) gotowe
if (document.readyState === "complete") {
ScrollTrigger.refresh();
} else {
window.addEventListener("load", () => ScrollTrigger.refresh(), { once: true });
}
// Debounced resize
let resizeTimer;
window.addEventListener("resize", () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => ScrollTrigger.refresh(), 250);
});
});
gsap.registerPlugin(ScrollTrigger);
(() => {
const SEL = {
parent: ".sticky-parent",
child: ".sticky-child",
list: ".home-benefit_list",
};
const BREAKPOINT = "(min-width: 991px)";
const END_BUFFER = 100;
const waitForAssets = async () => {
const imgs = [...document.querySelectorAll("img")];
const imgP = imgs.map(img =>
img.complete && img.naturalWidth !== 0
? Promise.resolve()
: new Promise(res => {
img.addEventListener("load", res, { once: true });
img.addEventListener("error", res, { once: true });
})
);
await Promise.all([...imgP, document.fonts?.ready ?? Promise.resolve()]);
};
const setup = () => {
const parent = document.querySelector(SEL.parent);
const child = document.querySelector(SEL.child);
const list = document.querySelector(SEL.list);
if (!parent || !child || !list) return;
const mm = gsap.matchMedia();
mm.add(BREAKPOINT, () => {
const getDistance = () => Math.max(0, list.scrollWidth - child.offsetWidth);
// Wysokość parenta = okno + dystans poziomy + bufor
const setParentHeight = () => {
const d = getDistance();
parent.style.height = `${window.innerHeight + d + END_BUFFER}px`;
};
setParentHeight();
const tween = gsap.to(list, {
x: () => -getDistance(),
ease: "none",
scrollTrigger: {
trigger: parent,
start: "top top",
end: "bottom bottom",
scrub: 1,
invalidateOnRefresh: true,
// markers: true,
},
});
// Każda zmiana rozmiaru listy lub okna → przelicz wysokość i refresh
const ro = new ResizeObserver(() => {
setParentHeight();
ScrollTrigger.refresh();
});
ro.observe(list);
ro.observe(child);
return () => {
ro.disconnect();
tween.scrollTrigger?.kill();
tween.kill();
gsap.set(list, { clearProps: "all" });
parent.style.height = "";
};
});
// Pierwszy refresh dopiero gdy obrazki i fonty są gotowe
waitForAssets().then(() => ScrollTrigger.refresh());
// Ignoruj zmianę wysokości viewportu (pasek adresu na mobile)
let t, lastW = window.innerWidth;
window.addEventListener("resize", () => {
clearTimeout(t);
t = setTimeout(() => {
if (window.innerWidth === lastW) return;
lastW = window.innerWidth;
ScrollTrigger.refresh();
}, 200);
});
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", setup, { once: true });
} else {
setup();
}
})();
// ─── Home Products Item — scroll reveal (only inside .grid-sticky_right) ──────
(function () {
document.addEventListener('DOMContentLoaded', function () {
const items = document.querySelectorAll('.grid-sticky_right .home-products_item');
if (!items.length) return;
items.forEach(function (item) {
gsap.fromTo(item,
{
opacity: 0,
scale: 0.85,
},
{
opacity: 1,
scale: 1,
ease: 'power2.out',
scrollTrigger: {
trigger: item,
start: 'top bottom',
end: 'bottom bottom',
scrub: true,
}
}
);
});
});
})();
// --- Set loader to 20% immediately so user never sees "0%" before window.load fires ---
(function setInitialLoader() {
const setIt = () => {
const el = document.querySelector(".loader-percent");
if (el) el.textContent = "20%";
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", setIt);
} else {
setIt();
}
})();
window.addEventListener("load", () => {
gsap.registerPlugin(ScrollTrigger);
// ============================================================
// KONFIGURACJA — desktop vs mobile
// ============================================================
const IS_PORTRAIT = window.matchMedia("(orientation: portrait)").matches;
const CONFIG = IS_PORTRAIT ? {
baseURL: "https://cyclomedia.fra1.cdn.digitaloceanspaces.com/hero-frames-mobile/frame_",
frameCount: 1534,
triggers: { intro: 21, h1_1: 324, h1_2: 783, h1_3: 1123 },
} : {
baseURL: "https://cyclomedia.fra1.cdn.digitaloceanspaces.com/hero-frames/frame_",
frameCount: 1469,
triggers: { intro: 20, h1_1: 310, h1_2: 750, h1_3: 1075 },
};
const { baseURL, frameCount } = CONFIG;
const { intro: FR_INTRO, h1_1: FR_H1_1, h1_2: FR_H1_2, h1_3: FR_H1_3 } = CONFIG.triggers;
const pad = (n) => String(n).padStart(4, "0");
const frameSrc = (i) => `${baseURL}${pad(i + 1)}.webp`;
// ============================================================
// DOM
// ============================================================
const canvas = document.getElementById("sequence-canvas");
const ctx = canvas.getContext("2d");
canvas.width = IS_PORTRAIT ? 1080 : 1920;
canvas.height = IS_PORTRAIT ? 1920 : 1080;
const loaderOverlay = document.querySelector(".loader-overlay");
const loaderPercent = document.querySelector(".loader-percent");
const h1Elements = document.querySelectorAll(".home-hero_h1-wrapper h1");
const subtext = document.querySelector(".home-hero_subtext");
document.body.style.overflow = "hidden";
// ============================================================
// LOADER PROGRESS
// ============================================================
const progressTracker = { val: 20, target: 20 };
let highestPercent = 20;
if (loaderPercent) loaderPercent.textContent = "20%";
function updateLoaderPercent(newPercentage) {
if (newPercentage < highestPercent) return;
highestPercent = newPercentage;
progressTracker.target = newPercentage;
}
function tickProgress() {
if (progressTracker.val < progressTracker.target) {
const diff = progressTracker.target - progressTracker.val;
progressTracker.val = Math.min(
progressTracker.target,
progressTracker.val + Math.max(diff * 0.08, 0.6)
);
if (loaderPercent) {
loaderPercent.textContent = Math.round(progressTracker.val) + "%";
}
}
}
gsap.ticker.add(tickProgress);
function hideLoaderAndStart() {
const waitForCounter = () => {
if (Math.round(progressTracker.val) >= 100) {
gsap.ticker.remove(tickProgress);
gsap.to(loaderOverlay, {
opacity: 0,
duration: 0.5,
delay: 0.3,
onComplete: () => {
if (loaderOverlay) loaderOverlay.style.display = "none";
document.body.style.overflow = "";
initScrollAnimations();
fillInRemainingFrames();
},
});
} else {
requestAnimationFrame(waitForCounter);
}
};
waitForCounter();
}
// ============================================================
// PRELOAD — priority batch (co 10 klatka) + background fill-in
// ============================================================
const images = new Array(frameCount);
const seq = { frame: 0 };
function drawFrame(index) {
const img = images[index];
if (!img || !img.complete || img.naturalWidth === 0) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
}
// FAZA 1: co 10 klatka (low-res scrub ready)
function preloadPriority() {
return new Promise((resolve) => {
const priorityIndexes = [];
for (let i = 0; i < frameCount; i += 10) priorityIndexes.push(i);
if (priorityIndexes[priorityIndexes.length - 1] !== frameCount - 1) {
priorityIndexes.push(frameCount - 1);
}
let loaded = 0;
const total = priorityIndexes.length;
priorityIndexes.forEach((idx) => {
const img = new Image();
img.decoding = "async";
img.onload = img.onerror = () => {
loaded++;
updateLoaderPercent(Math.round((loaded / total) * 100));
if (idx === 0 && img.complete) drawFrame(0);
if (loaded === total) resolve();
};
img.src = frameSrc(idx);
images[idx] = img;
});
});
}
// FAZA 2: uzupełnienie pozostałych klatek batched (nie blokuje scrollu)
async function fillInRemainingFrames() {
const batchSize = 20;
for (let i = 0; i < frameCount; i += batchSize) {
const batch = [];
for (let j = i; j < Math.min(i + batchSize, frameCount); j++) {
if (images[j]) continue;
const img = new Image();
img.decoding = "async";
img.src = frameSrc(j);
images[j] = img;
batch.push(new Promise((res) => { img.onload = img.onerror = res; }));
}
if (batch.length) await Promise.all(batch);
}
}
// ============================================================
// H1 LOGIKA
// ============================================================
function setAllH1Active() {
h1Elements.forEach((el) => el.classList.add("is-active"));
}
function setActiveH1(index) {
h1Elements.forEach((el, i) => {
el.classList.toggle("is-active", i === index);
});
}
// ============================================================
// SCROLL ANIMATIONS
// ============================================================
function initScrollAnimations() {
setAllH1Active();
let introSwitched = false;
let currentH1 = -1;
const wrapper = document.getElementById("sequence-wrapper");
const section = wrapper.parentElement;
gsap.to(seq, {
frame: frameCount - 1,
snap: "frame",
ease: "none",
scrollTrigger: {
trigger: section,
start: "top top",
end: "bottom bottom",
scrub: 0.5,
invalidateOnRefresh: true,
},
onUpdate: function () {
const frame = Math.round(seq.frame);
drawFrame(frame);
// --- intro switch (subtext fade) ---
if (frame >= FR_INTRO && !introSwitched) {
introSwitched = true;
if (subtext) {
gsap.to(subtext, {
opacity: 0,
height: 0,
duration: 0.6,
ease: "power2.out",
overflow: "hidden",
});
}
}
if (frame < FR_INTRO && introSwitched) {
introSwitched = false;
if (subtext) {
gsap.to(subtext, {
opacity: 1,
height: "auto",
duration: 0.6,
ease: "power2.out",
});
}
}
// --- H1 switching ---
let targetH1 = -1; // -1 = all active (intro)
if (frame >= FR_H1_3) targetH1 = 3;
else if (frame >= FR_H1_2) targetH1 = 2;
else if (frame >= FR_H1_1) targetH1 = 1;
else if (frame >= FR_INTRO) targetH1 = 0;
if (targetH1 !== currentH1) {
currentH1 = targetH1;
if (targetH1 === -1) setAllH1Active();
else setActiveH1(targetH1);
}
},
});
// Hide scroll hint once user starts scrolling
const scrollLottie = document.querySelector(".home-hero_scroll-lottie");
if (scrollLottie) {
gsap.to(scrollLottie, {
opacity: 0,
duration: 0.3,
ease: "power2.out",
scrollTrigger: {
trigger: section,
start: "top+=50 top",
toggleActions: "play none none reverse",
},
});
}
setTimeout(() => ScrollTrigger.refresh(), 500);
}
// ============================================================
// START
// ============================================================
updateLoaderPercent(30);
preloadPriority().then(() => {
updateLoaderPercent(100);
hideLoaderAndStart();
});
});
(function () {
const WRAPPER_SELECTOR = '.navbar_menu-list-wrappr';
const CTA_SELECTOR = '.navbar_menu-link-item.is-nav-cta';
const DESKTOP_BREAKPOINT = 992;
const EXTRA_GAP = 0;
const wrapper = document.querySelector(WRAPPER_SELECTOR);
const cta = document.querySelector(CTA_SELECTOR);
if (!wrapper || !cta) return;
let lastValue = -1;
let scheduled = false;
function compute() {
scheduled = false;
if (window.innerWidth < DESKTOP_BREAKPOINT) {
if (lastValue !== 0) {
wrapper.style.paddingRight = '';
lastValue = 0;
}
return;
}
const w = Math.round(cta.getBoundingClientRect().width + EXTRA_GAP);
if (w !== lastValue) {
wrapper.style.paddingRight = w + 'px';
lastValue = w;
}
}
function schedule() {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(compute);
}
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(schedule);
}
schedule();
window.addEventListener('resize', schedule, { passive: true });
window.addEventListener('load', schedule);
if (window.ResizeObserver) {
new ResizeObserver(schedule).observe(cta);
}
})();
window.Webflow ||= [];
window.Webflow.push(() => {
if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined" || typeof SplitText === "undefined") {
console.warn("[Highlight Text] GSAP, ScrollTrigger, or SplitText not loaded");
return;
}
gsap.registerPlugin(ScrollTrigger, SplitText);
const splitInstances = [];
function initHighlightText() {
splitInstances.forEach(s => s?.revert());
splitInstances.length = 0;
const targets = document.querySelectorAll("[data-highlight-text]");
if (targets.length === 0) return;
targets.forEach((heading) => {
const scrollStart = heading.getAttribute("data-highlight-scroll-start") || "top 90%";
const scrollEnd = heading.getAttribute("data-highlight-scroll-end") || "center 40%";
const fadedValue = parseFloat(heading.getAttribute("data-highlight-fade") || "0.2");
const staggerValue = parseFloat(heading.getAttribute("data-highlight-stagger") || "0.1");
const split = new SplitText(heading, {
type: "words, chars",
autoSplit: true,
onSplit(self) {
return gsap.context(() => {
gsap.set(self.chars, { opacity: fadedValue });
gsap.timeline({
scrollTrigger: {
trigger: heading,
start: scrollStart,
end: scrollEnd,
scrub: true,
invalidateOnRefresh: true,
}
}).to(self.chars, {
opacity: 1,
stagger: staggerValue,
ease: "linear",
});
});
}
});
splitInstances.push(split);
});
}
// Init od razu — ScrollTrigger sam recalculate przy ładowaniu lazy obrazków (invalidateOnRefresh)
initHighlightText();
// Refresh ScrollTrigger gdy reszta strony (obrazki, fonty) kończy ładować
window.addEventListener("load", () => ScrollTrigger.refresh());
});
// ─── Dropdown logic ───────────────────────────────────────────────────────────
function closeAllDropdowns() {
document.querySelectorAll('.navbar_menu-link-item.is-open').forEach(item => {
item.classList.remove('is-open');
});
}
document.querySelectorAll('.navbar_menu-link-item').forEach(trigger => {
trigger.addEventListener('click', function () {
const wasOpen = this.classList.contains('is-open');
const isSearch = this.classList.contains('is-search');
document.querySelectorAll('.navbar_menu-link-item.is-open').forEach(other => {
if (other !== this) other.classList.remove('is-open');
});
if (isSearch && wasOpen) return;
if (wasOpen) {
this.classList.remove('is-open');
} else {
this.classList.add('is-open');
}
});
});
document.querySelectorAll('.nav_search-close').forEach(closeBtn => {
closeBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation(); // ← added
document.querySelectorAll('.navbar_menu-link-item.is-search.is-open').forEach(item => {
item.classList.remove('is-open');
});
});
});
document.addEventListener('click', function (e) {
if (!e.target.closest('.navbar_menu-link-item')) {
closeAllDropdowns();
}
});
// ─── Hide/show navbar on scroll + combo class ─────────────────────────────────
$(document).ready(function () {
let lastScrollY = $(window).scrollTop();
const $nav = $('.nav');
const SCROLL_THRESHOLD = 8; // px of scroll before reacting (avoids micro-jitter)
function handleScroll() {
const currentScrollY = $(window).scrollTop();
const delta = currentScrollY - lastScrollY;
// ── Combo class (your existing logic) ──
const triggerTop = $('.nav-combo-class-trigger').offset().top;
if (currentScrollY >= triggerTop) {
$nav.addClass('is-scroll');
} else {
$nav.removeClass('is-scroll');
}
// ── Hide / show ──
if (Math.abs(delta) < SCROLL_THRESHOLD) return; // ignore tiny movements
if (delta > 0 && currentScrollY > 80) {
// Scrolling DOWN and past 80px from top → hide
closeAllDropdowns();
$nav.addClass('is-hidden');
} else {
// Scrolling UP → show
$nav.removeClass('is-hidden');
}
lastScrollY = currentScrollY;
}
$(window).on('scroll resize', handleScroll);
handleScroll(); // run once on load
});
// ─── Nav blur overlay ─────────────────────────────────────────────────────────
(function () {
const overlay = document.querySelector('.nav_blur-overlay');
if (!overlay) return;
// Upewnij się że overlay startuje jako ukryty
overlay.style.display = 'none';
overlay.style.opacity = '0';
overlay.style.transition = 'opacity 0.5s ease';
function updateOverlay() {
const anyOpen = document.querySelector('.navbar_menu-link-item.is-open');
if (anyOpen) {
overlay.style.display = 'block';
// Wymuś reflow żeby transition zadziałał po display:block
overlay.getBoundingClientRect();
overlay.style.opacity = '1';
} else {
overlay.style.opacity = '0';
overlay.addEventListener('transitionend', function hide() {
if (overlay.style.opacity === '0') {
overlay.style.display = 'none';
}
overlay.removeEventListener('transitionend', hide);
});
}
}
// Podpięcie pod istniejący MutationObserver na .navbar_menu-link-item
const navItems = document.querySelectorAll('.navbar_menu-link-item');
const observer = new MutationObserver(updateOverlay);
navItems.forEach(item => {
observer.observe(item, { attributes: true, attributeFilter: ['class'] });
});
})();
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.video-image_lightbox').forEach(link => {
const json = link.querySelector('.w-json');
if (!json) return;
try {
const data = JSON.parse(json.textContent);
if (data.items?.length > 0) link.classList.add('has-video');
} catch (e) {}
});
});
<iframe src=https://www.googletagmanager.com/ns.html?id=GTM-K2VSP89
height="0" width="0" style="display:none;visibility:hidden">
$(document).ready(function () {
let windowWidth = window.innerWidth;
let swipers = [];
function initSwipers() {
$(".is-mobile-swiper-component").each(function (index) {
if (window.innerWidth < 991) {
const alreadyInit = $(this).data("swiper-initialized");
if (!alreadyInit) {
const loopMode = $(this).attr("loop-mode") === "true";
const sliderDurationAttr = $(this).attr("slider-duration");
const sliderDuration = sliderDurationAttr !== undefined ? +sliderDurationAttr : 500;
const swiperEl = $(this).find(".swiper")[0];
const paginationEl = $(this).find(".swiper_nav-wrapper")[0];
const nextEl = $(this).find(".swiper-next")[0];
const prevEl = $(this).find(".swiper-prev")[0];
if (!swiperEl) return;
const swiper = new Swiper(swiperEl, {
speed: sliderDuration,
loop: loopMode,
autoHeight: false,
centeredSlides: loopMode,
followFinger: false,
freeMode: false,
slideToClickedSlide: false,
slidesPerView: "auto",
spaceBetween: 20,
rewind: false,
mousewheel: { forceToAxis: true },
pagination: paginationEl ? {
el: paginationEl,
bulletActiveClass: "is-active",
bulletClass: "swiper-bullet",
bulletElement: "button",
clickable: true,
} : false,
navigation: (nextEl && prevEl) ? {
nextEl: nextEl,
prevEl: prevEl,
disabledClass: "is-disabled",
} : false,
});
$(this).data("swiper-initialized", true);
swipers.push(swiper);
}
} else {
if ($(this).data("swiper-initialized")) {
const swiperInstance = $(this).find(".swiper")[0]?.swiper;
if (swiperInstance) swiperInstance.destroy(true, true);
$(this).data("swiper-initialized", false);
}
}
});
}
initSwipers();
$(window).on("resize", function () {
const currentWidth = window.innerWidth;
if (currentWidth !== windowWidth) {
windowWidth = currentWidth;
initSwipers();
}
});
});