1<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Sat Aug 22 2026 06:27:32 GMT+0000 (Coordinated Universal Time) --><html data-wf-domain="nomira-digital.webflow.io" data-wf-page="68ca7ac82e708712f7b3d802" data-wf-site="68ca7ac82e708712f7b3d78e" lang="en"><head><meta charset="utf-8"/><link href="https://cdn.prod.website-files.com" rel="preconnect" crossorigin="anonymous"/><title>nomÃra | Legal Notice & Privacy</title><meta content="Your privacy matters. This website collects only essential data to provide you with the best possible service and user experience." name="description"/><meta content="nomÃra | Legal Notice & Privacy" property="og:title"/><meta content="Your privacy matters. This website collects only essential data to provide you with the best possible service and user experience." property="og:description"/><meta content="https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68cad7333f518a4ff3ed7d6e_og-EN.jpg" property="og:image"/><meta content="nomÃra | Legal Notice & Privacy" name="twitter:title"/><meta content="Your privacy matters. This website collects only essential data to provide you with the best possible service and user experience." name="twitter:description"/><meta property="og:type" content="website"/><meta content="summary_large_image" name="twitter:card"/><meta content="width=device-width, initial-scale=1" name="viewport"/><meta content="K4gNBmYpdEgu8Qikxa_cl6D_qjo1BJJGWEl2iznREn8" name="google-site-verification"/><meta content="Webflow" name="generator"/><meta content="noindex" name="robots"/><link href="https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/css/nomira-digital.webflow.shared.14ac27279.min.css" rel="stylesheet" type="text/css" integrity="sha384-FKwnJ52HchqX0euIjfNCUpYStEl+wQFmRQl6WABOuFpskTGTEv/Ew9NakbNzjhxU" crossorigin="anonymous"/>
1<script type="text/javascript">!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);</script>
1<link href="https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68cae390c51bd1961ed7424a_favicon.svg" rel="icon" type="image/png" sizes="32x32" media="(prefers-color-scheme: light)"/><link href="https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/6a50c31af696bacd62c561fe_icon-black-purple.png" rel="icon" type="image/png" sizes="32x32" media="(prefers-color-scheme: dark)"/><link href="https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68cae4becbb9ee8eb7e5665a_webclip.png" rel="apple-touch-icon"/>
1<script>(function(w,i,g){w[g]=w[g]||[];if(typeof w[g].push=='function')w[g].push.apply(w[g],Array.isArray(i)?i:[i]);})(window,['G-SC8C1J82JW'],'google_tags_first_party');</script>
1<script async="" src="/v2645sy3qzdjNjhjYTdhYzgyZTcwODcxMmY3YjNkNzhl/6LaLqsB0JJTTPErqcObpJbbyCMo"></script>
1<script type="text/javascript">window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('set', 'developer_id.dZGVlNj', true);gtag('set', 'developer_id.dYWYxNW', true);gtag('js', new Date());gtag('config', 'G-SC8C1J82JW');</script>
1<link rel="alternate" hreflang="en" href="https://nomira.digital" /> 2<link rel="alternate" hreflang="de-CH" href="https://nomira.ch" /> 3<link rel="alternate" hreflang="x-default" href="https://nomira.digital" /> 4
5<script defer src="https://cloud.umami.is/script.js" data-website-id="b2edc94c-10c9-46bb-b9c8-15d51a9f1c9d"></script>
5 6 7<style> 8[data-initial-hidden] { 9 opacity: 0; 10 visibility: hidden; 11} 12</style> 13
14<script type="application/ld+json"> 15{ 16 "@context": "https://schema.org", 17 "@graph": [ 18 { 19 "@type": "ProfessionalService", 20 "@id": "https://www.nomira.digital/#organization", 21 "name": "nomÃra", 22 "alternateName": "nomÃra by Damian Müller", 23 "url": "https://www.nomira.digital/", 24 "logo": { 25 "@type": "ImageObject", 26 "@id": "https://www.nomira.digital/#logo", 27 "url": "https://cdn.prod.website-files.com/67d92f01b6a76882209da7d7/67d939da653338dfb7a25f47_nomira-default.svg", 28 "contentUrl": "https://cdn.prod.website-files.com/67d92f01b6a76882209da7d7/67d939da653338dfb7a25f47_nomira-default.svg", 29 "caption": "nomÃra" 30 }, 31 "image": "https://cdn.prod.website-files.com/67d92f01b6a76882209da7d7/67f66e64bfe4a0b08f210f01_og-03.jpg", 32 "description": "nomÃra is the UX and design studio of Damian Müller. Strategy, user experience, visual design and future-proof implementation following the Navigate. Craft. Evolve. approach â for digital experiences that truly move you forward. Simply Beyond.", 33 "slogan": "Simply Beyond", 34 "email": "[email protected]", 35 "founder": { "@id": "https://www.nomira.digital/#damianmueller" }, 36 "knowsLanguage": ["en", "de"], 37 "address": { 38 "@type": "PostalAddress", 39 "streetAddress": "Walterhofstrasse 1", 40 "postalCode": "4656", 41 "addressLocality": "Starrkirch-Wil", 42 "addressRegion": "SO", 43 "addressCountry": "CH" 44 }, 45 "areaServed": { "@type": "Place", "name": "Worldwide" }, 46 "contactPoint": { 47 "@type": "ContactPoint", 48 "email": "[email protected]", 49 "contactType": "customer service", 50 "availableLanguage": ["en", "de"] 51 }, 52 "sameAs": [ 53 "https://www.nomira.ch/", 54 "https://ch.linkedin.com/in/damian-mueller-nomira", 55 "https://www.instagram.com/nomira_damianmueller/" 56 ], 57 "knowsAbout": [ 58 "UX Design", 59 "User Experience", 60 "UI Design", 61 "Visual Design", 62 "Web Design", 63 "Digital Strategy", 64 "Webflow", 65 "Sanity CMS", 66 "Design Systems", 67 "Structured Content", 68 "Prototyping", 69 "User Testing", 70 "SEO", 71 "Answer Engine Optimization", 72 "Generative Engine Optimization" 73 ], 74 "hasOfferCatalog": { 75 "@type": "OfferCatalog", 76 "name": "Services & Formats", 77 "itemListElement": [ 78 { 79 "@type": "Offer", 80 "itemOffered": { 81 "@type": "Service", 82 "name": "UX Design", 83 "description": "User-centered concept work: research, analysis, user testing, personas, user journeys and validated prototypes." 84 } 85 }, 86 { 87 "@type": "Offer", 88 "itemOffered": { 89 "@type": "Service", 90 "name": "UI & Visual Design", 91 "description": "Visual design and interface craft: timeless, effective UI that brings brands to life." 92 } 93 }, 94 { 95 "@type": "Offer", 96 "itemOffered": { 97 "@type": "Service", 98 "name": "Webflow Development", 99 "description": "Custom, maintenance-free and self-manageable Webflow websites â from implementation to continuous evolution." 100 } 101 }, 102 { 103 "@type": "Offer", 104 "itemOffered": { 105 "@type": "Service", 106 "name": "Digital Strategy", 107 "description": "Digital sparring and strategy: question the status quo, uncover opportunities and sharpen direction." 108 } 109 }, 110 { 111 "@type": "Offer", 112 "itemOffered": { 113 "@type": "Service", 114 "name": "Website Reality Check", 115 "description": "An honest outside view of your website in a compact analysis with concrete recommendations and clear next steps.", 116 "url": "https://www.nomira.digital/reality-check" 117 } 118 }, 119 { 120 "@type": "Offer", 121 "itemOffered": { 122 "@type": "Service", 123 "name": "Prototype Sprint", 124 "description": "From idea to tested concept in three weeks â a clickable prototype with real user feedback and a clear direction for implementation.", 125 "url": "https://www.nomira.digital/prototype-sprint" 126 } 127 } 128 ] 129 } 130 }, 131 { 132 "@type": "Person", 133 "@id": "https://www.nomira.digital/#damianmueller", 134 "name": "Damian Müller", 135 "jobTitle": "UX & Visual Designer, Founder", 136 "worksFor": { "@id": "https://www.nomira.digital/#organization" }, 137 "url": "https://www.nomira.digital/", 138 "image": "https://cdn.prod.website-files.com/67d92f01b6a76882209da7d7/68caea30fdf9efd019f2cd0b_DM2503_0168_LowRes.webp", 139 "email": "[email protected]", 140 "knowsAbout": [ 141 "UX Design", 142 "User Experience", 143 "Visual Design", 144 "UI Design", 145 "Digital Strategy", 146 "Webflow", 147 "Sanity CMS", 148 "Structured Content", 149 "Prototyping", 150 "User Testing" 151 ], 152 "sameAs": [ 153 "https://ch.linkedin.com/in/damian-mueller-nomira", 154 "https://www.instagram.com/nomira_damianmueller/" 155 ] 156 }, 157 { 158 "@type": "WebSite", 159 "@id": "https://www.nomira.digital/#website", 160 "url": "https://www.nomira.digital/", 161 "name": "nomÃra", 162 "description": "Digital craftsmanship bridging strategy and design. Simply Beyond.", 163 "inLanguage": "en", 164 "publisher": { "@id": "https://www.nomira.digital/#organization" } 165 } 166 ] 167} 168</script>
168<style> 169[data-initial-hidden] { 170 opacity: 0; 171 visibility: hidden; 172} 173</style></head><body><div class="page-wrapper overflow-hidden"><div class="styles-wrapper"><div class="global-styles w-embed"><style> 174 175/* Make text look crisper and more legible in all browsers */ 176body { 177 -webkit-font-smoothing: antialiased; 178 -moz-osx-font-smoothing: grayscale; 179 font-smoothing: antialiased; 180 text-rendering: optimizeLegibility; 181} 182 183/* Focus state style for keyboard navigation for the focusable elements */ 184*[tabindex]:focus-visible, 185 input[type="file"]:focus-visible { 186 outline: 0.125rem solid #4d65ff; 187 outline-offset: 0.125rem; 188} 189 190/* Set color style to inherit */ 191.inherit-color * { 192 color: inherit; 193} 194 195/* Get rid of top margin on first element in any rich text element */ 196.w-richtext > :not(div):first-child, .w-richtext > div:first-child > :first-child { 197 margin-top: 0 !important; 198} 199 200/* Get rid of bottom margin on last element in any rich text element */ 201.w-richtext>:last-child, .w-richtext ol li:last-child, .w-richtext ul li:last-child { 202 margin-bottom: 0 !important; 203} 204 205 206/* Make sure containers never lose their center alignment */ 207.container-medium,.container-small, .container-large { 208 margin-right: auto !important; 209 margin-left: auto !important; 210} 211 212/* 213Make the following elements inherit typography styles from the parent and not have hardcoded values. 214Important: You will not be able to style for example "All Links" in Designer with this CSS applied. 215Uncomment this CSS to use it in the project. Leave this message for future hand-off. 216*/ 217/* 218a, 219.w-input, 220.w-select, 221.w-tab-link, 222.w-nav-link, 223.w-dropdown-btn, 224.w-dropdown-toggle, 225.w-dropdown-link { 226 color: inherit; 227 text-decoration: inherit; 228 font-size: inherit; 229} 230*/ 231 232/* Apply "..." after 3 lines of text */ 233.text-style-3lines { 234 display: -webkit-box; 235 overflow: hidden; 236 -webkit-line-clamp: 3; 237 -webkit-box-orient: vertical; 238} 239 240/* Apply "..." after 2 lines of text */ 241.text-style-2lines { 242 display: -webkit-box; 243 overflow: hidden; 244 -webkit-line-clamp: 2; 245 -webkit-box-orient: vertical; 246} 247 248/* These classes are never overwritten */ 249.hide { 250 display: none !important; 251} 252 253@media screen and (max-width: 991px) { 254 .hide, .hide-tablet { 255 display: none !important; 256 } 257} 258 @media screen and (max-width: 767px) { 259 .hide-mobile-landscape{ 260 display: none !important; 261 } 262} 263 @media screen and (max-width: 479px) { 264 .hide-mobile{ 265 display: none !important; 266 } 267} 268 269.margin-0 { 270 margin: 0rem !important; 271} 272 273.padding-0 { 274 padding: 0rem !important; 275} 276 277.spacing-clean { 278padding: 0rem !important; 279margin: 0rem !important; 280} 281 282.margin-top { 283 margin-right: 0rem !important; 284 margin-bottom: 0rem !important; 285 margin-left: 0rem !important; 286} 287 288.padding-top { 289 padding-right: 0rem !important; 290 padding-bottom: 0rem !important; 291 padding-left: 0rem !important; 292} 293 294.margin-right { 295 margin-top: 0rem !important; 296 margin-bottom: 0rem !important; 297 margin-left: 0rem !important; 298} 299 300.padding-right { 301 padding-top: 0rem !important; 302 padding-bottom: 0rem !important; 303 padding-left: 0rem !important; 304} 305 306.margin-bottom { 307 margin-top: 0rem !important; 308 margin-right: 0rem !important; 309 margin-left: 0rem !important; 310} 311 312.padding-bottom { 313 padding-top: 0rem !important; 314 padding-right: 0rem !important; 315 padding-left: 0rem !important; 316} 317 318.margin-left { 319 margin-top: 0rem !important; 320 margin-right: 0rem !important; 321 margin-bottom: 0rem !important; 322} 323 324.padding-left { 325 padding-top: 0rem !important; 326 padding-right: 0rem !important; 327 padding-bottom: 0rem !important; 328} 329 330.margin-horizontal { 331 margin-top: 0rem !important; 332 margin-bottom: 0rem !important; 333} 334 335.padding-horizontal { 336 padding-top: 0rem !important; 337 padding-bottom: 0rem !important; 338} 339 340.margin-vertical { 341 margin-right: 0rem !important; 342 margin-left: 0rem !important; 343} 344 345.padding-vertical { 346 padding-right: 0rem !important; 347 padding-left: 0rem !important; 348} 349 350</style></div><div class="custom-styles w-embed"><style> 351/* Bouncing verhindern bei schnellen Scrollen zum Top oder Bottom der Seite */ 352body {
353 overscroll-behavior: none; 354} 355 356section, header, footer { 357 position: relative; 358} 359 360/* Softe Ãbergänge bei Unterseiten-Wechsel */ 361@view-transition { 362 navigation: auto; 363} 364 365/* Automatische Silbentrennung durch Vergabe von Attributen "data-hyphenate" ("true") und "lang" ("de", "en"...) */ 366[data-hyphenate="true"] { 367 hyphens: auto; 368 overflow-wrap: break-word; /* oder word-wrap für ältere Browser */ 369 -webkit-hyphens: auto; 370 -ms-hyphens: auto; 371} 372 373[data-hyphenate="true"] * { 374 hyphens: auto; 375 overflow-wrap: break-word; 376 -webkit-hyphens: auto; 377 -ms-hyphens: auto; 378} 379 380button { 381 background-color: unset; 382 padding: unset; 383 text-align: inherit; 384} 385button:not(:disabled) { 386 cursor: pointer; 387} 388 389a:not([class]) { 390 text-decoration: none !important; 391} 392 393/* Global / Clickable Component */ 394.wf-design-mode .g_clickable_wrap { 395 z-index: 0; 396} 397.g_clickable_wrap a[href="#"] { 398 display: none; 399} 400.g_clickable_wrap a[href="#"] ~ button { 401 display: block; 402} 403 404.cta-icon { 405 transition: transform 300ms ease-out; 406} 407.cta-wrapper:has(.cta-link-wrapper:hover) .cta-icon { 408 transform: rotate(-35deg); 409} 410 411/* --------------------- 412CSS für WORK SECTION 413--------------------- */ 414 415html:not(.wf-design-mode) .work_img-wrapper { 416 opacity: 0; 417} 418 419html:not(.wf-design-mode) .section_work .g_clickable_wrap a, 420html:not(.wf-design-mode) .section_work .g_clickable_wrap button { 421 cursor: none; 422} 423 424 425/* --------------------- 426CSS für HEADING ICONS 427--------------------- */ 428 429/* Das letzte Wort als Wrapper (relative Positionierung für die innere absolute Positionierung) */ 430.last-word { 431 position: relative; 432 display: inline-block; 433} 434 435/* Positioniere das Icon relativ zum .last-word-Span */ 436.last-word .heading-icon { 437 position: absolute; 438 bottom: 0; 439 right: 0; 440 transform: translate(95%, 50%); 441} 442 443</style></div><div class="nav-styles w-embed"><style> 444 445body:has(.nav_component .w-nav-button.w--open) { 446 overflow: hidden; 447} 448 449/* Standard: geschlossen */ 450.nav_component:has(.w-nav-button:not(.w--open)) .w-nav-menu { 451 display: block; 452 position: fixed !important; 453 opacity: 0; 454 pointer-events: none; 455 transition: opacity 0.4s ease-out; 456} 457 458/* Geöffnet: weicher Fade-In */ 459.nav_component:has(.w-nav-button.w--open) .w-nav-menu { 460 display: block; 461 opacity: 1; 462 position: fixed !important; 463 pointer-events: auto; 464} 465 466/* Menu Button: Menü geschlossen */ 467.nav_menu-icon-text-close { 468 display: none; 469} 470.nav_menu-icon-text { 471 display: block; 472} 473.nav_menu-icon { 474 transform: rotate(0deg); 475} 476 477/* Menu Button: Menü geöffnet */ 478 479.nav_component:has(.w-nav-button.w--open) .nav_menu-icon-text-close { 480 display: block; 481} 482 483.nav_component:has(.w-nav-button.w--open) .nav_menu-icon-text { 484 display: none; 485} 486 487.nav_component:has(.w-nav-button.w--open) .nav_menu-icon { 488 transform: rotate(45deg); 489} 490 491.nav_component:has(.w-nav-button.w--open) .btn_wrap.is-nav { 492 visibility: hidden; 493 pointer-events: none; 494} 495 496/* Menu clip-paths*/ 497@media (min-width: 992px) { 498 .nav_menu { 499 clip-path: polygon(0% 13%, 73% 0%, 100% 0%, 100% 100%, 0% 100%) 500 } 501} 502@media (min-width: 480px) and (max-width: 991px) { 503 .nav_menu { 504 clip-path: polygon(0% 9%, 73% 0%, 100% 0%, 100% 100%, 0% 100%) 505 } 506} 507@media (max-width: 479px) { 508 .nav_menu { 509 clip-path: polygon(0% 7%, 75% 0%, 100% 0%, 100% 100%, 0% 100%) 510 } 511} 512 513.cta-2-icon { 514 transition: transform 300ms ease-out; 515} 516.cta-2-wrapper:has(.cta-2-link-wrapper:hover) .cta-2-icon { 517 transform: rotate(-35deg); 518} 519 520 521</style></div><div class="accordion-styles w-embed"><style> 522/* 523--------------- 524ALL ACCORDIONS 525--------------- 526*/ 527html:not(.wf-design-mode) .services_item, 528html:not(.wf-design-mode) .approach_item, 529html:not(.wf-design-mode) .webflow-benefits_item, 530html:not(.wf-design-mode) .services_description, 531html:not(.wf-design-mode) .approach_description, 532html:not(.wf-design-mode) .webflow-benefits_description { 533 pointer-events: none; 534 user-select: none; 535} 536 537 538/* 539--------------- 540SERVICES 541--------------- 542*/ 543 544.wf-design-mode .services_dropdown-height { 545 grid-template-rows: 1fr; 546} 547.wf-design-mode .services_dropdown-checkbox { 548 pointer-events: none; 549} 550 551 552.wf-design-mode .services_description { 553 opacity: 1; 554} 555.services_dropdown:has(.services_dropdown-checkbox:checked) .services_description { 556 opacity: 1; 557} 558.services_dropdown:has(.services_dropdown-checkbox:checked) .services_icon { 559 opacity: 0; 560 visibility: hidden; 561} 562 563 564@media (min-width: 992px) { 565 .services_dropdown:has(.services_dropdown-checkbox:checked) .services_dropdown-height { 566 grid-template-rows: 1fr; 567 margin-top: -2.5rem; 568 } 569 .services_dropdown:has(.services_dropdown-checkbox:checked) .services_item.is-1 { 570 transform: translateX(7rem); 571 } 572 .services_dropdown:has(.services_dropdown-checkbox:checked) .services_item.is-2 { 573 transform: translateX(-7rem); 574 } 575 .services_dropdown:has(.services_dropdown-checkbox:checked) .services_item.is-3 { 576 transform: translateX(4rem); 577 } 578 .services_dropdown:has(.services_dropdown-checkbox:checked) .services_item.is-4 { 579 transform: translateX(-5rem); 580 } 581} 582 583@media (max-width: 991px) { 584 .services_dropdown:has(.services_dropdown-checkbox:checked) .services_dropdown-height { 585 grid-template-rows: 1fr; 586 margin-top: -1.5rem; 587 } 588} 589 590.services_dropdown:has(.services_dropdown-checkbox:checked) .services_border { 591 margin-left: 0 !important; 592 margin-right: 0 !important; 593} 594 595 596/* 597--------------- 598APPROACH 599--------------- 600*/ 601 602.wf-design-mode .approach_dropdown-height { 603 grid-template-rows: 1fr; 604} 605.wf-design-mode .approach_dropdown-checkbox { 606 pointer-events: none; 607} 608 609 610.wf-design-mode .approach_description { 611 opacity: 1; 612} 613.approach_dropdown:has(.approach_dropdown-checkbox:checked) .approach_description { 614 opacity: 1; 615} 616.approach_dropdown:has(.approach_dropdown-checkbox:checked) .approach_icon { 617 opacity: 0; 618 visibility: hidden; 619} 620 621@media (min-width: 992px) { 622 .approach_dropdown:has(.approach_dropdown-checkbox:checked) .approach_dropdown-height { 623 grid-template-rows: 1fr; 624 margin-top: -2.5rem; 625 } 626 .approach_dropdown:has(.approach_dropdown-checkbox:checked) .approach_item.is-1 { 627 transform: translateX(-8vw); 628 } 629 .approach_dropdown:has(.approach_dropdown-checkbox:checked) .approach_item.is-2 { 630 transform: translateX(-9vw); 631 } 632 .approach_dropdown:has(.approach_dropdown-checkbox:checked) .approach_item.is-3 { 633 transform: translateX(15vw); 634 } 635} 636 637@media (max-width: 991px) { 638 .approach_dropdown:has(.approach_dropdown-checkbox:checked) .approach_dropdown-height { 639 grid-template-rows: 1fr; 640 margin-top: -1.5rem; 641 } 642} 643 644 645.approach_dropdown:has(.approach_dropdown-checkbox:checked) .approach_border { 646 margin-left: 0 !important; 647 margin-right: 0 !important; 648} 649 650/* 651--------------- 652Webflow Benefits 653--------------- 654*/ 655 656.wf-design-mode .webflow-benefits_dropdown-height { 657 grid-template-rows: 1fr; 658} 659.wf-design-mode .webflow-benefits_dropdown-checkbox { 660 pointer-events: none; 661} 662 663 664.wf-design-mode .webflow-benefits_description { 665 opacity: 1; 666} 667.webflow-benefits_dropdown:has(.webflow-benefits_dropdown-checkbox:checked) .webflow-benefits_description { 668 opacity: 1; 669} 670.webflow-benefits_dropdown:has(.webflow-benefits_dropdown-checkbox:checked) .webflow-benefits_icon { 671 opacity: 0; 672 visibility: hidden; 673} 674 675@media (min-width: 992px) { 676 .webflow-benefits_dropdown:has(.webflow-benefits_dropdown-checkbox:checked) .webflow-benefits_dropdown-height { 677 grid-template-rows: 1fr; 678 margin-top: -2.5rem; 679 } 680 .webflow-benefits_dropdown:has(.webflow-benefits_dropdown-checkbox:checked) .webflow-benefits_item.is-1 { 681 transform: translateX(-8vw); 682 } 683 .webflow-benefits_dropdown:has(.webflow-benefits_dropdown-checkbox:checked) .webflow-benefits_item.is-2 { 684 transform: translateX(-9vw); 685 } 686 .webflow-benefits_dropdown:has(.webflow-benefits_dropdown-checkbox:checked) .webflow-benefits_item.is-3 { 687 transform: translateX(15vw); 688 } 689} 690 691@media (max-width: 991px) { 692 .webflow-benefits_dropdown:has(.webflow-benefits_dropdown-checkbox:checked) .webflow-benefits_dropdown-height { 693 grid-template-rows: 1fr; 694 margin-top: -1.5rem; 695 } 696} 697 698 699.webflow-benefits_dropdown:has(.webflow-benefits_dropdown-checkbox:checked) .webflow-benefits_border { 700 margin-left: 0 !important; 701 margin-right: 0 !important; 702} 703 704</style></div><div class="custom-cursor-styles w-embed"><style> 705/* Custom Cursor CSS für Desktop only */ 706@media (max-width: 991px) { 707 .custom-cursor-dropdowns, 708 .custom-cursor-links, 709 .custom-cursor-offer { 710 display: none; 711 } 712} 713 714@media (min-width: 992px) { 715 /* Basisstil für alle dropdown-bezogenen Elemente */ 716 html:not(.wf-design-mode) .services_dropdown, 717 html:not(.wf-design-mode) .services_dropdown-checkbox, 718 html:not(.wf-design-mode) .approach_dropdown, 719 html:not(.wf-design-mode) .approach_dropdown-checkbox, 720 html:not(.wf-design-mode) .webflow-benefits_dropdown, 721 html:not(.wf-design-mode) .webflow-benefits_dropdown-checkbox, 722 html:not(.wf-design-mode) .work_item, 723 html:not(.wf-design-mode) .offer_card a {
724 cursor: none !important; 725 } 726} 727 728html:not(.wf-design-mode) .custom-cursor-dropdowns, 729html:not(.wf-design-mode) .custom-cursor-links, 730html:not(.wf-design-mode) .custom-cursor-offer { 731 opacity: 0; 732} 733 734html:not(.wf-design-mode) .custom-cursor-dropdowns.active, 735html:not(.wf-design-mode) .custom-cursor-links.active, 736html:not(.wf-design-mode) .custom-cursor-offer.active { 737 opacity: 1; 738} 739 740</style></div><style>:root {--site--margin: clamp(2rem, 1.84375rem + 0.78125vw, 2.5rem);}</style></div><div class="styleguide_guide_wrap"><div class="w-embed"><style> 741html:not(.wf-design-mode) .styleguide_guide_wrap { 742 display: none; 743} 744.styleguide_guide_layout { 745 counter-reset: gridguides; 746} 747.styleguide_guide_layout > div::before { 748 counter-increment: gridguides; 749 content: counter(gridguides); 750} 751 752:root { 753 --site--max-width: min(var(--site--width), 100vw); 754 --container--main: calc(var(--site--max-width) - var(--site--margin) * 2); 755 --container--full: calc(100vw - var(--site--margin) * 2); 756 --site--gutter-total: calc(var(--site--gutter) * (var(--site--column-count) - 1)); 757 --column-width--1: calc((var(--container--main) - var(--site--gutter-total)) / var(--site--column-count)); 758 --column-width--plus-gutter: calc(var(--column-width--1) + var(--site--gutter)); 759 --column-width--2: calc(var(--column-width--plus-gutter) * 2 - var(--site--gutter)); 760 --column-width--3: calc(var(--column-width--plus-gutter) * 3 - var(--site--gutter)); 761 --column-width--4: calc(var(--column-width--plus-gutter) * 4 - var(--site--gutter)); 762 --column-width--5: calc(var(--column-width--plus-gutter) * 5 - var(--site--gutter)); 763 --column-width--6: calc(var(--column-width--plus-gutter) * 6 - var(--site--gutter)); 764 --column-width--7: calc(var(--column-width--plus-gutter) * 7 - var(--site--gutter)); 765 --column-width--8: calc(var(--column-width--plus-gutter) * 8 - var(--site--gutter)); 766 --column-width--9: calc(var(--column-width--plus-gutter) * 9 - var(--site--gutter)); 767 --column-width--10: calc(var(--column-width--plus-gutter) * 10 - var(--site--gutter)); 768 --column-width--11: calc(var(--column-width--plus-gutter) * 11 - var(--site--gutter)); 769 --column-width--12: calc(var(--column-width--plus-gutter) * 12 - var(--site--gutter)); 770 --column-margin--1: calc(var(--column-width--plus-gutter) * 1); 771 --column-margin--2: calc(var(--column-width--plus-gutter) * 2); 772 --column-margin--3: calc(var(--column-width--plus-gutter) * 3); 773 --column-margin--4: calc(var(--column-width--plus-gutter) * 4); 774 --column-margin--5: calc(var(--column-width--plus-gutter) * 5); 775 --column-margin--6: calc(var(--column-width--plus-gutter) * 6); 776 --column-margin--7: calc(var(--column-width--plus-gutter) * 7); 777 --column-margin--8: calc(var(--column-width--plus-gutter) * 8); 778 --column-margin--9: calc(var(--column-width--plus-gutter) * 9); 779 --column-margin--10: calc(var(--column-width--plus-gutter) * 10); 780 --column-margin--11: calc(var(--column-width--plus-gutter) * 11); 781 --column-margin--12: calc(var(--column-width--plus-gutter) * 12); 782 --breakout-start: [full-start] minmax(0, 1fr) [content-start]; 783 --breakout-end: [content-end] minmax(0, 1fr) [full-end]; 784 --grid-breakout-single: var(--breakout-start) minmax(0, var(--container--main)) var(--breakout-end); 785 --grid-breakout: var(--breakout-start) repeat(var(--site--column-count), minmax(0, var(--column-width--1))) var(--breakout-end); 786 --grid-main: repeat(var(--site--column-count), minmax(0, 1fr)); 787 --grid-1: repeat(1, minmax(0, 1fr)); 788 --grid-2: repeat(2, minmax(0, 1fr)); 789 --grid-3: repeat(3, minmax(0, 1fr)); 790 --grid-4: repeat(4, minmax(0, 1fr)); 791 --grid-5: repeat(5, minmax(0, 1fr)); 792 --grid-6: repeat(6, minmax(0, 1fr)); 793 --grid-7: repeat(7, minmax(0, 1fr)); 794 --grid-8: repeat(8, minmax(0, 1fr)); 795 --grid-9: repeat(9, minmax(0, 1fr)); 796 --grid-10: repeat(10, minmax(0, 1fr)); 797 --grid-11: repeat(11, minmax(0, 1fr)); 798 --grid-12: repeat(12, minmax(0, 1fr)); 799} 800</style></div><div data-padding-bottom="none" data-padding-top="none" class="styleguide_guide_contain u-container"><div class="styleguide_guide_layout u-grid-custom"><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div><div class="styleguide_guide_column u-vflex-center-bottom"></div></div></div></div>
800<div data-animation="over-right" class="nav_component padding-global w-nav" data-easing2="ease" fs-scrolldisable-element="smart-nav" data-easing="ease" data-collapse="all" role="banner" data-initial-hidden="" data-duration="0" data-gsap-init="fade"><div class="nav_container"><div data-cal-config="{"layout":"month_view","theme":"dark"}" data-cal-link="nomira/lets-talk" data-cal-namespace="lets-talk" class="btn_wrap is-nav"><div class="g_clickable_wrap"><a target="" href="#" class="g_clickable_link w-inline-block"><span class="g_clickable_text screenreader-only">Letâs Talk</span></a><button type="button" class="g_clickable_btn"><span class="g_clickable_text screenreader-only">Letâs Talk</span></button></div><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 32 32" fill="none" class="btn-icon"><path d="M8 22C11.3137 22 14 19.3137 14 16C14 12.6863 11.3137 10 8 10C4.68629 10 2 12.6863 2 16C2 19.3137 4.68629 22 8 22Z" fill="currentColor"></path><path d="M24 22C27.3137 22 30 19.3137 30 16C30 12.6863 27.3137 10 24 10C20.6863 10 18 12.6863 18 16C18 19.3137 20.6863 22 24 22Z" fill="currentColor"></path></svg><div aria-hidden="true" class="btn_text">Letâs Talk</div></div><a href="/" class="nav_logo-link w-nav-brand"><img loading="lazy" src="https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68ca7ac82e708712f7b3d7c1_nomira-default.svg" alt="nomÃra by damian müller" class="nav_logo"/></a><div id="w-node-_8910e862-9853-9741-3b2f-84472168ecc2-2168ecbb" class="nav_menu-button w-nav-button"><div class="nav_menu-icon-wrapper"><div class="nav_menu-icon-text">Menu</div><div class="nav_menu-icon-text-close">close</div><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 32 32" fill="none" class="nav_menu-icon"><path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" fill="currentColor"></path><path d="M24 14C27.3137 14 30 11.3137 30 8C30 4.68629 27.3137 2 24 2C20.6863 2 18 4.68629 18 8C18 11.3137 20.6863 14 24 14Z" fill="currentColor"></path><path d="M8 30C11.3137 30 14 27.3137 14 24C14 20.6863 11.3137 18 8 18C4.68629 18 2 20.6863 2 24C2 27.3137 4.68629 30 8 30Z" fill="currentColor"></path><path d="M24 30C27.3137 30 30 27.3137 30 24C30 20.6863 27.3137 18 24 18C20.6863 18 18 20.6863 18 24C18 27.3137 20.6863 30 24 30Z" fill="currentColor"></path></svg></div></div><nav role="navigation" class="nav_menu padding-global w-nav-menu"><div class="nav_menu-layout container-medium"><div class="nav_menu-layout-left"><div data-cal-link="nomira/lets-talk" data-cal-namespace="lets-talk" data-cal-config="{"layout":"month_view","theme":"dark"}" STYLE="max-width: min-content;" class="cta-2-wrapper"><div class="cta-2-link-wrapper"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 54 54" fill="none" class="cta-2-icon icon-1x1-large"><g clip-path="url(#clip0_38_2261)"><path d="M27 52.3125C40.9797 52.3125 52.3125 40.9797 52.3125 27C52.3125 13.0203 40.9797 1.6875 27 1.6875C13.0203 1.6875 1.6875 13.0203 1.6875 27C1.6875 40.9797 13.0203 52.3125 27 52.3125Z" stroke="currentColor" stroke-width="3.375" stroke-linecap="round" stroke-linejoin="round"></path><path d="M13.5 27H28.6875" stroke="currentColor" stroke-width="3.375" stroke-linecap="round" stroke-linejoin="round"></path><path d="M28.6875 35.4375V18.5625L40.5 27L28.6875 35.4375Z" stroke="currentColor" stroke-width="3.375" stroke-linecap="round" stroke-linejoin="round"></path></g></svg><div class="g_clickable_wrap"><a target="" href="#" class="g_clickable_link w-inline-block"><span class="g_clickable_text screenreader-only">Letâs Talk</span></a><button type="button" class="g_clickable_btn"><span class="g_clickable_text screenreader-only">Letâs Talk</span></button></div></div><div class="cta-2-link-wrapper"><div aria-hidden="true" class="cta-2-text">Letâs Talk</div><div class="g_clickable_wrap"><a target="" href="#" class="g_clickable_link w-inline-block"><span class="g_clickable_text screenreader-only">Letâs Talk</span></a><button type="button" class="g_clickable_btn"><span class="g_clickable_text screenreader-only">Letâs Talk</span></button></div></div></div><div class="nav_menu-brand">
800<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 216 54" fill="none" class="nav_menu-brand-logo"><path d="M211.918 46.7743L210.341 44.4082L215.255 46.5923V52.6591L202.454 53.2658L200.937 47.5629L201.665 47.745C200.29 49.6459 198.713 51.1019 196.933 52.1131C195.194 53.0837 193.253 53.5691 191.109 53.5691C188.157 53.5691 185.811 52.6388 184.072 50.7784C182.332 48.9179 181.463 46.4305 181.463 43.3162C181.463 41.0917 181.948 39.2716 182.919 37.8561C183.89 36.4405 185.366 35.3889 187.348 34.7013C189.37 34.0137 191.938 33.67 195.053 33.67H200.391L199.785 34.2766V32.0926C199.785 30.5557 199.461 29.4636 198.814 28.8165C198.167 28.1289 197.115 27.7852 195.659 27.7852C195.053 27.7852 194.365 27.8256 193.597 27.9065C192.828 27.9874 192.019 28.1289 191.17 28.3312L192.08 27.2391L191.109 31.5466H183.829L183.344 21.415C186.498 20.6061 189.37 19.9994 191.958 19.595C194.587 19.1905 196.953 18.9883 199.057 18.9883C203.384 18.9883 206.6 19.8781 208.703 21.6577C210.846 23.4373 211.918 26.1673 211.918 29.8479V46.7743ZM193.597 41.8602C193.597 42.9926 193.839 43.8218 194.325 44.3475C194.85 44.8733 195.497 45.1362 196.266 45.1362C196.913 45.1362 197.58 44.9744 198.268 44.6509C198.956 44.2869 199.582 43.7813 200.149 43.1342L199.785 44.4689V38.4627L200.452 39.1301H197.055C195.801 39.1301 194.911 39.3323 194.385 39.7368C193.859 40.1412 193.597 40.849 193.597 41.8602Z" fill="currentColor"></path><path d="M153.152 52.6591V46.5923L157.52 44.8936L156.489 46.5923V26.1471L157.702 28.0278L153.152 26.2078V20.141L165.953 19.5343L167.409 25.1158L165.893 24.8124C167.794 22.9115 169.634 21.4757 171.413 20.505C173.193 19.4938 175.074 18.9883 177.056 18.9883C178.35 18.9883 179.745 19.1905 181.242 19.595L180.635 33.1846H173.719L172.687 26.3898L173.719 28.6345C173.557 28.5536 173.375 28.4929 173.173 28.4525C172.971 28.4121 172.768 28.3918 172.566 28.3918C171.676 28.3918 170.766 28.6952 169.836 29.3019C168.906 29.8681 168.198 30.5557 167.713 31.3646L168.623 28.2705V46.5923L167.349 44.8936L172.869 46.5923V52.6591H153.152Z" fill="currentColor"></path><path d="M148.123 45.8649L147.395 44.9548L151.459 46.5929V52.6597H132.652V46.5929L136.717 44.9548L135.989 45.8649V27.1184L136.596 27.7251L132.652 26.2084V20.1416L148.123 19.6562V45.8649Z" fill="currentColor"></path><path d="M108.014 24.1451L106.315 24.6911C108.216 22.7901 110.238 21.3746 112.382 20.4443C114.525 19.4736 116.568 18.9883 118.509 18.9883C121.138 18.9883 123.342 19.8579 125.122 21.597C126.942 23.3362 127.852 25.9853 127.852 29.5445V46.4103L126.881 44.8329L131.189 46.5923V52.6591H115.719V32.2139C115.719 30.7983 115.456 29.767 114.93 29.1199C114.445 28.4323 113.777 28.0885 112.928 28.0885C112.281 28.0885 111.532 28.2907 110.683 28.6952C109.874 29.0996 109.025 29.6861 108.135 30.4545L108.984 27.9065C109.025 28.2301 109.045 28.4929 109.045 28.6952C109.045 28.857 109.045 28.9783 109.045 29.0592V46.4103L108.074 44.8329L112.382 46.5923V52.6591H96.9114V32.2139C96.9114 30.7983 96.6485 29.767 96.1227 29.1199C95.597 28.4323 94.9296 28.0885 94.1207 28.0885C93.4736 28.0885 92.7456 28.2907 91.9366 28.6952C91.1277 29.0592 90.3188 29.585 89.5099 30.2725L90.2379 28.7558V46.6529L89.2673 45.0149L93.5747 46.5923V52.6591H74.7676V46.5923L79.439 44.7722L78.1043 46.4103V26.2078L79.439 28.0278L74.7676 26.2078V20.141L87.5685 19.5343L89.0246 25.1764L87.5079 24.6911C89.3684 22.871 91.3704 21.4757 93.514 20.505C95.6576 19.4938 97.7203 18.9883 99.7022 18.9883C101.482 18.9883 103.1 19.413 104.556 20.2623C106.012 21.0712 107.164 22.3655 108.014 24.1451Z" fill="currentColor"></path><path d="M56.4406 18.9883C61.6176 18.9883 65.6014 20.4241 68.3922 23.2957C71.2233 26.1673 72.6389 30.2928 72.6389 35.672C72.6389 39.4334 71.9311 42.6488 70.5155 45.3182C69.1404 47.9876 67.1586 50.0301 64.5701 51.4457C61.9816 52.8613 58.8875 53.5691 55.2879 53.5691C50.1108 53.5691 46.1472 52.1131 43.3969 49.201C40.6466 46.2889 39.2715 42.1635 39.2715 36.8247C39.2715 33.0633 39.9388 29.8681 41.2735 27.2391C42.6487 24.5697 44.6103 22.5272 47.1583 21.1117C49.7064 19.6961 52.8005 18.9883 56.4406 18.9883ZM55.8945 27.4818C54.3981 27.4818 53.3465 28.1289 52.7398 29.4232C52.1736 30.7174 51.8904 33.0026 51.8904 36.2787C51.8904 39.5548 52.1736 41.8399 52.7398 43.1342C53.3465 44.4284 54.3981 45.0756 55.8945 45.0756C56.9461 45.0756 57.755 44.7924 58.3213 44.2262C58.9279 43.6195 59.3526 42.6691 59.5953 41.3748C59.8784 40.0401 60.02 38.301 60.02 36.1574C60.02 32.9217 59.7166 30.677 59.1099 29.4232C58.5033 28.1289 57.4315 27.4818 55.8945 27.4818Z" fill="currentColor"></path><path d="M0 52.6591V46.5923L4.67144 44.7722L3.33675 46.4103V26.2078L4.67144 28.0278L0 26.2078V20.141L12.801 19.5343L14.257 25.1764L12.7403 24.6911C14.6817 22.7901 16.7242 21.3746 18.8678 20.4443C21.0518 19.4736 23.1348 18.9883 25.1166 18.9883C27.8669 18.9883 30.1723 19.8781 32.0328 21.6577C33.9337 23.4373 34.8842 26.1269 34.8842 29.7265V46.4103L33.9135 44.8329L38.2209 46.5923V52.6591H22.7505V32.2746C22.7505 30.8186 22.4472 29.767 21.8405 29.1199C21.2743 28.4323 20.526 28.0885 19.5958 28.0885C18.8678 28.0885 18.0791 28.2907 17.2297 28.6952C16.4208 29.0592 15.5917 29.585 14.7423 30.2725L15.4704 28.6952V46.6529L14.4997 45.0149L18.2004 46.5923V52.6591H0Z" fill="currentColor"></path><path d="M133.139 10.9809L137.203 16.7444L153.159 7.94752L147.274 0L133.139 10.9809Z" fill="var(--base-color-brand--offwhite)"></path></svg><div class="spacer-small"></div><div>Digital craftsman bridging <br/>strategy and design. <br/>Simply Beyond.<br/></div><div class="spacer-medium"></div><div class="text-weight-xbold">nomÃra by damian müller</div><div class="spacer-xsmall"></div><div>Digital Design & Strategy<br/>Switzerland<br/><a href="mailto:[email protected]?subject=Website%20Request">[email protected]</a></div></div></div><ul role="list" class="nav_menu-list"><li class="nav_menu-list-item"><a href="/#services" class="nav_menu-link w-nav-link">Services</a></li><li class="nav_menu-list-item"><a href="/#process" class="nav_menu-link w-nav-link">Approach</a></li><li class="nav_menu-list-item"><a href="/#why" class="nav_menu-link w-nav-link">Core Values</a></li><li class="nav_menu-list-item"><a href="/#cases" class="nav_menu-link w-nav-link">Cases</a></li><li class="nav_menu-list-item is-last"><div class="spacer-medium"></div><a href="https://www.nomira.ch/" target="_blank" class="nav_menu-link is-secondary w-nav-link">Switch to German.</a></li></ul></div></nav></div></div><div class="custom-cursor-wrapper"><div class="custom-cursor-dropdowns">
800<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 33 33" fill="none" class="cursor-icon icon-1x1-small text-color-alternate"><g clip-path="url(#clip0_54_3766)"><path d="M16.7266 9.24219V23.2422" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linecap="square" class="cursor-icon-vertical-line"></path><path d="M23.7266 16.2422H9.72656" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linecap="square"></path><path d="M16.7266 31.2422C25.0108 31.2422 31.7266 24.5265 31.7266 16.2422C31.7266 7.95792 25.0108 1.24219 16.7266 1.24219C8.44229 1.24219 1.72656 7.95792 1.72656 16.2422C1.72656 24.5265 8.44229 31.2422 16.7266 31.2422Z" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linecap="square"></path></g></svg><div class="cursor-text-wrapper-dropdowns"><div class="cursor-text-dropdowns">more</div><div class="cursor-text-dropdowns">less</div></div></div><div class="custom-cursor-links"><div class="cursor-text-wrapper-links"><div class="cursor-text-link">view Website</div></div></div><div class="custom-cursor-offer"><div class="cursor-text-wrapper-offer"><div class="cursor-text-offer">jetztâ¨anfragen</div></div></div></div><main class="main-wrapper"><header class="section_hero"><div class="padding-global"><div class="container-large"><div class="hero_component u-grid-custom"><div data-gsap-trigger=".section_hero" data-gsap-parallax="strong" class="hero_heading-wrapper u-column-custom"><div class="heading-icon-wrapper position-relativ"><h1 data-initial-hidden="" data-gsap-words="up" data-heading-icon="" class="hero_heading">Imprint.<br/>Privacy</h1><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 51 43" fill="none" data-initial-hidden="" class="heading-icon icon-1x1-large text-color-secondary"><path d="M9.4525 42.6658L0 29.2333L36.4833 0.709961L50.4133 19.4491L9.4525 42.6658Z" fill="currentColor"></path></svg></div></div><div class="hero_bg-img-wrapper"><img class="hero_bg-img" src="https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68ca7ac82e708712f7b3d7c2_header-webflow.webp" data-gsap-parallax="bg" data-gsap-trigger=".section_hero" alt="" sizes="(max-width: 3440px) 100vw, 3440px" loading="eager" srcset="https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68ca7ac82e708712f7b3d7c2_header-webflow.webp 500w, https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68ca7ac82e708712f7b3d7c2_header-webflow.webp 800w, https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68ca7ac82e708712f7b3d7c2_header-webflow.webp 1080w, https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68ca7ac82e708712f7b3d7c2_header-webflow.webp 1600w, https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68ca7ac82e708712f7b3d7c2_header-webflow.webp 2000w, https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68ca7ac82e708712f7b3d7c2_header-webflow.webp 2600w, https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/68ca7ac82e708712f7b3d7c2_header-webflow.webp 3440w"/></div></div></div></div></header><section class="section_privacy-text"><div class="padding-global padding-section-small"><div class="container-large"><div class="privacy-text_component u-grid-custom"><div class="privacy-text_content-wrapper u-column-custom"><h3 class="heading-style-h3">Imprint.</h3><div class="text-rich-text w-richtext"><p>nomÃra by damian müller<br/>Digital Design & Development<br/>Switzerland<br/>E-Mail: <a href="mailto:[email protected]?subject=Anfrage%20via%20Website">[email protected]</a></p><p>â</p><p><strong>Privacy Policy</strong></p><p>Your privacy matters. This website collects only essential data to provide you with the best possible service and user experience.<br/>â</p><p>What we collect:</p><ul role="list"><li>Information you provide through contact forms (name, email, message)</li><li>Analytics data to improve our services (via privacy-friendly tools)</li><li>Technical data necessary for website functionality</li></ul><p>Your rights:<br/>You have the right to access, correct, or delete your personal data at any time. We never sell or share your information with third parties without your explicit consent.</p><p>â<br/>Contact:<br/>For any privacy-related questions or requests, please contact: <a href="mailto:[email protected]">[email protected]</a>â</p><p>For our comprehensive privacy policy and cookie information, please see below and please refer also to our <a href="https://www.nomira.ch/impressum" target="_blank">German legal notice</a>.</p></div><div class="spacer-large"></div><div><div class="w-embed w-script">
800<script src="https://app.privacybee.io/widget.js" defer></script>
800 801<privacybee-widget website-id="cm9oaqy3o001wv4nbirv59mfk" type="dsg" lang="en"></privacybee-widget></div></div></div></div></div></div></section></main><footer class="footer overflow-hidden"><div class="footer_cta-wrapper"><div STYLE="clip-path: polygon(18% 17%, 100% 0%, 100% 100%, 25% 100%)" class="footer_bg-layer background-color-mint"></div><div class="padding-global heigth-100"><div class="container-large heigth-100"><div class="footer_cta-content-wrapper u-grid-custom"><div class="footer_cta-content u-column-custom"><div class="cta-element-wrapper"><div data-gsap-view="left" data-cal-link="nomira/lets-talk" data-cal-namespace="lets-talk" data-cal-config="{"layout":"month_view","theme":"dark"}" class="cta-wrapper"><div class="cta-link-wrapper"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 54 54" fill="none" class="cta-icon"><g clip-path="url(#clip0_38_2261)"><path d="M27 52.3125C40.9797 52.3125 52.3125 40.9797 52.3125 27C52.3125 13.0203 40.9797 1.6875 27 1.6875C13.0203 1.6875 1.6875 13.0203 1.6875 27C1.6875 40.9797 13.0203 52.3125 27 52.3125Z" stroke="currentColor" stroke-width="3.375" stroke-linecap="round" stroke-linejoin="round"></path><path d="M13.5 27H28.6875" stroke="currentColor" stroke-width="3.375" stroke-linecap="round" stroke-linejoin="round"></path><path d="M28.6875 35.4375V18.5625L40.5 27L28.6875 35.4375Z" stroke="currentColor" stroke-width="3.375" stroke-linecap="round" stroke-linejoin="round"></path></g></svg><div class="g_clickable_wrap"><a target="" href="#" class="g_clickable_link w-inline-block"><span class="g_clickable_text screenreader-only">Get in Touch</span></a><button type="button" class="g_clickable_btn"><span class="g_clickable_text screenreader-only">Get in Touch</span></button></div></div><div STYLE="max-width: 12rem;" class="cta-link-wrapper"><div aria-hidden="true" class="cta-text">Get in Touch</div><div class="g_clickable_wrap"><a target="" href="#" class="g_clickable_link w-inline-block"><span class="g_clickable_text screenreader-only">Get in Touch</span></a><button type="button" class="g_clickable_btn"><span class="g_clickable_text screenreader-only">Get in Touch</span></button></div></div></div></div><div class="cta-text-divider background-color-alternate"></div><div class="footer_cta-heading-wrapper"><div class="heading-icon-wrapper position-relativ"><div data-gsap-scrollWords="up" data-heading-icon="" class="cta-heading">Letâs create something extraordinary together.</div><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 51 43" fill="none" class="heading-icon icon-1x1-large text-color-primary"><path d="M9.4525 42.6658L0 29.2333L36.4833 0.709961L50.4133 19.4491L9.4525 42.6658Z" fill="currentColor"></path></svg></div></div></div></div></div></div></div></footer></div>
801<script src="https://d3e54v103j8qbb.cloudfront.net/js/jquery-3.5.1.min.dc5e7f18c8.js?site=68ca7ac82e708712f7b3d78e" type="text/javascript" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
801<script src="https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/js/webflow.schunk.47d6c6fda17ce6cd.js" type="text/javascript" integrity="sha384-Lir0RD5ekpd2MnGnYAQgABmR5JLlqnk+1yuFajhbYJ1UEeYeLoiw4y8/3V7XPtBu" crossorigin="anonymous"></script>
801<script src="https://cdn.prod.website-files.com/68ca7ac82e708712f7b3d78e/js/webflow.a9ba356d.d2f30196489a2709.js" type="text/javascript" integrity="sha384-csGkhjFGgkSz8t16Ud75ydCbjLPF/KwVXVDchd4zvLDKt8Bf0NTemUS5BXjXjjmC" crossorigin="anonymous"></script>
801<!-- Cal element-click embed code begins -->
802<script type="text/javascript"> 803 (function (C, A, L) { let p = function (a, ar) { a.q.push(ar); }; let d = C.document; C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments; if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement("script")).src = A; cal.loaded = true; } if (ar[0] === L) { const api = function () { p(api, arguments); }; const namespace = ar[1]; api.q = api.q || []; if(typeof namespace === "string"){cal.ns[namespace] = cal.ns[namespace] || api;p(cal.ns[namespace], ar);p(cal, ["initNamespace", namespace]);} else p(cal, ar); return;} p(cal, ar); }; })(window, "https://app.cal.com/embed/embed.js", "init"); 804Cal("init", "lets-talk", {origin:"https://cal.com"}); 805 806 807 // Important: Please add the following attributes to the element that should trigger the calendar to open upon clicking. 808 // `data-cal-link="nomira/lets-talk"` 809 // data-cal-namespace="lets-talk" 810 // `data-cal-config='{"layout":"month_view","theme":"dark"}'` 811 812 Cal.ns.lets-talk("ui", {"theme":"dark","cssVarsPerTheme":{"light":{"cal-brand":"#000000"},"dark":{"cal-brand":"#F5F5F5"}},"hideEventTypeDetails":false,"layout":"month_view"}); 813 </script>
813 814 <!-- Cal element-click embed code ends --> 815 816 817<!-- JSON Presets (als globales Objekt oder als inline JSON) -->
818<script type="application/json" id="gsap-presets"> 819{ 820 "init": { 821 "fade": { 822 "startOpacity": 0, 823 "endOpacity": 1, 824 "duration": 1.2, 825 "delay": 0.5, 826 "ease": "power2.out" 827 }, 828 "up": { 829 "startY": 30, 830 "endY": 0, 831 "startOpacity": 0, 832 "endOpacity": 1, 833 "duration": 1, 834 "delay": 0, 835 "ease": "power2.out" 836 }, 837 "left": { 838 "startX": -30, 839 "endX": 0, 840 "startOpacity": 0, 841 "endOpacity": 1, 842 "duration": 1, 843 "delay": 0, 844 "ease": "power2.out" 845 }, 846 "right": { 847 "startX": 30, 848 "endX": 0, 849 "startOpacity": 0, 850 "endOpacity": 1, 851 "duration": 1, 852 "delay": 0, 853 "ease": "power2.out" 854 } 855 }, 856 857 "view": { 858 "fade": { 859 "startOpacity": 0, 860 "endOpacity": 1, 861 "duration": 1, 862 "delay": 0, 863 "ease": "power1.out" 864 }, 865 "up": { 866 "startY": 30, 867 "endY": 0, 868 "startOpacity": 0, 869 "endOpacity": 1, 870 "duration": 1, 871 "delay": 0, 872 "ease": "power1.out" 873 }, 874 "left": { 875 "startX": -30, 876 "endX": 0, 877 "startOpacity": 0, 878 "endOpacity": 1, 879 "duration": 1, 880 "delay": 0, 881 "ease": "power1.out" 882 }, 883 "right": { 884 "startX": 30, 885 "endX": 0, 886 "startOpacity": 0, 887 "endOpacity": 1, 888 "duration": 1, 889 "delay": 0, 890 "ease": "power1.out" 891 } 892 }, 893 894 "words": { 895 "up": { 896 "startY": 30, 897 "endY": 0, 898 "startOpacity": 0, 899 "endOpacity": 1, 900 "duration": 1, 901 "delay": 0, 902 "ease": "power1.out", 903 "stagger": 0.12, 904 "split": true, 905 "markers": false 906 }, 907 "left": { 908 "startX": -30, 909 "endX": 0, 910 "startOpacity": 0, 911 "endOpacity": 1, 912 "duration": 1, 913 "delay": 0, 914 "ease": "power1.out", 915 "stagger": 0.12, 916 "split": true, 917 "markers": true 918 }, 919 "right": { 920 "startX": 30, 921 "endX": 0, 922 "startOpacity": 0, 923 "endOpacity": 1, 924 "duration": 1, 925 "delay": 0, 926 "ease": "power1.out", 927 "stagger": 0.12, 928 "split": true, 929 "markers": false 930 } 931 }, 932 933 "scrollWords": { 934 "up": { 935 "startY": 30, 936 "endY": 0, 937 "startOpacity": 0, 938 "endOpacity": 1, 939 "ease": "none", 940 "scrub": 2, 941 "split": true, 942 "trigger": "", 943 "start": "top 90%", 944 "end": "top 50%", 945 "wordSpread": 0.5, 946 "markers": false, 947 "tablet": { 948 "start": "top 90%", 949 "end": "top 45%", 950 "scrub": 1 951 }, 952 "mobile-l": { 953 "start": "top 90%", 954 "end": "top 45%", 955 "scrub": 1 956 }, 957 "mobile-p": { 958 "start": "top 90%", 959 "end": "top 45%", 960 "scrub": 1 961 } 962 }, 963 "left": { 964 "startX": -30, 965 "endX": 0, 966 "startOpacity": 0, 967 "endOpacity": 1, 968 "ease": "none", 969 "scrub": 2, 970 "split": true, 971 "trigger": "", 972 "start": "top bottom", 973 "end": "top 80%", 974 "wordSpread": 0.5, 975 "markers": false, 976 "tablet": { 977 "start": "top bottom", 978 "end": "top 65%", 979 "scrub": 1 980 }, 981 "mobile-l": { 982 "start": "top bottom", 983 "end": "top 65%", 984 "scrub": 1 985 }, 986 "mobile-p": { 987 "start": "top bottom", 988 "end": "top 65%", 989 "scrub": 1 990 } 991 }, 992 "right": { 993 "startX": 30, 994 "endX": 0, 995 "startOpacity": 0, 996 "endOpacity": 1, 997 "ease": "none", 998 "scrub": 2, 999 "split": true, 1000 "trigger": "", 1001 "start": "top bottom", 1002 "end": "top 70%", 1003 "wordSpread": 0.5, 1004 "markers": false, 1005 "tablet": { 1006 "start": "top 90%", 1007 "end": "top 65%", 1008 "scrub": 0.5 1009 }, 1010 "mobile-l": { 1011 "start": "top 90%", 1012 "end": "top 65%", 1013 "scrub": 0.5 1014 }, 1015 "mobile-p": { 1016 "start": "top 90%", 1017 "end": "top 65%", 1018 "scrub": 0.5 1019 } 1020 } 1021 }, 1022 1023 "parallax": { 1024 "subtle": { 1025 "startY": 0, 1026 "endY": -50, 1027 "ease": "none", 1028 "scrub": 1, 1029 "trigger": "", 1030 "start": "top top", 1031 "end": "bottom top", 1032 "markers": false, 1033 "tablet": { 1034 "endY": -30, 1035 "scrub": 0.2 1036 }, 1037 "mobile-l": { 1038 "endY": -10, 1039 "scrub": 0.1 1040 }, 1041 "exclude": "mobile-p" 1042 }, 1043 "medium": { 1044 "startY": 0, 1045 "endY": -220, 1046 "startOpacity": 1, 1047 "endOpacity": 1, 1048 "ease": "none", 1049 "scrub": 1, 1050 "trigger": "", 1051 "start": "top top", 1052 "end": "bottom top", 1053 "markers": false, 1054 "tablet": { 1055 "endY": -130, 1056 "scrub": 0.5 1057 }, 1058 "mobile-l": { 1059 "endY": -60, 1060 "scrub": 0.3 1061 }, 1062 "mobile-p": { 1063 "endY": -60, 1064 "scrub": 0.3 1065 } 1066 }, 1067 "strong": { 1068 "startY": 0, 1069 "endY": -280, 1070 "ease": "none", 1071 "scrub": 1, 1072 "trigger": "", 1073 "start": "top top", 1074 "end": "bottom top", 1075 "markers": false, 1076 "tablet": { 1077 "endY": -170, 1078 "scrub": 0.5 1079 }, 1080 "mobile-l": { 1081 "endY": -80, 1082 "scrub": 0.3 1083 }, 1084 "mobile-p": { 1085 "endY": -80, 1086 "scrub": 0.3 1087 } 1088 }, 1089 "bg": { 1090 "startY": 0, 1091 "endY": -120, 1092 "ease": "none", 1093 "scrub": 1, 1094 "trigger": "", 1095 "start": "top top", 1096 "end": "bottom top", 1097 "markers": false, 1098 "tablet": { 1099 "endY": -70, 1100 "scrub": 0.5 1101 }, 1102 "mobile-l": { 1103 "endY": -40, 1104 "scrub": 0.3 1105 }, 1106 "mobile-p": { 1107 "endY": -40, 1108 "scrub": 0.3 1109 } 1110 } 1111 } 1112} 1113</script>
1113 1114 1115<!-- GSAP Core -->
1116<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
1116 1117 1118<!-- GSAP ScrollTrigger Plugin -->
1119<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
1119 1120 1121<!-- Split Type Library -->
1122<script src="https://unpkg.com/split-type"></script>
1122 1123 1124<!-- Live Code (Presets in Project Settings beachten)-->
1125<script src="https://cdn.jsdelivr.net/gh/nomira-digital/[email protected]/script.js"></script>
1125 1126 1127<!-- Code Editor
1128<script type=module src="http://127.0.0.1:5500/animations.js"></script>
1128 --><!-- GSAP Core -->
1129<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
1129 1130 1131<!-- GSAP ScrollTrigger Plugin -->
1132<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
1132 1133 1134<!-- Split Type Library -->
1135<script src="https://unpkg.com/split-type"></script>
1135 1136 1137 1138<!-- Live Code (Presets in Project Settings beachten)
1139<script src="https://cdn.jsdelivr.net/gh/nomira-digital/[email protected]/script.js"></script>
1139 --> 1140 1141<!-- Code Editor
1142<script type="module" src="http://127.0.0.1:5500/animations.js"></script>
1142 --> 1143
1144<script> 1145// ================================================================ 1146// =============== GSAP ANIMATION PRESET SYSTEM ================== 1147// ================================================================ 1148// Dieses System ermöglicht die zentrale Verwaltung aller GSAP-Animationen 1149// über ein JSON-Preset-System. 1150// 1. Presets werden in einem HTML-Element mit id="gsap-presets" als JSON definiert 1151// (Siehe Webflow (Projekt/Page)-Settings) oder als globales Objekt im Code Editor (gsapAnimations). 1152// 2. Jeder Top-Level-Schlüssel im JSON (z.B. "init", "view", "words") wird als Animation-Typ erkannt. 1153// 3. Elemente mit data-gsap-[typ]="[animation]" erhalten automatisch alle zugehörigen Parameter. 1154// 4. Neue Animationstypen können durch Erweiterung des JSON hinzugefügt werden, ohne den JS-Code zu ändern. 1155// 5. Falls im Preset bspw. "split": true gesetzt wird, fügt der Code automatisch data-gsap-split="true" hinzu. 1156// 6. Die eigentlichen GSAP-Animationen werden im nächsten Abschnitt initialisiert. 1157// 7. Responsive Breakpoints (desktop, tablet, mobile-l, mobile-p) werden automatisch als data-Attribute gesetzt. 1158// 8. Ausschluss von Animationen per Preset mit "exclude" (z.B. "exclude": "tablet,mobile-p"). 1159 1160(function(){ 1161 // Falls ein globales Objekt "gsapAnimations" definiert wurde, verwende dieses, 1162 // andernfalls suche im DOM nach einem Element mit id "gsap-presets" und parse dessen Inhalt. 1163 let presets; 1164 if (typeof gsapAnimations !== 'undefined') { 1165 presets = gsapAnimations; 1166 } else { 1167 const presetsElement = document.getElementById('gsap-presets'); 1168 if (presetsElement) { 1169 try { 1170 presets = JSON.parse(presetsElement.textContent); 1171 } catch (err) { 1172 console.error('Error parsing GSAP presets:', err); 1173 return; 1174 } 1175 } else { 1176 console.error('No GSAP presets found (neither global variable nor #gsap-presets element).'); 1177 return; 1178 } 1179 } 1180 1181 // Hilfsfunktion, die camelCase in kebab-case umwandelt und den Präfix "data-gsap-" hinzufügt. 1182 function toDataAttr(key) { 1183 return 'data-gsap-' + key.replace(/([A-Z])/g, '-$1').toLowerCase(); 1184 } 1185 1186 // Liste der bekannten Breakpoints 1187 const breakpoints = ['desktop', 'tablet', 'mobile-l', 'mobile-p']; 1188 1189 // Iteriere über alle Top-Level-Presets (z. B. "init", "view", "words") 1190 Object.keys(presets).forEach(function(animationType) { 1191 const presetGroup = presets[animationType]; 1192 const typeAttr = `data-gsap-${animationType}`; // z.B. data-gsap-init 1193 1194 document.querySelectorAll(`[${typeAttr}]`).forEach(function(element) { 1195 const animationName = element.getAttribute(typeAttr); 1196 const preset = presetGroup[animationName]; 1197 1198 if (!preset) { 1199 console.warn(`Animation "${animationName}" not found in ${animationType} category.`); 1200 return; 1201 } 1202 1203 // Falls im Preset "split": true gesetzt ist, füge data-gsap-split="true" hinzu. 1204 if (preset.split === true) { 1205 element.setAttribute('data-gsap-split', 'true'); 1206 } 1207 1208 // Falls im Preset "exclude" gesetzt ist (z.B. "exclude": "tablet,mobile-p"), 1209 // setze data-gsap-exclude entsprechend. 1210 if (preset.exclude) { 1211 element.setAttribute('data-gsap-exclude', preset.exclude); 1212 } 1213 1214 // Ãbertrage alle Parameter aus dem Preset als data-Attribute 1215 Object.entries(preset).forEach(function([key, value]) { 1216 // Ãberspringe Schlüssel, die speziell behandelt werden oder bereits gesetzt sind 1217 if (key === "split" || key === "exclude") return; 1218 // Trigger-Attribut nicht überschreiben, wenn es bereits gesetzt ist 1219 if (key === "trigger" && element.hasAttribute('data-gsap-trigger')) return; 1220 1221 // Prüfen, ob es sich um einen Breakpoint handelt 1222 if (breakpoints.includes(key)) { 1223 // Wenn ja, setze die breakpoint-spezifischen Attribute 1224 if (typeof value === 'object') { 1225 Object.entries(value).forEach(function([bpKey, bpValue]) { 1226 element.setAttribute(toDataAttr(bpKey + '-' + key), bpValue); 1227 }); 1228 } 1229 } else { 1230 // Sonst normales Attribut setzen 1231 element.setAttribute(toDataAttr(key), value); 1232 } 1233 }); 1234 }); 1235 }); 1236})(); 1237 1238// ================================================================ 1239// ========= GSAP: TEXT-SPLITTING & ICON SETUP ================== 1240// ================================================================ 1241// Zentrale Funktion zum Aufteilen von Text und Vorbereiten des Icons. 1242function setupAnimatedHeading(element) { 1243const hasHeadingIcon = element.hasAttribute('data-heading-icon'); 1244let iconElement = null; 1245let splitWords = []; 1246let lastWordElement = null; 1247 1248// 1. Finde Icon (falls vorhanden) - im Elternelement suchen 1249if (hasHeadingIcon) { 1250 const container = element.parentElement; 1251 iconElement = container ? container.querySelector('.heading-icon') : null; 1252 if (iconElement) { 1253 gsap.set(iconElement, { opacity: 0, visibility: "hidden" }); // Initial verstecken 1254 } else { 1255 console.warn("Heading icon element not found for:", element); 1256 } 1257} 1258 1259// 2. Splitte Text mit SplitType, falls noch nicht geschehen 1260const existingSplitWords = element.querySelectorAll('.split-word'); 1261if (existingSplitWords.length > 0) { 1262 splitWords = Array.from(existingSplitWords); 1263 // Stelle sicher, dass der Style korrekt ist 1264 splitWords.forEach(word => { word.style.display = 'inline-block'; }); 1265} else { 1266 new SplitType(element, { 1267 types: 'words', 1268 tagName: 'span', 1269 wordClass: 'split-word' 1270 }); 1271 splitWords = Array.from(element.querySelectorAll('.split-word')); 1272 splitWords.forEach(word => { word.style.display = 'inline-block'; }); 1273} 1274 1275// 3. Identifiziere letztes Wort für Icon-Platzierung (falls Icon vorhanden) 1276if (iconElement && splitWords.length > 0) { 1277 lastWordElement = splitWords[splitWords.length - 1]; 1278 lastWordElement.classList.add('last-word'); 1279 // Das Anhängen des Icons geschieht später, je nach Animationstyp 1280} 1281 1282// Gib die vorbereiteten Elemente zurück 1283return { splitWords, iconElement, lastWordElement }; 1284} 1285 1286 1287// ================================================================ 1288// ============= HEADINGS ICON SETUP OHNE GSAP ==================== 1289// ================================================================ 1290// Dieser Handler kümmert sich um Icons bei Headings OHNE GSAP Wort-Animationen 1291document.addEventListener('DOMContentLoaded', function() { 1292const headings = document.querySelectorAll('[data-heading-icon]'); 1293
1294headings.forEach(function(heading) { 1295 // Ãberspringen, wenn GSAP Wort-Animationen aktiv sind (diese kümmern sich selbst) 1296 if (heading.hasAttribute('data-gsap-words') || heading.hasAttribute('data-gsap-scrollwords')) { 1297 return; 1298 } 1299 1300 // Setup für Text und Icon durchführen 1301 const { iconElement, lastWordElement } = setupAnimatedHeading(heading); 1302 1303 // Icon anhängen und einblenden, falls vorhanden 1304 if (iconElement && lastWordElement) { 1305 lastWordElement.appendChild(iconElement); 1306 // Icon sanft einblenden 1307 gsap.to(iconElement, { 1308 opacity: 1, 1309 visibility: "visible", 1310 duration: 0.3, 1311 delay: 0.1, // Kleine Verzögerung für smoothes Laden 1312 ease: "power1.out" 1313 }); 1314 } 1315}); 1316}); 1317 1318 1319// ================================================================ 1320// ========= AUTOMATISCHE TEXT-AUFTEILUNG FÃR STAGGER ============ 1321// ================================================================ 1322// Dies wird nun hauptsächlich durch setupAnimatedHeading abgedeckt. 1323// Wir behalten diesen Block, um data-gsap-split="true" bei Bedarf zu setzen, 1324// falls Elemente nur gesplittet, aber nicht animiert werden sollen (obwohl das selten sein sollte). 1325(function(){ 1326document.querySelectorAll('[data-gsap-split="true"]').forEach(el => { 1327 // Prüfen ob bereits animiert (words/scrollwords) 1328 if (el.hasAttribute('data-gsap-words') || el.hasAttribute('data-gsap-scrollwords')) { 1329 return; // Wird schon von den Animationsfunktionen behandelt 1330 } 1331 // Nur splitten, wenn nicht schon geschehen und keine GSAP-Animation aktiv ist 1332 if (!el.querySelector('.split-word')) { 1333 new SplitType(el, { 1334 types: 'words', 1335 tagName: 'span', 1336 wordClass: 'split-word' 1337 }); 1338 el.querySelectorAll('.split-word').forEach(word => { 1339 word.style.display = 'inline-block'; 1340 }); 1341 } 1342}); 1343})(); 1344 1345 1346// ================================================================ 1347// =============== GSAP ANIMATION INITIALISIERUNG ================ 1348// ================================================================ 1349// Hier werden die GSAP-Animationen basierend auf den zuvor gesetzten data-Attributen initialisiert. 1350 1351(function(){ 1352// ================================================================ 1353// ================= HILFSFUNKTIONEN ============================= 1354// ================================================================ 1355 1356// Definition der Breakpoints für responsive Animationen 1357const BREAKPOINTS = { 1358 'desktop': { min: 992, max: Infinity, order: 4 }, 1359 'tablet': { min: 768, max: 991, order: 3 }, 1360 'mobile-l': { min: 480, max: 767, order: 2 }, 1361 'mobile-p': { min: 0, max: 479, order: 1 } 1362}; 1363 1364// Cache-Variablen für Breakpoints und responsive Werte 1365let currentBreakpoint = null; 1366const responsiveValueCache = new Map(); 1367 1368// Ermittelt den aktuellen Breakpoint basierend auf der Fensterbreite 1369function getCurrentBreakpoint() { 1370 // Wenn der aktuelle Breakpoint bereits berechnet wurde, verwende ihn 1371 if (currentBreakpoint) return currentBreakpoint; 1372 1373 const width = window.innerWidth; 1374 for (const [name, range] of Object.entries(BREAKPOINTS)) { 1375 if (width >= range.min && width <= range.max) { 1376 currentBreakpoint = name; 1377 return name; 1378 } 1379 } 1380 currentBreakpoint = 'desktop'; // Fallback 1381 return 'desktop'; 1382} 1383 1384// Cache für Element-Werte aufbauen oder aktualisieren 1385function cacheResponsiveValuesForElement(element) { 1386 const cacheKey = element; 1387 if (!responsiveValueCache.has(cacheKey)) { 1388 responsiveValueCache.set(cacheKey, new Map()); 1389 } 1390 return responsiveValueCache.get(cacheKey); 1391} 1392 1393// Prüft, ob die Animation für den aktuellen Breakpoint ausgeschlossen werden soll 1394function shouldExcludeAnimation(element) { 1395 const breakpoint = getCurrentBreakpoint(); 1396 const breakpointOrder = BREAKPOINTS[breakpoint].order; 1397 1398 // Cache für dieses Element abrufen oder erstellen 1399 const elementCache = cacheResponsiveValuesForElement(element); 1400 1401 // Prüfen, ob wir das Ergebnis bereits gecacht haben 1402 const cacheKey = `exclude_${breakpoint}`; 1403 if (elementCache.has(cacheKey)) { 1404 return elementCache.get(cacheKey); 1405 } 1406 1407 // Prüfe auf neue Exclude-Angabe (data-gsap-exclude="tablet,mobile-p") 1408 const excludeValue = element.getAttribute('data-gsap-exclude'); 1409 if (excludeValue) { 1410 const excludedBreakpoints = excludeValue.split(',').map(b => b.trim()); 1411 1412 for (const excludedBreakpoint of excludedBreakpoints) { 1413 // Wenn der angegebene Breakpoint nicht existiert, ignorieren 1414 if (!BREAKPOINTS[excludedBreakpoint]) continue; 1415 1416 // Wenn der aktuelle Breakpoint dem ausgeschlossenen entspricht oder 1417 // einen niedrigeren Ordnungswert hat (kaskadierend nach unten) 1418 if (BREAKPOINTS[excludedBreakpoint].order >= breakpointOrder) { 1419 elementCache.set(cacheKey, true); 1420 return true; 1421 } 1422 } 1423 } 1424 1425 // Prüfe auf altes Format für Abwärtskompatibilität (data-gsap-exclude-tablet) 1426 if (element.hasAttribute(`data-gsap-exclude-${breakpoint}`)) { 1427 elementCache.set(cacheKey, true); 1428 return true; 1429 } 1430 1431 elementCache.set(cacheKey, false); 1432 return false;
1433} 1434 1435// Optimierte Funktion zum Abrufen responsiver Werte mit Caching 1436function getResponsiveValue(element, paramName, defaultValue) { 1437 const breakpoint = getCurrentBreakpoint(); 1438 1439 // Cache für dieses Element abrufen oder erstellen 1440 const elementCache = cacheResponsiveValuesForElement(element); 1441 1442 // Prüfen, ob wir den Wert bereits gecacht haben 1443 const cacheKey = `${paramName}_${breakpoint}`; 1444 if (elementCache.has(cacheKey)) { 1445 return elementCache.get(cacheKey); 1446 } 1447 1448 // 1. Versuche breakpoint-spezifisches Attribut zu finden (z.B. data-gsap-end-y-tablet) 1449 const breakpointValue = element.getAttribute(`data-gsap-${paramName}-${breakpoint}`); 1450 if (breakpointValue !== null) { 1451 const value = isNaN(parseFloat(breakpointValue)) ? breakpointValue : parseFloat(breakpointValue); 1452 elementCache.set(cacheKey, value); 1453 return value; 1454 } 1455 1456 // 2. Fallback auf Standard-Attribut (z.B. data-gsap-end-y) 1457 const standardValue = element.getAttribute(`data-gsap-${paramName}`); 1458 if (standardValue !== null) { 1459 const value = isNaN(parseFloat(standardValue)) ? standardValue : parseFloat(standardValue); 1460 elementCache.set(cacheKey, value); 1461 return value; 1462 } 1463 1464 // 3. Letzer Fallback auf übergebenen Standardwert 1465 elementCache.set(cacheKey, defaultValue); 1466 return defaultValue; 1467} 1468 1469// Prüft, ob das Element seine Opacity animieren soll (von 0 zu 1) 1470// Elemente mit data-initial-hidden brauchen Opacity-Animation 1471function shouldAnimateOpacity(element) { 1472 return element.hasAttribute('data-initial-hidden'); 1473} 1474 1475// Hilfsfunktion zum Ermitteln des Trigger-Elements 1476function getTriggerElement(el) { 1477 const triggerSelector = el.getAttribute('data-gsap-trigger'); 1478 if (!triggerSelector) return el; // Wenn kein Trigger angegeben, nutze das Element selbst 1479 const triggerElement = document.querySelector(triggerSelector); 1480 return triggerElement || el; // Fallback auf das Element selbst, wenn Selector nicht gefunden 1481} 1482 1483// Hilfsfunktion zum Ermitteln der Start- und Endpositionen 1484function getScrollPositions(el) { 1485 return { 1486 start: getResponsiveValue(el, 'start', "top bottom"), 1487 end: getResponsiveValue(el, 'end', "bottom top") 1488 }; 1489} 1490 1491// Hilfsfunktion zur Ãberprüfung von data-gsap-markers 1492function hasMarkers(element) { 1493 return element.getAttribute('data-gsap-markers') === 'true'; 1494} 1495 1496// Erkennt iOS-Geräte 1497function isIOS() { 1498 return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; 1499} 1500 1501// Optimierungen für ScrollTrigger, besonders für iOS 1502function optimizeScrollTrigger() { 1503 // Grundlegende Optimierungen für alle Geräte 1504 ScrollTrigger.config({ 1505 // Ignoriert mobile resize events, die durch das Ein-/Ausblenden 1506 // der Adressleiste verursacht werden (wichtig für iOS) 1507 ignoreMobileResize: true 1508 }); 1509 1510 // Zusätzliche iOS-spezifische Optimierungen 1511 if (isIOS()) { 1512 // Gibt iOS mehr Zeit, Momentum-Scroll zu initialisieren 1513 ScrollTrigger.config({ 1514 // Reduziert die Anzahl der Auto-Refreshes (für bessere Performance) 1515 autoRefreshEvents: "visibilitychange,DOMContentLoaded,load", 1516 // Vermeidet Probleme mit der iOS-Tastatur 1517 syncInterval: 150 1518 }); 1519 } 1520} 1521 1522// Optimierter Event-Listener für responsives Verhalten 1523function setupResponsiveListeners() { 1524 // Cache für den aktuellen Breakpoint 1525 currentBreakpoint = getCurrentBreakpoint(); 1526 1527 // Variable für Timeout-ID 1528 let resizeTimeout; 1529 1530 // Resize-Handler mit Debounce 1531 window.addEventListener('resize', function() { 1532 // Debounce für bessere Performance 1533 clearTimeout(resizeTimeout); 1534 resizeTimeout = setTimeout(function() { 1535 // Prüfen, ob sich der Breakpoint tatsächlich geändert hat 1536 const newBreakpoint = getCurrentBreakpoint(); 1537 if (newBreakpoint !== currentBreakpoint) {
1538 // Breakpoint hat sich geändert, Cache leeren 1539 currentBreakpoint = newBreakpoint; 1540 responsiveValueCache.clear(); 1541 1542 // Forciere Aktualisierung aller ScrollTrigger-Instanzen 1543 ScrollTrigger.refresh(); 1544 } 1545 }, 250); 1546 }); 1547 1548 // Spezielle Behandlung für Orientierungswechsel (wichtig für Mobile) 1549 window.addEventListener('orientationchange', function() { 1550 // Nach Orientierungswechsel mehr Zeit geben, damit sich das Gerät anpassen kann 1551 setTimeout(function() { 1552 // Breakpoint neu ermitteln und Cache leeren 1553 currentBreakpoint = getCurrentBreakpoint(); 1554 responsiveValueCache.clear(); 1555 1556 // ScrollTrigger aktualisieren 1557 ScrollTrigger.refresh(); 1558 }, 350); // Längere Verzögerung für zuverlässigere Aktualisierung 1559 }); 1560} 1561 1562// Initialisiert alle Optimierungen 1563function initOptimizations() { 1564 // ScrollTrigger optimieren 1565 optimizeScrollTrigger(); 1566 1567 // Responsive Listener einrichten 1568 setupResponsiveListeners(); 1569} 1570 1571// ================================================================ 1572// ================= ANIMATIONS-IMPLEMENTIERUNGEN ================= 1573// ================================================================ 1574 1575// ============================================================== 1576// ==================== INIT ANIMATIONEN ======================= 1577// ============================================================== 1578document.querySelectorAll('[data-gsap-init]').forEach(function(el){ 1579 // Prüfen, ob die Animation für den aktuellen Breakpoint ausgeschlossen werden soll 1580 if (shouldExcludeAnimation(el)) return; 1581 1582 // Parameter mit responsiver Unterstützung auslesen 1583 const startOpacity = getResponsiveValue(el, 'start-opacity', 0); 1584 const endOpacity = getResponsiveValue(el, 'end-opacity', 1); 1585 const startY = getResponsiveValue(el, 'start-y', 0); 1586 const startX = getResponsiveValue(el, 'start-x', 0); 1587 const duration = getResponsiveValue(el, 'duration', 1); 1588 const delay = getResponsiveValue(el, 'delay', 0); 1589 const ease = getResponsiveValue(el, 'ease', "power2.out"); 1590 const initType = el.getAttribute('data-gsap-init'); 1591 1592 // Prüfen, ob das Element initial hidden ist und Opacity animiert werden soll 1593 const shouldAnimate = shouldAnimateOpacity(el); 1594 1595 // From-Konfiguration 1596 const fromConfig = { 1597 visibility: "hidden" // Immer von hidden starten 1598 }; 1599 1600 // Opacity nur setzen, wenn das Element initial hidden ist 1601 if (shouldAnimate) { 1602 fromConfig.opacity = startOpacity; 1603 } 1604 1605 // To-Konfiguration 1606 const toConfig = { 1607 visibility: "visible", // Immer zu visible animieren 1608 duration: duration, 1609 delay: delay, 1610 ease: ease 1611 }; 1612 1613 // Opacity nur setzen, wenn das Element initial hidden ist 1614 if (shouldAnimate) { 1615 toConfig.opacity = endOpacity; 1616 } 1617 1618 // Y-Position hinzufügen 1619 if (initType === 'up') { 1620 fromConfig.y = startY; 1621 toConfig.y = 0; 1622 } 1623 1624 // X-Position hinzufügen 1625 if (initType === 'left' || initType === 'right') { 1626 fromConfig.x = startX; 1627 toConfig.x = 0; 1628 } 1629 1630 // Animation mit fromTo durchführen 1631 gsap.fromTo(el, fromConfig, toConfig); 1632}); 1633 1634// ============================================================== 1635// ==================== VIEW ANIMATIONEN ======================= 1636// ============================================================== 1637document.querySelectorAll('[data-gsap-view]').forEach(function(el){ 1638 // Wenn das Element auch init hat, überspringen wir es hier 1639 if (el.hasAttribute('data-gsap-init')) return; 1640 1641 // Prüfen, ob die Animation für den aktuellen Breakpoint ausgeschlossen werden soll 1642 if (shouldExcludeAnimation(el)) return; 1643 1644 // Parameter mit responsiver Unterstützung auslesen 1645 const startOpacity = getResponsiveValue(el, 'start-opacity', 0); 1646 const endOpacity = getResponsiveValue(el, 'end-opacity', 1); 1647 const startY = getResponsiveValue(el, 'start-y', 0); 1648 const endY = getResponsiveValue(el, 'end-y', 0); 1649 const startX = getResponsiveValue(el, 'start-x', 0); 1650 const endX = getResponsiveValue(el, 'end-x', 0); 1651 const duration = getResponsiveValue(el, 'duration', 1);
1652 const delay = getResponsiveValue(el, 'delay', 0); 1653 const ease = getResponsiveValue(el, 'ease', "power2.out"); 1654 const viewType = el.getAttribute('data-gsap-view'); 1655 1656 // Prüfen, ob das Element initial hidden ist und Opacity animiert werden soll 1657 const shouldAnimate = shouldAnimateOpacity(el); 1658 1659 // From-Konfiguration 1660 const fromConfig = { 1661 visibility: "hidden" // Immer von hidden starten 1662 }; 1663 1664 // Opacity nur setzen, wenn das Element initial hidden ist 1665 if (shouldAnimate) { 1666 fromConfig.opacity = startOpacity; 1667 } 1668 1669 // To-Konfiguration 1670 const toConfig = { 1671 visibility: "visible", // Immer zu visible animieren 1672 duration: duration, 1673 delay: delay, 1674 ease: ease 1675 }; 1676 1677 // Opacity nur setzen, wenn das Element initial hidden ist 1678 if (shouldAnimate) { 1679 toConfig.opacity = endOpacity; 1680 } 1681 1682 // Y-Position hinzufügen 1683 if (viewType === 'up') { 1684 fromConfig.y = startY; 1685 toConfig.y = endY; 1686 } 1687 1688 // X-Position hinzufügen 1689 if (viewType === 'left' || viewType === 'right') { 1690 fromConfig.x = startX; 1691 toConfig.x = endX; 1692 } 1693 1694 // ScrollTrigger-Konfiguration 1695 const triggerElement = getTriggerElement(el); 1696 const { start: startPos } = getScrollPositions(el); 1697 toConfig.scrollTrigger = { 1698 trigger: triggerElement, 1699 start: startPos, 1700 markers: hasMarkers(el) 1701 }; 1702 1703 // Animation durchführen 1704 gsap.fromTo(el, fromConfig, toConfig); 1705}); 1706 1707// ============================================================== 1708// ==================== PARALLAX ANIMATIONEN ================== 1709// ============================================================== 1710document.querySelectorAll('[data-gsap-parallax]').forEach(function(el){ 1711 // Prüfen, ob die Animation für den aktuellen Breakpoint ausgeschlossen werden soll 1712 if (shouldExcludeAnimation(el)) return; 1713 1714 // Parameter mit responsiver Unterstützung auslesen 1715 const startY = getResponsiveValue(el, 'start-y', 0); 1716 const endY = getResponsiveValue(el, 'end-y', 0); 1717 const scrub = getResponsiveValue(el, 'scrub', 1); 1718 const ease = getResponsiveValue(el, 'ease', "none"); 1719 1720 // Trigger und Positionen ermitteln 1721 const triggerElement = getTriggerElement(el); 1722 const { start: startPos, end: endPos } = getScrollPositions(el); 1723 1724 // Einfache TO-Animation - vom Standard-DOM-Zustand zum Zielwert 1725 gsap.to(el, { 1726 y: endY, 1727 scrollTrigger: { 1728 trigger: triggerElement || el, 1729 start: startPos, 1730 end: endPos, 1731 scrub: scrub, 1732 markers: hasMarkers(el), 1733 ease: ease, 1734 // Für iOS optimierte Einstellungen 1735 ...(isIOS() && { 1736 invalidateOnRefresh: true, // Besser für Orientierungswechsel 1737 fastScrollEnd: true, // Verbessert das Ende von schnellem Scrollen 1738 preventOverlaps: true // Verhindert Ãberlappungen bei schnellen Scrolls 1739 }) 1740 } 1741 }); 1742}); 1743 1744// ============================================================== 1745// ==================== WORDS ANIMATIONEN ===================== 1746// ============================================================== 1747document.querySelectorAll('[data-gsap-words]').forEach(function(el){ 1748 // Prüfen, ob die Animation für den aktuellen Breakpoint ausgeschlossen werden soll 1749 if (shouldExcludeAnimation(el)) return; 1750 1751 // Parameter mit responsiver Unterstützung auslesen 1752 const startOpacity = getResponsiveValue(el, 'start-opacity', 0); 1753 const endOpacity = getResponsiveValue(el, 'end-opacity', 1); 1754 const startY = getResponsiveValue(el, 'start-y', 50); 1755 const endY = getResponsiveValue(el, 'end-y', 0); 1756 const startX = getResponsiveValue(el, 'start-x', 0); 1757 const endX = getResponsiveValue(el, 'end-x', 0); 1758 const duration = getResponsiveValue(el, 'duration', 1); 1759 const delay = getResponsiveValue(el, 'delay', 0); 1760 const staggerTime = getResponsiveValue(el, 'stagger', 0.1); 1761 const ease = getResponsiveValue(el, 'ease', "power2.out"); 1762 const wordsType = el.getAttribute('data-gsap-words'); 1763 1764 // Text splitten und Icon vorbereiten 1765 const { splitWords, iconElement, lastWordElement } = setupAnimatedHeading(el); 1766 1767 // Wenn keine Wörter gefunden wurden, abbrechen 1768 if (splitWords.length === 0) {
1769 console.warn("No words found to animate for:", el); 1770 return; 1771 } 1772 1773 // Prüfen, ob der Container opacity: 0 hat und animiert werden soll 1774 const shouldAnimate = shouldAnimateOpacity(el); 1775 1776 // 1. Wörter immer auf ihren Startzustand setzen 1777 const wordInitialState = { 1778 opacity: startOpacity, 1779 visibility: "hidden" 1780 }; 1781 1782 if (wordsType === 'up') { 1783 wordInitialState.y = startY; 1784 } else if (wordsType === 'left' || wordsType === 'right') { 1785 wordInitialState.x = startX; 1786 } 1787 1788 // Wörter auf initialen Zustand setzen 1789 gsap.set(splitWords, wordInitialState); 1790 1791 // 2. Container vorbereiten 1792 if (shouldAnimate) { 1793 // Container mit data-initial-hidden: Container wird transparent starten 1794 gsap.set(el, { visibility: "hidden" }); 1795 } else { 1796 // Container ohne data-initial-hidden: Sofort sichtbar machen 1797 gsap.set(el, { visibility: "visible" }); 1798 } 1799 1800 // 3. Wort-Animation konfigurieren 1801 const wordAnimConfig = { 1802 opacity: endOpacity, 1803 visibility: "visible", 1804 duration: duration, 1805 delay: delay, 1806 ease: ease, 1807 stagger: staggerTime, 1808 onComplete: function() { 1809 // Icon-Handling nach Abschluss der Wort-Animation 1810 if (iconElement && lastWordElement) { 1811 if (!lastWordElement.contains(iconElement)) { 1812 lastWordElement.appendChild(iconElement); 1813 } 1814 // Icon einblenden 1815 gsap.to(iconElement, { 1816 opacity: 1, 1817 visibility: "visible", 1818 duration: 0.3, 1819 ease: "power1.out" 1820 }); 1821 } 1822 } 1823 }; 1824 1825 // Y/X-Position hinzufügen 1826 if (wordsType === 'up') { 1827 wordAnimConfig.y = endY; 1828 } else if (wordsType === 'left' || wordsType === 'right') { 1829 wordAnimConfig.x = endX; 1830 } 1831 1832 // 4. ScrollTrigger vorbereiten, falls benötigt 1833 let scrollTriggerInstance = null; 1834 if (el.hasAttribute('data-gsap-trigger') || el.hasAttribute('data-gsap-start')) { 1835 const triggerElement = getTriggerElement(el); 1836 const { start: startPos } = getScrollPositions(el); 1837 1838 const scrollTriggerConfig = { 1839 trigger: triggerElement, 1840 start: startPos, 1841 markers: hasMarkers(el) 1842 }; 1843 1844 scrollTriggerInstance = ScrollTrigger.create(scrollTriggerConfig); 1845 wordAnimConfig.scrollTrigger = scrollTriggerInstance; 1846 } 1847 1848 // 5. Container-Animation, falls benötigt 1849 if (shouldAnimate) { 1850 // Container von transparent zu sichtbar animieren 1851 const containerConfig = { 1852 opacity: 1, 1853 visibility: "visible", 1854 duration: duration, 1855 delay: delay, 1856 ease: ease 1857 }; 1858 1859 // Falls ScrollTrigger existiert, auch für Container verwenden 1860 if (scrollTriggerInstance) { 1861 containerConfig.scrollTrigger = scrollTriggerInstance; 1862 } 1863 1864 // Container-Animation starten 1865 gsap.to(el, containerConfig); 1866 } 1867 1868 // 6. Wort-Animation starten 1869 gsap.to(splitWords, wordAnimConfig); 1870}); 1871 1872// ============================================================== 1873// ==================== SCROLL WORDS ANIMATIONEN =============== 1874// ============================================================== 1875document.querySelectorAll('[data-gsap-scrollwords]').forEach(function(el) { 1876 // Prüfen, ob die Animation für den aktuellen Breakpoint ausgeschlossen werden soll 1877 if (shouldExcludeAnimation(el)) return; 1878 1879 // Parameter mit responsiver Unterstützung auslesen 1880 const startY = getResponsiveValue(el, 'start-y', 30); 1881 const endY = getResponsiveValue(el, 'end-y', 0); 1882 const startX = getResponsiveValue(el, 'start-x', 0); 1883 const endX = getResponsiveValue(el, 'end-x', 0); 1884 const startOpacity = getResponsiveValue(el, 'start-opacity', 0); 1885 const endOpacity = getResponsiveValue(el, 'end-opacity', 1); 1886 const scrub = getResponsiveValue(el, 'scrub', 2); 1887 const wordSpread = getResponsiveValue(el, 'word-spread', 0.5); 1888 const ease = getResponsiveValue(el, 'ease', "none"); 1889 const scrollWordsType = el.getAttribute('data-gsap-scrollwords'); 1890 1891 // Trigger und Positionen ermitteln 1892 const triggerElement = getTriggerElement(el); 1893 const { start: startPos, end: endPos } = getScrollPositions(el); 1894 1895 // Text splitten und Icon vorbereiten 1896 const { splitWords, iconElement, lastWordElement } = setupAnimatedHeading(el); 1897 1898 // Wenn keine Wörter gefunden wurden, abbrechen 1899 if (splitWords.length === 0) {
1900 console.warn("No words found to animate for scroll:", el); 1901 return; 1902 } 1903 1904 // Prüfen, ob der Container opacity: 0 hat und animiert werden soll 1905 const shouldAnimate = shouldAnimateOpacity(el); 1906 1907 // 1. Wörter auf Startzustand setzen 1908 const wordInitialState = { 1909 opacity: startOpacity, 1910 visibility: "hidden" 1911 }; 1912 1913 if (scrollWordsType === 'up') { 1914 wordInitialState.y = startY; 1915 } else if (scrollWordsType === 'left') { 1916 wordInitialState.x = startX; 1917 } else if (scrollWordsType === 'right') { 1918 wordInitialState.x = startX; 1919 } 1920 1921 // Wörter auf initialen Zustand setzen 1922 gsap.set(splitWords, wordInitialState); 1923 1924 // 2. Container vorbereiten 1925 if (shouldAnimate) { 1926 // Container mit data-initial-hidden: Container wird transparent starten 1927 gsap.set(el, { visibility: "hidden" }); 1928 } else { 1929 // Container ohne data-initial-hidden: Sofort sichtbar machen 1930 gsap.set(el, { visibility: "visible" }); 1931 } 1932 1933 // 3. Timeline erstellen 1934 const tl = gsap.timeline({ 1935 scrollTrigger: { 1936 trigger: triggerElement, 1937 start: startPos, 1938 end: endPos, 1939 scrub: scrub, 1940 markers: hasMarkers(el), 1941 // Für iOS optimierte Einstellungen 1942 ...(isIOS() && { 1943 invalidateOnRefresh: true, 1944 fastScrollEnd: true, 1945 preventOverlaps: true 1946 }) 1947 } 1948 }); 1949 1950 // 4. Container-Animation hinzufügen, falls nötig 1951 if (shouldAnimate) { 1952 // Container mit data-initial-hidden: von 0 auf 1 animieren 1953 tl.to(el, { 1954 opacity: 1, 1955 visibility: "visible", 1956 ease: ease 1957 }, 0); // Am Anfang der Timeline (bei Zeit 0) 1958 } else { 1959 // Container ohne data-initial-hidden: nur visibility sicherstellen 1960 tl.set(el, { visibility: "visible" }, 0); 1961 } 1962 1963 // 5. Wort-Animationen hinzufügen 1964 splitWords.forEach((word, index) => { 1965 // Animation für dieses Wort 1966 const wordAnimation = { 1967 opacity: endOpacity, 1968 visibility: "visible", 1969 ease: ease 1970 }; 1971 1972 // Y/X-Position hinzufügen 1973 if (scrollWordsType === 'up') { 1974 wordAnimation.y = endY; 1975 } else if (scrollWordsType === 'left') { 1976 wordAnimation.x = endX; 1977 } else if (scrollWordsType === 'right') { 1978 wordAnimation.x = endX; 1979 } 1980 1981 // Zeitpunkt in der Timeline berechnen 1982 const startTime = (splitWords.length > 1) ? 1983 index * wordSpread / (splitWords.length - 1) : 0; 1984 1985 // Wort zur Timeline hinzufügen 1986 tl.fromTo( 1987 word, 1988 { 1989 opacity: startOpacity, 1990 visibility: "hidden", 1991 y: wordInitialState.y, 1992 x: wordInitialState.x 1993 }, 1994 wordAnimation, 1995 startTime 1996 ); 1997 1998 // Icon-Logik für das letzte Wort 1999 if (index === splitWords.length - 1 && iconElement && lastWordElement) { 2000 const iconAttachTime = startTime; 2001 const iconFadeInDelay = 0.05; // Kleine Verzögerung 2002 const iconFadeInStartTime = iconAttachTime + iconFadeInDelay; 2003 2004 // Icon anhängen 2005 tl.call(() => { 2006 if (!lastWordElement.contains(iconElement)) { 2007 lastWordElement.appendChild(iconElement); 2008 } 2009 }, null, iconAttachTime); 2010 2011 // Icon einblenden 2012 tl.fromTo( 2013 iconElement, 2014 { opacity: 0, visibility: "hidden" }, 2015 { opacity: 1, visibility: "visible", ease: ease }, 2016 iconFadeInStartTime 2017 ); 2018 } 2019 }); 2020}); 2021 2022// Alte Resize-Listener durch optimierte Version ersetzen 2023// und die initialen Optimierungen anwenden 2024initOptimizations(); 2025 2026})(); // Ende der IIFE für GSAP Animation Initialisierung 2027 2028 2029// ================================================================ 2030// =================== CUSTOM CURSOR SETUP ======================== 2031// ================================================================ 2032document.addEventListener('DOMContentLoaded', () => { 2033 // Nur auf Desktop aktivieren 2034 const isMobile = window.matchMedia('(max-width: 991px)').matches; 2035 if (isMobile) return; 2036 2037 // Elemente finden 2038 const cursorDropdowns = document.querySelector('.custom-cursor-dropdowns'); 2039 const cursorLinks = document.querySelector('.custom-cursor-links'); 2040 if (!cursorDropdowns && !cursorLinks) return; 2041 2042 const dropdowns = document.querySelectorAll('.services_dropdown, .approach_dropdown'); 2043 const workItems = document.querySelectorAll('.work_item'); 2044 const workImgWrappers = document.querySelectorAll('.work_img-wrapper'); 2045 2046 // =================== CURSOR VARIABLES & SETUP ===================== 2047 // Aktuelle Hover-Information 2048 let currentDropdown = null; 2049 let currentWorkItem = null; 2050 let dropdownRect = null; 2051 let workItemRect = null; 2052 2053 // Tracker für Mausbewegung 2054 let lastMouseX = 0; 2055 let lastMouseY = 0; 2056 let hasMouseMoved = false;
2057 let cursorIsReady = false; 2058 2059 // Verzögerungsvariablen und Status-Tracker 2060 let dropdownLeaveTimeout = null; 2061 let isHoveringDropdown = false; 2062 2063 // =================== PHYSIKBASIERTE ANIMATION =================== 2064 // Für Beschleunigungseffekt 2065 let targetX = 0; 2066 let currentX = 0; 2067 let currentSkew = 0; 2068 let skewVelocity = 0; 2069 2070 // =================== CURSOR INITIALIZATION ====================== 2071 // Initial GSAP setup für den Cursor (mit Opacity 0) 2072 if (cursorDropdowns) { 2073 gsap.set(cursorDropdowns, { 2074 xPercent: -50, 2075 yPercent: -50, 2076 transformOrigin: 'center center', 2077 opacity: 0, 2078 visibility: 'hidden' 2079 }); 2080 } 2081 2082 if (cursorLinks) { 2083 gsap.set(cursorLinks, { 2084 xPercent: -50, 2085 yPercent: -50, 2086 transformOrigin: 'center center', 2087 opacity: 0, 2088 visibility: 'hidden' 2089 }); 2090 } 2091 2092 // Setup für Text-Elemente 2093 let cursorTextWrapper = null; 2094 let cursorTextElements = []; 2095 let cursorVerticalLine = null; 2096 2097 if (cursorDropdowns) { 2098 cursorTextWrapper = cursorDropdowns.querySelector('.cursor-text-wrapper-dropdowns'); 2099 cursorTextElements = cursorDropdowns.querySelectorAll('.cursor-text-dropdowns'); 2100 cursorVerticalLine = cursorDropdowns.querySelector('.cursor-icon-vertical-line'); 2101 } 2102 2103 if (cursorDropdowns && (!cursorTextWrapper || cursorTextElements.length === 0)) { 2104 console.warn('Cursor text elements not found'); 2105 // Nicht komplett returnen, damit links-cursor noch funktioniert 2106 } 2107 2108 // Hilfsfunktion: Cursor nur anzeigen, wenn er an der richtigen Position ist 2109 function showCursorWhenReady(cursorElement, x, y) { 2110 if (!cursorElement || !hasMouseMoved) return; 2111 2112 // Cursor an die aktuelle Mausposition setzen 2113 gsap.set(cursorElement, { 2114 x: x, 2115 y: y 2116 }); 2117 2118 // Cursor einblenden, erst wenn die Maus bewegt wurde 2119 if (!cursorElement.classList.contains('active')) return; 2120 2121 if (!cursorIsReady) { 2122 // Verzögertes Einblenden, um sicherzustellen, dass der Cursor an der richtigen Position ist 2123 setTimeout(() => { 2124 gsap.to(cursorElement, { 2125 opacity: 1, 2126 visibility: 'visible', 2127 duration: 0.2, 2128 ease: 'power2.out' 2129 }); 2130 cursorIsReady = true; 2131 }, 20); 2132 } 2133 } 2134 2135 // =================== MOUSEMOVE EVENT HANDLING =================== 2136 // Mousemove Event für Cursor-Positionierung 2137 document.addEventListener('mousemove', function(e) { 2138 // Setze hasMouseMoved auf true, sobald die Maus bewegt wurde 2139 hasMouseMoved = true; 2140 2141 // Speichern der aktuellen Mausposition 2142 lastMouseX = e.clientX; 2143 lastMouseY = e.clientY; 2144 2145 // Parallax für Work Item Images 2146 if (workImgWrappers.length > 0) { 2147 // Berechne relative Position (0 bis 1) basierend auf Viewport-Breite 2148 const relativeX = e.clientX / window.innerWidth; 2149 2150 // Berechne Verschiebung (von -8rem bis +8rem) 2151 targetX = (relativeX - 0.5) * 128; // 0.5 ist die Mitte, 128 ist die maximale Verschiebung in PX 2152 2153 // Wenn über einem Dropdown, füge Rotation hinzu 2154 if (currentDropdown && dropdownRect) { 2155 // Relative Positionen berechnen (0 bis 1) 2156 const relativeX = (e.clientX - dropdownRect.left) / dropdownRect.width; 2157 2158 // Rotation 2159 const rotationDeg = 15 + (relativeX * -30); 2160 2161 // Animation mit GSAP 2162 gsap.to(cursorDropdowns, { 2163 duration: 0.2, 2164 x: e.clientX, 2165 y: e.clientY, 2166 rotation: rotationDeg, 2167 ease: "power3.out" 2168 }); 2169 2170 // Cursor anzeigen, wenn er bereit ist 2171 showCursorWhenReady(cursorDropdowns, e.clientX, e.clientY); 2172 2173 } else if (currentWorkItem && workItemRect && cursorLinks) { 2174 // Relative Positionen berechnen für Work Step 2175 const relativeX = (e.clientX - workItemRect.left) / workItemRect.width; 2176 2177 // Rotation 2178 const rotationDeg = 15 + (relativeX * -30); 2179 2180 // Animation mit GSAP für Links-Cursor 2181 gsap.to(cursorLinks, { 2182 duration: 0.2, 2183 x: e.clientX, 2184 y: e.clientY, 2185 rotation: rotationDeg, 2186 ease: "power3.out" 2187 }); 2188 2189 // Cursor anzeigen, wenn er bereit ist 2190 showCursorWhenReady(cursorLinks, e.clientX, e.clientY); 2191 2192 } else { 2193 // Normale Bewegung ohne Rotation - Cursor verstecken 2194 if (cursorDropdowns) { 2195 gsap.to(cursorDropdowns, { 2196 duration: 0.2, 2197 x: e.clientX, 2198 y: e.clientY, 2199 rotation: 0, 2200 ease: "power3.out", 2201 opacity: 0, 2202 visibility: 'hidden' 2203 }); 2204 cursorIsReady = false;
2205 } 2206 2207 if (cursorLinks) { 2208 gsap.to(cursorLinks, { 2209 duration: 0.2, 2210 x: e.clientX, 2211 y: e.clientY, 2212 rotation: 0, 2213 ease: "power3.out", 2214 opacity: 0, 2215 visibility: 'hidden' 2216 }); 2217 cursorIsReady = false; 2218 } 2219 } 2220 } 2221 }); 2222 2223 // =================== DROPDOWN INTERACTION ====================== 2224 // Hover Events für Dropdowns 2225 dropdowns.forEach(dropdown => { 2226 const checkbox = dropdown.querySelector('.services_dropdown-checkbox, .approach_dropdown-checkbox'); 2227 if (!checkbox) return; 2228 2229 // Hover Events 2230 dropdown.addEventListener('mouseenter', () => { 2231 // Wenn ein Timeout zum Ausblenden gesetzt war, abbrechen 2232 if (dropdownLeaveTimeout) { 2233 clearTimeout(dropdownLeaveTimeout); 2234 dropdownLeaveTimeout = null; 2235 } 2236 2237 isHoveringDropdown = true; 2238 cursorDropdowns.classList.add('active'); 2239 currentDropdown = dropdown; 2240 dropdownRect = dropdown.getBoundingClientRect(); 2241 2242 // Text basierend auf dem aktuellen Zustand dieses spezifischen Dropdowns aktualisieren 2243 updateCursorTextState(checkbox.checked); 2244 2245 // Vertikale Linie basierend auf Checkbox-Status ein-/ausblenden 2246 if (cursorVerticalLine) { 2247 // Gleiche Animation wie beim Click verwenden 2248 if (checkbox.checked) { 2249 // Nach oben ausblenden (wenn geöffnet) 2250 gsap.to(cursorVerticalLine, { 2251 opacity: 0, 2252 y: "-30%", // Hochanimieren 2253 duration: 0.15, 2254 ease: "power2.out" // Etwas smoothere Easing-Funktion 2255 }); 2256 } else { 2257 // Direkt an der aktuellen Position wieder einblenden 2258 gsap.to(cursorVerticalLine, { 2259 opacity: 1, 2260 y: 0, // Zurück zur Ausgangsposition 2261 duration: 0.15, 2262 ease: "power2.out" // Etwas smoothere Easing-Funktion 2263 }); 2264 } 2265 } 2266 2267 // Cursor nur anzeigen, wenn die Maus bereits bewegt wurde 2268 if (hasMouseMoved) { 2269 // Cursor an die aktuelle Mausposition setzen und dann erst einblenden 2270 showCursorWhenReady(cursorDropdowns, lastMouseX, lastMouseY); 2271 } 2272 }); 2273 2274 dropdown.addEventListener('mouseleave', () => { 2275 // Verzögern des Ausblendens, um Flackern zu vermeiden 2276 dropdownLeaveTimeout = setTimeout(() => { 2277 // Nur ausblenden, wenn wir nicht über einem anderen Dropdown sind 2278 if (!isHoveringDropdown) { 2279 cursorDropdowns.classList.remove('active'); 2280 currentDropdown = null; 2281 dropdownRect = null; 2282 2283 // Cursor ausblenden mit etwas Verzögerung 2284 gsap.to(cursorDropdowns, { 2285 opacity: 0, 2286 visibility: 'hidden', 2287 duration: 0.2, 2288 ease: "power3.out" 2289 }); 2290 cursorIsReady = false; 2291 } 2292 2293 dropdownLeaveTimeout = null; 2294 }, 50); // Kurze Verzögerung, um Flackern zu vermeiden 2295 2296 isHoveringDropdown = false; 2297 }); 2298 2299 // Checkbox Change Event 2300 checkbox.addEventListener('change', () => { 2301 // Text und vertikale Linie gleichzeitig animieren 2302 updateCursorTextState(checkbox.checked); 2303 2304 // Vertikale Linie basierend auf Checkbox-Status ein-/ausblenden 2305 if (cursorVerticalLine) { 2306 if (checkbox.checked) { 2307 // Nach oben ausblenden (wenn geöffnet) 2308 gsap.to(cursorVerticalLine, { 2309 opacity: 0, 2310 y: "-30%", // Hochanimieren 2311 duration: 0.15, 2312 ease: "power2.out" // Etwas smoothere Easing-Funktion 2313 }); 2314 } else { 2315 // Direkt an der aktuellen Position wieder einblenden 2316 gsap.to(cursorVerticalLine, { 2317 opacity: 1, 2318 y: 0, // Zurück zur Ausgangsposition 2319 duration: 0.15, 2320 ease: "power2.out" // Etwas smoothere Easing-Funktion 2321 }); 2322 } 2323 } 2324 }); 2325 2326 // Initialen Zustand setzen 2327 if (checkbox.checked) { 2328 updateCursorTextState(true); 2329 2330 // Auch beim initialen Zustand die vertikale Linie richtig setzen 2331 if (cursorVerticalLine) { 2332 gsap.set(cursorVerticalLine, { opacity: 0, y: "-30%" }); 2333 } 2334 } 2335 }); 2336 2337 // =================== WORK ITEM INTERACTION ===================== 2338 // Hover Events für Work Step Headings
2339 workItems.forEach(workItem => { 2340 if (!cursorLinks) return; 2341 2342 // Finde den zugehörigen Image Wrapper 2343 const imgWrapper = workItem.querySelector('.work_img-wrapper'); 2344 2345 // Hover Events 2346 workItem.addEventListener('mouseenter', () => { 2347 cursorLinks.classList.add('active'); 2348 currentWorkItem = workItem; 2349 workItemRect = workItem.getBoundingClientRect(); 2350 2351 // Zeige Image Wrapper 2352 if (imgWrapper) { 2353 gsap.set(imgWrapper, { opacity: 1 }); 2354 } 2355 2356 // Cursor nur anzeigen, wenn die Maus bereits bewegt wurde 2357 if (hasMouseMoved) { 2358 // Cursor an die aktuelle Mausposition setzen und dann erst einblenden 2359 showCursorWhenReady(cursorLinks, lastMouseX, lastMouseY); 2360 } 2361 }); 2362 2363 workItem.addEventListener('mouseleave', () => { 2364 cursorLinks.classList.remove('active'); 2365 currentWorkItem = null; 2366 workItemRect = null; 2367 2368 // Verstecke Image Wrapper 2369 if (imgWrapper) { 2370 gsap.set(imgWrapper, { opacity: 0 }); 2371 } 2372 2373 // Cursor ausblenden 2374 gsap.to(cursorLinks, { 2375 opacity: 0, 2376 visibility: 'hidden', 2377 duration: 0.2, 2378 ease: "power3.out" 2379 }); 2380 cursorIsReady = false; 2381 }); 2382 }); 2383 2384 // =================== HELPER FUNCTIONS ========================== 2385 // Funktion zum Aktualisieren der Text-Zustände 2386 function updateCursorTextState(isChecked) { 2387 if (cursorTextElements.length === 0) return; 2388 2389 // Animation ohne unnötige Parameter 2390 const offset = isChecked ? "-1.1em" : "0"; 2391 cursorTextElements.forEach(element => { 2392 gsap.to(element, { 2393 y: offset, 2394 duration: 0.1, // 100ms für schnellere, gleichmäÃigere Animation 2395 ease: "ease" // Linear und gleichmäÃig 2396 }); 2397 }); 2398 } 2399 2400 // =================== PHYSIKBASIERTE ANIMATION LOOP ============= 2401 // Animation Loop für physikbasierte Bewegung 2402 function animateWorkImages() { 2403 if (workImgWrappers.length > 0) { 2404 // Physikbasierte Animation für X-Position 2405 const xDiff = targetX - currentX; 2406 currentX += xDiff * 0.2; // Verzögerung für natürliches Gefühl - höhere Werte (0.2-0.3) = schnellere Reaktion 2407 2408 // Berechne Skew basierend auf der Geschwindigkeit der Bewegung 2409 const skewFactor = 0.5; // Stärke des Effekts - höhere Werte = stärkere Verzerrung (0.1-0.5 empfohlen) 2410 const targetSkew = xDiff * skewFactor; // Positiver Wert damit der untere Teil zuerst beschleunigt 2411 2412 // Physikbasierte Animation für Skew 2413 skewVelocity += (targetSkew - currentSkew) * 0.5; // Beschleunigung - höhere Werte = schnellere Reaktion 2414 skewVelocity *= 0.75; // Dämpfung - niedrigere Werte = längeres Nachschwingen 2415 currentSkew += skewVelocity; 2416 2417 // Animiere alle Work Item Images 2418 workImgWrappers.forEach(wrapper => { 2419 gsap.set(wrapper, { 2420 x: currentX, 2421 skewX: currentSkew 2422 }); 2423 }); 2424 } 2425 2426 // Animation fortsetzen 2427 requestAnimationFrame(animateWorkImages); 2428 } 2429 2430 // Animation starten 2431 animateWorkImages(); 2432}); 2433</script>
2433</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.