{"roots":["0:3"],"nodeById":{"680:6579":{"id":"680:6579","type":"VARIABLE","assetId":"VariableID:680:6579","name":"Blue","resolvedType":"COLOR","valuesByMode":{"361:0":{"r":0.0,"g":0.133333340287209,"b":1.0,"a":1.0}},"variableCollectionId":"VariableCollectionId:361:5468","codeSyntax":{}},"801:759":{"type":"FRAME","id":"801:759","name":"Frame 181","absoluteBoundingBox":{"x":-26662.0,"y":149.0,"width":274.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":149.0,"width":274.0,"height":40.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,320.0]],"size":{"x":274.0,"y":40.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":4.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["801:760"]},"482:4652":{"type":"FRAME","id":"482:4652","name":"Frame 180","absoluteBoundingBox":{"x":-26662.0,"y":109.0,"width":274.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":109.0,"width":274.0,"height":40.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,280.0]],"size":{"x":274.0,"y":40.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":4.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["482:4653"]},"482:4653":{"type":"TEXT","id":"482:4653","name":"Dispo Flomi","absoluteBoundingBox":{"x":-26662.0,"y":109.0,"width":181.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26659.822265625,"y":116.437995910645,"width":176.587890625,"height":30.1620101928711},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":181.0,"y":40.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.04999995231628,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.04999995231628,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"characterStyleOverrides":[5,5,5,5,5,5,5,5,5,5,5],"characters":"Dispo Flomi","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":33.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{"5":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","hyperlink":{"type":"NODE","nodeID":"/dispo-flomi"},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","isOverrideOverTextStyle":true,"fontSize":33.0,"opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","inheritTextStyleId":"341:19602","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"}}},"482:4650":{"type":"FRAME","id":"482:4650","name":"Frame 179","absoluteBoundingBox":{"x":-26662.0,"y":69.0,"width":274.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":69.0,"width":274.0,"height":40.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,240.0]],"size":{"x":274.0,"y":40.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":4.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["482:4651"]},"482:4651":{"type":"TEXT","id":"482:4651","name":"Speak Type?","absoluteBoundingBox":{"x":-26662.0,"y":69.0,"width":202.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26660.87890625,"y":76.4379959106445,"width":199.1953125,"height":30.1620025634766},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":202.0,"y":40.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.04999995231628,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.04999995231628,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"characterStyleOverrides":[5,5,5,5,5,5,5,5,5,5,5],"characters":"Speak Type?","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":33.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{"5":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","hyperlink":{"type":"NODE","nodeID":"/speak-type"},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","isOverrideOverTextStyle":true,"fontSize":33.0,"opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","inheritTextStyleId":"341:19602","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"}}},"482:4648":{"type":"FRAME","id":"482:4648","name":"Frame 176","absoluteBoundingBox":{"x":-26662.0,"y":29.0,"width":274.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":29.0,"width":274.0,"height":40.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,200.0]],"size":{"x":274.0,"y":40.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":4.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["482:4649"]},"482:4649":{"type":"TEXT","id":"482:4649","name":"Focus","absoluteBoundingBox":{"x":-26662.0,"y":29.0,"width":95.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26659.822265625,"y":36.8999977111816,"width":91.169921875,"height":23.5620040893555},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":95.0,"y":40.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.04999995231628,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.04999995231628,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"characterStyleOverrides":[5,5,5,5,5],"characters":"Focus","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":33.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{"5":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","hyperlink":{"type":"NODE","nodeID":"/focus"},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","isOverrideOverTextStyle":true,"fontSize":33.0,"opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","inheritTextStyleId":"341:19602","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"}}},"482:4638":{"type":"FRAME","id":"482:4638","name":"Frame 164","absoluteBoundingBox":{"x":-26662.0,"y":-131.0,"width":274.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":-131.0,"width":274.0,"height":40.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,40.0]],"size":{"x":274.0,"y":40.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":4.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["482:4639"]},"801:760":{"type":"TEXT","id":"801:760","name":"Portfolio","absoluteBoundingBox":{"x":-26662.0,"y":149.0,"width":139.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26659.822265625,"y":156.438003540039,"width":135.158203125,"height":24.0240020751953},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":139.0,"y":40.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.133333340287209,"b":1.0,"a":1.0},"boundVariables":{"color":{"type":"VARIABLE_ALIAS","id":"VariableID:680:6579"}},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"boundVariables":{"fills":[{"type":"VARIABLE_ALIAS","id":"VariableID:680:6579"}],"textRangeFills":[{"type":"VARIABLE_ALIAS","id":"VariableID:680:6579"}]},"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.04999995231628,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.04999995231628,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"characterStyleOverrides":[12,12,12,12,12,12,12,12,12],"characters":"Portfolio","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","boundVariables":{"paints":[{"type":"VARIABLE_ALIAS","id":"VariableID:680:6579"}]},"fontVariantPosition":"NORMAL","fontSize":33.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{"12":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","hyperlink":{"type":"URL","url":"https://www.loic-berger.ch/?project=portfolio","openInNewTab":false},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","boundVariables":{"paints":[{"type":"VARIABLE_ALIAS","id":"VariableID:680:6579"}]},"fontVariantPosition":"NORMAL","isOverrideOverTextStyle":true,"fontSize":33.0,"opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.133333340287209,"b":1.0,"a":1.0},"boundVariables":{"color":{"type":"VARIABLE_ALIAS","id":"VariableID:680:6579"}},"visible":true,"opacity":1.0}],"lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","inheritTextStyleId":"341:19602","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"}}},"482:4640":{"type":"FRAME","id":"482:4640","name":"Frame 173","absoluteBoundingBox":{"x":-26662.0,"y":-171.0,"width":274.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":-171.0,"width":274.0,"height":40.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":274.0,"y":40.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":4.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["482:4641"]},"482:4641":{"type":"TEXT","id":"482:4641","name":"Meetingpoint","absoluteBoundingBox":{"x":-26662.0,"y":-171.0,"width":217.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26659.822265625,"y":-163.561996459961,"width":213.189453125,"height":30.1620025634766},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":217.0,"y":40.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.04999995231628,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.04999995231628,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"characterStyleOverrides":[5,5,5,5,5,5,5,5,5,5,5,5],"characters":"Meetingpoint","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":33.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{"5":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","hyperlink":{"type":"NODE","nodeID":"/meetingpoint"},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","isOverrideOverTextStyle":true,"fontSize":33.0,"opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","inheritTextStyleId":"341:19602","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"}}},"485:13994":{"type":"FRAME","id":"485:13994","name":"Frame 154","absoluteBoundingBox":{"x":-26691.0,"y":-509.0,"width":243.607360839844,"height":64.0},"isolatedAbsoluteRenderBounds":{"x":-26691.0,"y":-509.0,"width":243.607421875,"height":64.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":243.607360839844,"y":64.0},"fills":[],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","counterAxisAlignItems":"CENTER","children":["485:14099","485:14134"]},"485:14134":{"type":"FRAME","id":"485:14134","name":"Frame 1448","absoluteBoundingBox":{"x":-26592.392578125,"y":-509.0,"width":145.0,"height":64.0},"isolatedAbsoluteRenderBounds":{"x":-26592.392578125,"y":-509.0,"width":145.0,"height":64.0},"relativeTransform":[[1.0,0.0,98.6073608398438],[0.0,1.0,0.0]],"size":{"x":145.0,"y":64.0},"fills":[],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":485,"localID":14144},"event":{"interactionType":"ON_CLICK"},"actions":[{"transitionNodeID":{"sessionID":277,"localID":13457},"connectionType":"INTERNAL_NODE","navigationType":"NAVIGATE","connectionURL":"/about-me"}],"isDeleted":false,"stateManagementVersion":1}],"behaviors":{"appear":{"otherLayer":{"sessionID":-1,"localID":-1},"trigger":"PAGE_LOAD","direction":"UP","enterTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"enterState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":0.0},"exitTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"exitState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":1.0},"playsOnce":false,"behaviorType":"appear"},"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.10000002384186,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.10000002384186,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"paddingTop":20.0,"paddingRight":20.0,"paddingBottom":20.0,"paddingLeft":20.0,"layoutMode":"HORIZONTAL","itemSpacing":43.0,"children":["485:14135"]},"487:25373":{"type":"COMPONENT","id":"487:25373","name":"Property 1=LoicMOBILE","absoluteBoundingBox":{"x":-25835.0,"y":-625.0,"width":129.0,"height":56.0},"isolatedAbsoluteRenderBounds":{"x":-25835.0,"y":-625.0,"width":129.0,"height":56.0},"relativeTransform":[[1.0,0.0,957.0],[0.0,1.0,113.0]],"size":{"x":129.0,"y":56.0},"blendMode":"DIFFERENCE","fills":[{"opacity":0.0,"blendMode":"DIFFERENCE","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true}],"constraintValues":{"left":{"pixelOffset":957.0,"sizeFraction":0.0},"top":{"pixelOffset":113.0,"sizeFraction":0.0}},"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":573,"localID":4230},"event":{"interactionType":"ON_CLICK"},"actions":[{"transitionNodeID":{"sessionID":277,"localID":13457},"connectionType":"INTERNAL_NODE","navigationType":"NAVIGATE","connectionURL":"/about-me"}],"isDeleted":false,"stateManagementVersion":1}],"behaviors":{"appear":{"otherLayer":{"sessionID":-1,"localID":-1},"trigger":"THIS_LAYER_IN_VIEW","direction":"UP","enterTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"enterState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":0.0},"exitTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"exitState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":1.0},"playsOnce":false,"behaviorType":"appear"}},"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["487:25374"],"componentSetId":"278:13821"},"361:5468":{"id":"361:5468","assetId":"VariableCollectionId:361:5468","type":"VARIABLE_COLLECTION","name":"Variable collection","defaultModeId":"361:0","modes":[{"modeId":"361:0","name":"Mode 1"}]},"487:25374":{"type":"FRAME","id":"487:25374","name":"Frame 154","absoluteBoundingBox":{"x":-25835.0,"y":-625.0,"width":129.0,"height":56.0},"isolatedAbsoluteRenderBounds":{"x":-25835.0,"y":-625.0,"width":129.0,"height":56.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":129.0,"y":56.0},"fills":[],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["487:25375"]},"487:25375":{"type":"FRAME","id":"487:25375","name":"Frame 150","absoluteBoundingBox":{"x":-25835.0,"y":-625.0,"width":129.0,"height":56.0},"isolatedAbsoluteRenderBounds":{"x":-25835.0,"y":-625.0,"width":129.0,"height":56.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":129.0,"y":56.0},"fills":[],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":493,"localID":36075},"event":{"interactionType":"ON_CLICK"},"actions":[{"transitionNodeID":{"sessionID":277,"localID":13457},"connectionType":"INTERNAL_NODE","navigationType":"NAVIGATE","connectionURL":"/about-me"}],"isDeleted":false,"stateManagementVersion":1}],"paddingTop":18.0,"paddingRight":18.0,"paddingBottom":18.0,"paddingLeft":18.0,"layoutMode":"HORIZONTAL","itemSpacing":43.0,"children":["487:25376"]},"485:13993":{"type":"COMPONENT","id":"485:13993","name":"Property 1=Variant4","absoluteBoundingBox":{"x":-26691.0,"y":-509.0,"width":243.607360839844,"height":64.0},"isolatedAbsoluteRenderBounds":{"x":-26691.0,"y":-509.0,"width":243.607421875,"height":64.0},"relativeTransform":[[1.0,0.0,101.0],[0.0,1.0,229.0]],"size":{"x":243.607360839844,"y":64.0},"blendMode":"EXCLUSION","fills":[],"constraintValues":{"left":{"pixelOffset":101.0,"sizeFraction":0.0},"top":{"pixelOffset":229.0,"sizeFraction":0.0}},"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["485:13994"],"componentSetId":"278:13821"},"487:25376":{"type":"TEXT","id":"487:25376","name":"Loïc Berger","absoluteBoundingBox":{"x":-25817.0,"y":-607.0,"width":93.0,"height":20.0},"isolatedAbsoluteRenderBounds":{"x":-25815.87890625,"y":-603.070007324219,"width":91.296875,"height":15.4700317382812},"relativeTransform":[[1.0,0.0,18.0],[0.0,1.0,18.0]],"size":{"x":93.0,"y":20.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Loïc Berger","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:489:26959","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":17.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1,"SS01":1,"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":20.4000015258789,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"493:33053":{"type":"TEXT","id":"493:33053","name":"Projects","absoluteBoundingBox":{"x":-25785.0,"y":-544.0,"width":68.0,"height":20.0},"isolatedAbsoluteRenderBounds":{"x":-25783.87890625,"y":-540.138000488281,"width":66.009765625,"height":15.5380249023438},"relativeTransform":[[1.0,0.0,18.0],[0.0,1.0,18.0]],"size":{"x":68.0,"y":20.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Projects","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:489:26959","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":17.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1,"SS01":1,"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":20.4000015258789,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"278:13822":{"type":"COMPONENT","id":"278:13822","name":"Property 1=Variant2","absoluteBoundingBox":{"x":-26690.0,"y":-591.0,"width":176.0,"height":64.0},"isolatedAbsoluteRenderBounds":{"x":-26690.0,"y":-591.0,"width":176.0,"height":64.0},"relativeTransform":[[1.0,0.0,102.0],[0.0,1.0,147.0]],"size":{"x":176.0,"y":64.0},"blendMode":"DIFFERENCE","fills":[{"opacity":0.0,"blendMode":"DIFFERENCE","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true}],"constraintValues":{"left":{"pixelOffset":102.0,"sizeFraction":0.0},"top":{"pixelOffset":147.0,"sizeFraction":0.0}},"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"appear":{"otherLayer":{"sessionID":-1,"localID":-1},"trigger":"THIS_LAYER_IN_VIEW","direction":"UP","enterTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"enterState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":0.0},"exitTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"exitState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":1.0},"playsOnce":false,"behaviorType":"appear"}},"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["278:13825"],"componentSetId":"278:13821"},"278:13825":{"type":"FRAME","id":"278:13825","name":"Frame 119","absoluteBoundingBox":{"x":-26690.0,"y":-591.0,"width":176.0,"height":64.0},"isolatedAbsoluteRenderBounds":{"x":-26690.0,"y":-591.0,"width":176.0,"height":64.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":176.0,"y":64.0},"fills":[],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":482,"localID":4825},"event":{"interactionType":"MOUSE_ENTER"},"actions":[{"transitionNodeID":{"sessionID":482,"localID":4824},"transitionType":"INSTANT_TRANSITION","transitionDuration":1.02209377288818,"easingType":"GENTLE_SPRING","easingFunction":[1.0,100.0,15.0,0.0],"connectionType":"INTERNAL_NODE","navigationType":"SWAP_STATE","transitionResetVideoPosition":false,"stateGroupContext":"278:13821"}],"isDeleted":false,"stateManagementVersion":1}],"paddingTop":20.0,"paddingRight":50.0,"paddingBottom":20.0,"paddingLeft":50.0,"layoutMode":"HORIZONTAL","counterAxisAlignItems":"CENTER","children":["447:5568"]},"447:5568":{"type":"TEXT","id":"447:5568","name":"Projects","absoluteBoundingBox":{"x":-26640.0,"y":-571.0,"width":76.0,"height":24.0},"isolatedAbsoluteRenderBounds":{"x":-26638.74609375,"y":-565.565979003906,"width":73.79296875,"height":17.365966796875},"relativeTransform":[[1.0,0.0,50.0],[0.0,1.0,20.0]],"size":{"x":76.0,"y":24.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Projects","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:354:1888","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":19.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":23.75,"lineHeightPercent":97.9623794555664,"lineHeightPercentFontSize":125.0,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"493:33013":{"type":"FRAME","id":"493:33013","name":"Mobile","absoluteBoundingBox":{"x":-24074.0,"y":3674.0,"width":375.0,"height":1080.0},"isolatedAbsoluteRenderBounds":{"x":-24074.0,"y":3674.0,"width":375.0,"height":1080.0},"relativeTransform":[[1.0,0.0,828.0],[0.0,1.0,100.0]],"size":{"x":375.0,"y":1080.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"clipsContent":true,"overflowDirection":"VERTICAL_SCROLLING","layoutMode":"VERTICAL","counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","counterAxisSizingMode":"FIXED","isBreakpointFrame":true,"children":["493:33014","807:5328","728:9160","537:3087","680:6588","807:5324"]},"482:4635":{"type":"FRAME","id":"482:4635","name":"bilbao 1","absoluteBoundingBox":{"x":-26682.0,"y":-191.0,"width":345.0,"height":400.0},"isolatedAbsoluteRenderBounds":{"x":-26682.0,"y":-191.0,"width":345.0,"height":400.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,20.0]],"size":{"x":345.0,"y":400.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"effects":[{"type":"BACKGROUND_BLUR","visible":true,"radius":10.0}],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"paddingTop":20.0,"paddingRight":20.0,"paddingBottom":20.0,"paddingLeft":20.0,"layoutMode":"VERTICAL","counterAxisSizingMode":"FIXED","children":["482:4636"]},"482:4637":{"type":"FRAME","id":"482:4637","name":"Frame 1445","absoluteBoundingBox":{"x":-26662.0,"y":-171.0,"width":274.0,"height":360.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":-171.0,"width":274.0,"height":360.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":274.0,"y":360.0},"fills":[],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"VERTICAL","children":["482:4640","482:4638","482:4642","482:4644","482:4646","482:4648","482:4650","482:4652","801:759"]},"482:4644":{"type":"FRAME","id":"482:4644","name":"Frame 175","absoluteBoundingBox":{"x":-26662.0,"y":-51.0,"width":274.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":-51.0,"width":274.0,"height":40.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,120.0]],"size":{"x":274.0,"y":40.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"DIV","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":485,"localID":13958},"event":{"interactionType":"ON_CLICK"},"actions":[{"transitionNodeID":{"sessionID":388,"localID":6924},"connectionType":"INTERNAL_NODE","navigationType":"NAVIGATE","connectionURL":"/interdependence"}],"isDeleted":false,"stateManagementVersion":1}],"layoutMode":"HORIZONTAL","itemSpacing":4.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["482:4645"]},"807:5324":{"mainComponentId":"493:33051","type":"INSTANCE","id":"807:5324","name":"Frame 151","absoluteBoundingBox":{"x":-23803.0,"y":3674.0,"width":104.0,"height":56.0},"isolatedAbsoluteRenderBounds":{"x":-23803.0,"y":3674.0,"width":104.0,"height":56.0},"relativeTransform":[[1.0,0.0,271.0],[0.0,1.0,0.0]],"size":{"x":104.0,"y":56.0},"blendMode":"DIFFERENCE","fills":[{"opacity":0.0,"blendMode":"DIFFERENCE","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true}],"constraints":{"vertical":"TOP","horizontal":"RIGHT"},"constraintValues":{"right":{"pixelOffset":0.0,"sizeFraction":1.0},"top":{"pixelOffset":0.0,"sizeFraction":0.0}},"strokeAlign":"INSIDE","layoutPositioning":"ABSOLUTE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":493,"localID":35848},"event":{"interactionType":"ON_CLICK"},"actions":[{"transitionNodeID":{"sessionID":493,"localID":35769},"connectionType":"INTERNAL_NODE","navigationType":"NAVIGATE","connectionURL":"/menumobile"}],"isDeleted":false,"stateManagementVersion":1}],"behaviors":{"appear":{"otherLayer":{"sessionID":-1,"localID":-1},"trigger":"THIS_LAYER_IN_VIEW","direction":"UP","enterTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"enterState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":0.0},"exitTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"exitState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":1.0},"playsOnce":false,"behaviorType":"appear"}},"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["I807:5324;493:33052"],"componentSetId":"278:13821","componentProperties":{"Property 1":{"value":"Proj Mobile","type":"VARIANT","boundVariables":{}}},"overrides":[{"key":[],"value":{"layoutPositioning":"ABSOLUTE"}}]},"482:4639":{"type":"TEXT","id":"482:4639","name":"Bilbao","absoluteBoundingBox":{"x":-26662.0,"y":-131.0,"width":99.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26659.822265625,"y":-123.562004089355,"width":94.875,"height":24.0240020751953},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":99.0,"y":40.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.04999995231628,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.04999995231628,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"characterStyleOverrides":[5,5,5,5,5,5],"characters":"Bilbao","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":33.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{"5":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","hyperlink":{"type":"NODE","nodeID":"/bilbao"},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","isOverrideOverTextStyle":true,"fontSize":33.0,"opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","inheritTextStyleId":"341:19602","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"}}},"728:9160":{"mainComponentId":"485:13993","type":"INSTANCE","id":"728:9160","name":"Frame 1495","absoluteBoundingBox":{"x":-24008.302734375,"y":4690.0,"width":243.607360839844,"height":64.0},"isolatedAbsoluteRenderBounds":{"x":-24008.302734375,"y":4690.0,"width":243.607360839844,"height":64.0},"relativeTransform":[[1.0,0.0,65.6963195800781],[0.0,1.0,1016.0]],"size":{"x":243.607360839844,"y":64.0},"blendMode":"EXCLUSION","fills":[],"visible":false,"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["I728:9160;485:13994"],"componentSetId":"278:13821","componentProperties":{"Property 1":{"value":"Variant4","type":"VARIANT","boundVariables":{}}},"overrides":[{"key":[],"value":{"name":"Frame 1495","size":{"x":243.607360839844,"y":64.0}}}]},"482:4647":{"type":"TEXT","id":"482:4647","name":"Dampfzentrale","absoluteBoundingBox":{"x":-26662.0,"y":-11.0,"width":234.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26659.822265625,"y":-3.10000157356262,"width":230.203125,"height":29.7000026702881},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":234.0,"y":40.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.04999995231628,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.04999995231628,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"characterStyleOverrides":[5,5,5,5,5,5,5,5,5,5,5,5,5],"characters":"Dampfzentrale","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":33.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{"5":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","hyperlink":{"type":"NODE","nodeID":"/dampfzentrale"},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","isOverrideOverTextStyle":true,"fontSize":33.0,"opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","inheritTextStyleId":"341:19602","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"}}},"485:14135":{"type":"TEXT","id":"485:14135","name":"Loïc Berger","absoluteBoundingBox":{"x":-26572.392578125,"y":-489.0,"width":105.0,"height":24.0},"isolatedAbsoluteRenderBounds":{"x":-26571.138671875,"y":-483.489990234375,"width":102.537109375,"height":17.2899780273438},"relativeTransform":[[1.0,0.0,20.0],[0.0,1.0,20.0]],"size":{"x":105.0,"y":24.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Loïc Berger","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:354:1888","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":19.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":23.75,"lineHeightPercent":97.9623794555664,"lineHeightPercentFontSize":125.0,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"537:3087":{"mainComponentId":"487:25373","type":"INSTANCE","id":"537:3087","name":"Frame 1471","absoluteBoundingBox":{"x":-24074.0,"y":3674.0,"width":129.0,"height":56.0},"isolatedAbsoluteRenderBounds":{"x":-24074.0,"y":3674.0,"width":129.0,"height":56.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":129.0,"y":56.0},"fills":[{"opacity":0.0,"blendMode":"DIFFERENCE","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true}],"constraintValues":{"left":{"pixelOffset":0.0,"sizeFraction":0.0},"top":{"pixelOffset":0.0,"sizeFraction":0.0}},"strokeAlign":"INSIDE","layoutPositioning":"ABSOLUTE","scrollBehavior":"STICKY_SCROLLS","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":573,"localID":4230},"event":{"interactionType":"ON_CLICK"},"actions":[{"transitionNodeID":{"sessionID":277,"localID":13457},"connectionType":"INTERNAL_NODE","navigationType":"NAVIGATE","connectionURL":"/about-me"}],"isDeleted":false,"stateManagementVersion":1}],"behaviors":{"appear":{"otherLayer":{"sessionID":-1,"localID":-1},"trigger":"THIS_LAYER_IN_VIEW","direction":"UP","enterTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"enterState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":0.0},"exitTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"exitState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":1.0},"playsOnce":false,"behaviorType":"appear"}},"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["I537:3087;487:25374"],"componentSetId":"278:13821","componentProperties":{"Property 1":{"value":"LoicMOBILE","type":"VARIANT","boundVariables":{}}},"overrides":[{"key":[],"value":{"blendMode":null,"layoutPositioning":"ABSOLUTE","name":"Frame 1471"}},{"key":["Frame 1540","Frame 1500","Loïc Berger0"],"value":{"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}]}}]},"62:2984":{"type":"TEXT","id":"62:2984","name":"Text1","absoluteBoundingBox":{"x":0.0,"y":0.0,"width":23.0,"height":27.0},"isolatedAbsoluteRenderBounds":{"x":0.216000005602837,"y":7.14799928665161,"width":21.3423748016357,"height":16.5420017242432},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":23.0,"y":27.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"strokeWeight":0.0,"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Ag","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","style":{"fontFamily":"Aktiv Grotesk","fontPostScriptName":"AktivGrotesk-Medium","fontStyle":"Medium","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":18.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":-0.341999995708466,"letterSpacingValue":-1.89999997615814,"letterSpacingUnit":"PERCENT","lineHeightPx":27.0,"lineHeightPercent":117.1875,"lineHeightPercentFontSize":150.0,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"680:6588":{"type":"TEXT","id":"680:6588","name":"Mobile version in progress The site may experience crashes or instability.","absoluteBoundingBox":{"x":-23983.0,"y":3832.0,"width":192.0,"height":78.0},"isolatedAbsoluteRenderBounds":{"x":-23980.919921875,"y":3835.86206054688,"width":187.44140625,"height":73.337890625},"relativeTransform":[[1.0,0.0,91.0],[0.0,1.0,158.0]],"size":{"x":192.0,"y":78.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"constraints":{"vertical":"TOP","horizontal":"LEFT_RIGHT"},"constraintValues":{"left":{"pixelOffset":91.0,"sizeFraction":0.0},"right":{"pixelOffset":-92.0,"sizeFraction":1.0},"top":{"pixelOffset":158.0,"sizeFraction":0.0}},"strokeAlign":"OUTSIDE","layoutPositioning":"ABSOLUTE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"characters":"Mobile version in progress\nThe site may experience crashes or instability.","lineIndentations":[0,0],"lineTypes":["NONE","NONE"],"listStartOffsets":[],"lineStyleOverrides":[0,0],"lineTextDirections":null,"textAutoResize":"HEIGHT","textAlignHorizontal":"CENTER","listSpacing":4.0,"style":{"styleIdForText":"StyleId:489:26959","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"HEIGHT","fontVariantPosition":"NORMAL","fontSize":17.0,"textAlignHorizontal":"CENTER","textAlignVertical":"TOP","opentypeFlags":{"SS04":1,"SS01":1,"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":20.4000015258789,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{"1":{"styleIdForText":"StyleId:489:26959","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Medium","fontStyle":"Medium","fontVariations":{"Weight":450.0},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":16.0,"opentypeFlags":{"SS04":1,"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":19.2000007629395,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","inheritTextStyleId":"489:26958","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"}}},"536:7355":{"mainComponentId":"278:13822","type":"INSTANCE","id":"536:7355","name":"Frame 1470","absoluteBoundingBox":{"x":-24314.0,"y":3674.0,"width":176.0,"height":64.0},"isolatedAbsoluteRenderBounds":{"x":-24314.0,"y":3674.0,"width":176.0,"height":64.0},"relativeTransform":[[1.0,0.0,524.0],[0.0,1.0,0.0]],"size":{"x":176.0,"y":64.0},"fills":[],"constraints":{"vertical":"TOP","horizontal":"RIGHT"},"constraintValues":{"right":{"pixelOffset":0.0,"sizeFraction":1.0},"top":{"pixelOffset":0.0,"sizeFraction":0.0}},"strokeAlign":"INSIDE","layoutPositioning":"ABSOLUTE","scrollBehavior":"STICKY_SCROLLS","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"appear":{"otherLayer":{"sessionID":-1,"localID":-1},"trigger":"THIS_LAYER_IN_VIEW","direction":"UP","enterTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"enterState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":0.0},"exitTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"exitState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":1.0},"playsOnce":false,"behaviorType":"appear"}},"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["I536:7355;278:13825"],"componentSetId":"278:13821","componentProperties":{"Property 1":{"value":"Variant2","type":"VARIANT","boundVariables":{}}},"overrides":[{"key":[],"value":{"blendMode":null,"fills":[],"layoutPositioning":"ABSOLUTE","name":"Frame 1470","size":{"x":176.0,"y":64.0}}},{"key":["Frame 1190","Projects0"],"value":{"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}]}}]},"807:5328":{"type":"WIDGET","id":"807:5328","name":"Embed 6","absoluteBoundingBox":{"x":-22571.0,"y":3674.0,"width":375.0,"height":1080.0},"isolatedAbsoluteRenderBounds":{"x":-22571.0,"y":3674.0,"width":375.0,"height":1080.0},"relativeTransform":[[1.0,0.0,1503.0],[0.0,1.0,0.0]],"size":{"x":375.0,"y":1080.0},"visible":false,"layoutAlign":"STRETCH","layoutGrow":1.0,"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"widgetType":"GENERIC","syncedState":{"embedAllowFullscreen":"false","embedCodeType":"html","embedIframeHtml":"<!DOCTYPE html>\n<html lang=\"de\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <title>Portfolio 3D — Mobile</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\" />\n  <style>\n    html,body{margin:0;height:100%;overflow:hidden;background:#fff;overscroll-behavior:none;-webkit-tap-highlight-color:transparent}\n    #stage{width:100%;height:100vh;position:relative;touch-action:none}\n    canvas{display:block;width:100%;height:100%}\n    /* Weißes Fade-Overlay */\n    #fade{position:fixed;inset:0;background:#fff;opacity:0;pointer-events:none;z-index:9999;transition:opacity .6s ease}\n\n    /* Font: SpaceGrotesk[wght].woff2 */\n    @font-face{\n      font-family:\"Space Grotesk\";\n      src:\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@2eb12abf61bddfeeebcd4edad67a8dd1acb1f9ac/SpaceGrotesk%5Bwght%5D.woff2\") format(\"woff2-variations\"),\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@2eb12abf61bddfeeebcd4edad67a8dd1acb1f9ac/SpaceGrotesk%5Bwght%5D.woff2\") format(\"woff2\");\n      font-weight:300 800; font-style:normal; font-display:swap;\n    }\n    .sg{font-family:\"Space Grotesk\",system-ui,-apple-system,\"Segoe UI\",Roboto,Arial,sans-serif}\n\n    /* Projekt-Info-Card (mobile): full width, unten, padding 17 */\n    #infoCard{\n      position:fixed; left:17px; right:17px; bottom:17px; z-index:9000;\n      border-radius:10px; padding:14px 17px 17px;\n      background:#eee; color:#000; display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.08)\n    }\n    .ic-title{ font-weight:700; font-size:28px; line-height:1.2; margin:0 0 6px 0; white-space:nowrap }\n    .ic-row{ display:flex; align-items:center; gap:7px; margin:0 0 7px 0; font-weight:450; font-size:16px; line-height:1.2 }\n    .ic-row:last-child{ margin-bottom:0 }\n    .ic-dot{ width:8px; height:8px; border-radius:50%; background:currentColor; opacity:.95 }\n\n    /* Help-Card (nur 1 Zeile), gleiche Position wie Info-Card */\n    #helpCard{\n      position:fixed; left:17px; right:17px; bottom:17px; z-index:9000;\n      border-radius:10px; padding:14px 17px 17px;\n      background:#02f; color:#fff; display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.12)\n    }\n    .hc-line{ font-weight:700; font-size:19px; line-height:1.2; margin:0; white-space:nowrap; text-align:left }\n\n    /* Größere Touch-Ziele */\n    #infoCard, #helpCard{ touch-action:manipulation }\n  </style>\n\n  <!-- Preload Font -->\n  <link rel=\"preload\" as=\"font\" type=\"font/woff2\" crossorigin\n        href=\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@2eb12abf61bddfeeebcd4edad67a8dd1acb1f9ac/SpaceGrotesk%5Bwght%5D.woff2\">\n</head>\n<body>\n<div id=\"stage\"></div>\n<div id=\"fade\"></div>\n\n<!-- Help-Card (nur 1 Zeile) -->\n<div id=\"helpCard\" class=\"sg\" aria-live=\"polite\">\n  <p class=\"hc-line\">scroll to move</p>\n</div>\n\n<!-- Projekt-Info-Card (tapbar) -->\n<div id=\"infoCard\" class=\"sg\" aria-live=\"polite\" role=\"button\">\n  <div class=\"ic-title\" id=\"icTitle\"></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL1\"></span></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL2\"></span></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL3\"></span></div>\n</div>\n\n<!-- Import-Map Polyfill -->\n<script async src=\"https://cdn.jsdelivr.net/npm/es-module-shims@1/dist/es-module-shims.min.js\"></script>\n<script type=\"importmap\">\n{ \"imports\": {\n  \"three\": \"https://cdn.jsdelivr.net/npm/three@0.158/build/three.module.js\",\n  \"three/addons/\": \"https://cdn.jsdelivr.net/npm/three@0.158/examples/jsm/\"\n}}\n</script>\n\n<script type=\"module\">\nimport * as THREE from 'three';\nimport { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\n\nconst IS_TOUCH = ('ontouchstart' in window) || (navigator.maxTouchPoints>0);\n\n/* ======= KONFIG ======= */\nconst ASSET_COMMIT = '2eb12abf61bddfeeebcd4edad67a8dd1acb1f9ac';\nconst CDN_BASE     = `https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@${ASSET_COMMIT}`;\nconst GLB_URL      = `${CDN_BASE}/xprt260105.glb`;\nconst PROJECTS     = ['javelin','bildsprache','meetingpoint','fototage','dampfzentrale','bilbao','flomi','nlch'];\nconst START_PATH_INDEX = 20;\n\n/* Zielseiten */\nconst PROJECT_URLS = {\n  bilbao:        'https://loicberger.figma.site/bilbao',\n  meetingpoint:  'https://loicberger.figma.site/meetingpoint',\n  javelin:       'https://loicberger.figma.site/javelin',\n  bildsprache:   'https://loicberger.figma.site/interdependence',\n  dampfzentrale: 'https://loicberger.figma.site/dampfzentrale',\n  fototage:      'https://loicberger.figma.site/focus',\n  nlch:          'https://loicberger.figma.site/speak-type',\n  flomi:         'https://loicberger.figma.site/dispo-flomi'\n};\n\n/* ======= Info-Karten ======= */\nconst INFO_BLOCKS = [\n  { key:'dampfzentrale', from:325, to:375, bg:'#E8FA70', txt:'#000',\n    title:'Dampfzentrale', l1:'Poster Series', l2:'4 Posters', l3:'2024' },\n  { key:'bilbao', from:538, to:600, bg:'#FF3D3D', txt:'#000',\n    title:'Bilbao', l1:'Corporate Identity', l2:'Logo, Posters, Mural, Tote Bag', l3:'2024' },\n  { key:'javelin', from:477, to:524, bg:'#692FFF', txt:'#000',\n    title:'Javelin', l1:'Typeface', l2:'Regular 30 ...', l3:'2024' },\n  { key:'focus', from:263, to:300, bg:'#94006C', txt:'#FFFFFF',\n    title:'Focus', l1:'Video Installation', l2:'Mockup Installation', l3:'2025' },\n  { key:'meetingpoint', from:650, to:746, bg:'#C1FF6A', txt:'#000',\n    title:'Meetingpoint', l1:'VJ and Music Performance', l2:'Video 20:22 min', l3:'2024' },\n  { key:'bildsprache', from:395, to:448, bg:'#180152', txt:'#FFFFFF',\n    title:'Interdependence', l1:'Music, Painting', l2:'Painting 200 × 100 mm + Music', l3:'2025' },\n  { key:'flomi', from:117, to:160, bg:'#FF8E25', txt:'#000',\n    title:'Dispo Flomi', l1:'Poster, Poster-System', l2:'', l3:'2025' },\n  { key:'nlch', from:173, to:240, bg:'#00A6A9', txt:'#000',\n    title:'Speak Type?', l1:'Editorial, Concept', l2:'Brochure, 220 × 280 mm, 31 pages', l3:'2025' }\n];\n/* Help-Range über 0 hinweg: 793–30 */\nconst HELP_RANGE = { from:793, to:30 };\n\n/* ======= Persistenter State ======= */\nconst saveState = (u)=>{ try{ sessionStorage.setItem('camState@home', JSON.stringify({u,ts:Date.now()})); }catch{} };\nconst loadState = ()=>{ try{ const s=sessionStorage.getItem('camState@home'); return s?JSON.parse(s):null; }catch{return null;} };\n\n/* ======= Utils ======= */\nconst clamp=(n,a,b)=>Math.max(a,Math.min(b,n));\nconst wrap01=t=>((t%1)+1)%1;\nconst WORLD_UP = new THREE.Vector3(0,1,0);\nconst norm = s => (s||'').normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').replace(/\\s+/g,'').toLowerCase();\n\nfunction cleanMeshKey(origName){\n  if (!origName) return null;\n  let name = origName.trim().replace(/^P_/,'').replace(/^p_/,'');\n  const dot = name.indexOf('.'); if (dot>-1) name = name.slice(0,dot);\n  name = name.replace(/(\\d)[a-z]+$/i, '$1');\n  const m = name.match(/^(.+\\d{1,})0{1,}\\d{2,}$/);\n  if (m) name = m[1];\n  const clean = norm(name).replace(/[^a-z0-9_-]/g,'');\n  if (!clean || clean.includes('path') || clean==='scene') return null;\n  return clean;\n}\nfunction detectProjectFromKey(key){\n  for (const p of PROJECTS){ if (key.startsWith(p)) return p; }\n  return null;\n}\nfunction fileKey(filename){\n  const base = filename.split('/').pop() || filename;\n  const i = base.lastIndexOf('.'); const stem = i>-1 ? base.slice(0,i) : base;\n  return norm(stem).replace(/[^a-z0-9_-]/g,'');\n}\n\n/* ======= Loader ======= */\nconst texLoader = new THREE.TextureLoader(); texLoader.setCrossOrigin('anonymous');\nfunction loadTexture(url){\n  return new Promise((resolve,reject)=>{\n    texLoader.load(url, tx=>{\n      tx.flipY = false;\n      tx.colorSpace = THREE.SRGBColorSpace;\n      tx.minFilter  = THREE.LinearMipmapLinearFilter;\n      tx.magFilter  = THREE.LinearFilter;\n      tx.generateMipmaps = true;\n      resolve(tx);\n    }, undefined, err=>reject(err));\n  });\n}\nasync function applyImage(mesh, url){\n  try{\n    const texture = await loadTexture(url);\n    const mat = new THREE.MeshBasicMaterial({ color:0xffffff, map:texture, side:THREE.DoubleSide, transparent:true });\n    mesh.material = mat; mesh.material.needsUpdate = true;\n    mesh.userData.imageMat = mat;\n  }catch{}\n}\n\n/* ——— Video (EXAKT wie in der Desktop-Version) ——— */\nfunction createVideoTexture(url){\n  return new Promise((resolve, reject)=>{\n    const vid = document.createElement('video');\n    vid.src = url;\n    vid.crossOrigin = 'anonymous';\n    vid.loop = true;\n    vid.muted = true;\n    vid.setAttribute('muted','');\n    vid.playsInline = true;\n    vid.setAttribute('webkit-playsinline','');\n    vid.preload = 'auto';\n\n    const start = ()=> vid.play().catch(()=>{});\n    vid.addEventListener('loadeddata', start, {once:true});\n    vid.addEventListener('canplay',     start, {once:true});\n    vid.addEventListener('error', ()=>reject(new Error('video error '+url)), {once:true});\n\n    const vtex = new THREE.VideoTexture(vid);\n    vtex.flipY = false;\n    vtex.colorSpace = THREE.SRGBColorSpace;\n    vtex.minFilter  = THREE.LinearFilter;\n    vtex.magFilter  = THREE.LinearFilter;\n    vtex.generateMipmaps = false;\n    vtex.needsUpdate = true;\n\n    vid.play().catch(()=>{});\n    resolve({ vtex, vid });\n  });\n}\n\n/* ======= Manifest ======= */\nasync function fetchManifest(project){\n  const url = `${CDN_BASE}/assets/img/${project}/manifest.json`;\n  const r = await fetch(url, { cache:'no-cache' });\n  if (!r.ok) throw new Error(`manifest missing: ${project}`);\n  const m = await r.json();\n  const base = (m.base || `assets/img/${project}`).replace(/\\/+$/,'');\n  const files = Array.isArray(m.files) ? m.files : [];\n\n  const best = new Map();\n  for (const name of files){\n    const ext  = (name.split('.').pop()||'').toLowerCase();\n    const key  = fileKey(name);\n    const full = `${CDN_BASE}/${base}/${name}`;\n    const prev = best.get(key) || {};\n    if (/(webp|jpe?g|png|gif)$/.test(ext)){\n      if (!prev.image) prev.image = full;\n    } else if (/mp4|webm/.test(ext)){\n      const prio = ext==='mp4' ? 3 : 2;\n      if (!prev.video || prio > (prev.vprio||0)){ prev.video = full; prev.vprio = prio; }\n    }\n    best.set(key, prev);\n  }\n  for (const v of best.values()) delete v.vprio;\n  return best;\n}\n\n/* ======= Three Setup ======= */\nconst stage = document.getElementById('stage');\nconst overlay = document.getElementById('fade');\n\nconst renderer = new THREE.WebGLRenderer({ antialias:true, powerPreference:'high-performance' });\nrenderer.outputColorSpace = THREE.SRGBColorSpace;\nrenderer.setPixelRatio(Math.min(devicePixelRatio||1,2));\nrenderer.setSize(stage.clientWidth, stage.clientHeight, false);\nstage.appendChild(renderer.domElement);\n\nconst scene = new THREE.Scene(); scene.background = new THREE.Color(0xffffff);\nconst FOV_DEG = 120;                                    /* großer Kamerawinkel */\nconst camera = new THREE.PerspectiveCamera(FOV_DEG, stage.clientWidth/stage.clientHeight, 0.1, 5000);\nscene.add(new THREE.AmbientLight(0xffffff,1));\n\n/* ======= Pfad & Media ======= */\nlet pathCurve=null, pathCount=0;\nconst loader = new GLTFLoader();\n\nconst phMeshes = [];\nconst raycaster = new THREE.Raycaster(); raycaster.near=0.1; raycaster.far=50;\n\n/* Karten-Refs */\nconst ic = document.getElementById('infoCard');\nconst icTitle = document.getElementById('icTitle');\nconst icL1 = document.getElementById('icL1');\nconst icL2 = document.getElementById('icL2');\nconst icL3 = document.getElementById('icL3');\nconst helpCard = document.getElementById('helpCard');\n\n/* Belegungs-Map */\nlet occupancy = null;\nfunction idxNorm(i){ return (((i-1) % pathCount) + pathCount) % pathCount + 1; }\nfunction markRange(from, to, value){\n  if (!pathCount) return;\n  const a = idxNorm(from), b = idxNorm(to);\n  if (a<=b){ for (let i=a;i<=b;i++) occupancy[i]=value; }\n  else { for (let i=a;i<=pathCount;i++) occupancy[i]=value; for (let i=1;i<=b;i++) occupancy[i]=value; }\n}\nfunction buildOccupancy(){\n  occupancy = new Array(pathCount+1).fill(null).map(()=>({type:'none'}));\n  markRange(HELP_RANGE.from, HELP_RANGE.to, {type:'help'});\n  for (const block of INFO_BLOCKS){ markRange(block.from, block.to, {type:'project', block}); }\n}\n\n/* Navigation mit Fade (weiß) */\nfunction navigateWithFade(url){\n  if (!url) return;\n  saveState(u);\n  overlay.style.pointerEvents = 'auto';\n  overlay.style.opacity = '1';\n  setTimeout(()=>{ window.location.href = url; }, 600);\n}\n\n/* Videos */\nasync function ensureVideoForMesh(mesh){\n  if (!mesh.userData.videoURL) return;\n  if (mesh.userData._videoEl && mesh.userData._videoTex){\n    try{ mesh.userData._videoEl.play().catch(()=>{}); }catch{}\n    return;\n  }\n  try{\n    const { vtex, vid } = await createVideoTexture(mesh.userData.videoURL);\n    mesh.userData._videoEl  = vid;\n    mesh.userData._videoTex = vtex;\n    mesh.material = new THREE.MeshBasicMaterial({ color:0xffffff, map:vtex, side:THREE.DoubleSide, transparent:true });\n    mesh.material.needsUpdate = true;\n  }catch{}\n}\nasync function startAllVideos(){ await Promise.allSettled(phMeshes.map(m => ensureVideoForMesh(m))); }\nfunction resumeAllVideos(){\n  for (const m of phMeshes){\n    const v = m.userData._videoEl;\n    if (v && v.paused) { try{ v.play().catch(()=>{}); }catch{} }\n  }\n}\n\n/* Anzeige-Logik + InfoCard-Linking */\nlet currentProjectKey = null;\nfunction updateCardsByOccupancy(idx){\n  if (!occupancy) return;\n  const slot = occupancy[idx] || {type:'none'};\n\n  if (slot.type === 'project'){\n    const b = slot.block;\n    ic.style.display = 'block';\n    helpCard.style.display = 'none';\n    ic.style.background = b.bg || '#eee';\n    ic.style.color = b.txt || '#000';\n    icTitle.textContent = b.title || '';\n    icL1.textContent = b.l1 || '';\n    icL2.textContent = b.l2 || '';\n    icL3.textContent = b.l3 || '';\n    currentProjectKey = b.key || null;\n    return;\n  }\n  if (slot.type === 'help'){\n    ic.style.display = 'none';\n    helpCard.style.display = 'block';\n    currentProjectKey = null;\n    return;\n  }\n  ic.style.display = 'none';\n  helpCard.style.display = 'none';\n  currentProjectKey = null;\n}\n\n/* ======= Init ======= */\n(async function init(){\n  // Manifeste\n  const results = await Promise.allSettled(PROJECTS.map(fetchManifest));\n  const imagesByKey = new Map();  const videosByKey = new Map();\n  for (const res of results){\n    if (res.status!=='fulfilled') continue;\n    for (const [k,v] of res.value){\n      if (v.image && !imagesByKey.has(k)) imagesByKey.set(k, v.image);\n      if (v.video && !videosByKey.has(k)) videosByKey.set(k, v.video);\n    }\n  }\n\n  loader.load(GLB_URL, async (gltf)=>{\n    const root = gltf.scene; scene.add(root);\n    root.updateWorldMatrix(true,true);\n\n    // PATH\n    const empties=[];\n    root.traverse(o=>{ const m=/^PATH_(\\d+)/i.exec(o.name||''); if(m) empties.push({i:+m[1],obj:o}); });\n    if (empties.length){\n      empties.sort((a,b)=>a.i-b.i);\n      const pts = empties.map(n=> n.obj.getWorldPosition(new THREE.Vector3()));\n      const closedPts = (pts[0].distanceTo(pts[pts.length-1])<1e-5)?pts.slice(0,-1):pts;\n      pathCurve = new THREE.CatmullRomCurve3(closedPts, true, 'catmullrom', 0.15);\n      pathCount = pts.length;\n    } else {\n      const pts = [\n        new THREE.Vector3(-120,-40,160),\n        new THREE.Vector3(-60,20,80),\n        new THREE.Vector3(0,0,0),\n        new THREE.Vector3(40,-20,-80),\n        new THREE.Vector3(90,30,-160)\n      ];\n      pathCurve = new THREE.CatmullRomCurve3(pts, true, 'catmullrom', 0.15);\n      pathCount = pts.length;\n    }\n\n    buildOccupancy();\n\n    // Meshes + Medien\n    const jobs=[];\n    root.traverse(o=>{\n      if (!(o.isMesh && o.geometry)) return;\n\n      // Platzhalter\n      o.material = new THREE.MeshBasicMaterial({ color:0xdddddd, side:THREE.DoubleSide, transparent:true });\n\n      // Basis-Transform (Hover auf Mobile nicht benutzt)\n      o.userData.basePos   = o.position.clone();\n      o.userData.baseScale = o.scale.clone();\n\n      const key = cleanMeshKey(o.name);\n      if (key){\n        const project = detectProjectFromKey(key);\n        o.userData.project = project || null;\n        // Mobile: Mesh-Klicks deaktiviert\n        o.userData.linkURL = null;\n\n        const img = imagesByKey.get(key);\n        if (img) jobs.push(applyImage(o, img));\n\n        const vid = videosByKey.get(key);\n        if (vid) o.userData.videoURL = vid;\n      }\n      phMeshes.push(o);\n    });\n    await Promise.allSettled(jobs);\n\n    // Kamera-Start (Restore bevorzugt, sonst PATH_20), Blick 180°\n    const saved = loadState();\n    let u0=0;\n    if (saved && typeof saved.u==='number'){\n      u0 = wrap01(saved.u);\n    } else if (pathCount>0){\n      const clamped = Math.max(1, Math.min(START_PATH_INDEX, pathCount));\n      u0 = (clamped - 1) / pathCount;\n    }\n    u = u0;\n\n    const p0 = pathCurve.getPointAt(u);\n    const p1 = pathCurve.getPointAt(wrap01(u + 0.0045));\n    const forward = p1.clone().sub(p0).normalize().negate();\n    camera.position.copy(p0);\n    camera.lookAt(p0.clone().add(forward));\n\n    // Videos\n    await startAllVideos();\n  }, undefined, err=>{ console.error(err); });\n})();\n\n/* ======= Bewegung (ohne Look), Touch/Wheel ======= */\n/* vorher: BASE/4 * 2 → jetzt deutlich schneller: *4 (≈4× schneller als zuvor) */\nconst BASE_SCROLL_GAIN = 0.00006 / 4;\nconst BASE_VEL_MAX     = 0.0035  / 4;\nconst SPEED_FACTOR     = 4.0;\nconst SCROLL_GAIN = BASE_SCROLL_GAIN * SPEED_FACTOR;\nconst VEL_MAX     = BASE_VEL_MAX     * SPEED_FACTOR;\n\nconst VEL_EASE=0.12, VEL_STOP_EPS=1e-6, NO_WHEEL_TIMEOUT=140, AHEAD_T=0.0045;\n\nlet u=0, vel=0, velTarget=0, lastWheelAt=0;\n\n/* Wheel: Richtung invertiert (wie gewünscht) */\nstage.addEventListener('wheel', e=>{\n  e.preventDefault(); if(e.ctrlKey||e.metaKey) return;\n  const dy=e.deltaY||0; if(Math.abs(dy)<0.01) return;\n  lastWheelAt=performance.now();\n  velTarget=clamp(velTarget - dy*SCROLL_GAIN, -VEL_MAX, VEL_MAX);\n},{passive:false});\n\n/* Touch-Scroll (Mobile) – Richtung invertiert */\nlet tY=0, tActive=false;\nstage.addEventListener('touchstart', e=>{\n  if (!IS_TOUCH) return;\n  if (!e.touches || e.touches.length===0) return;\n  tActive=true; tY=e.touches[0].clientY;\n},{passive:true});\nstage.addEventListener('touchmove', e=>{\n  if (!IS_TOUCH || !tActive) return;\n  const y=e.touches[0].clientY;\n  const dy = (y - tY);      // invertiert: wischen nach oben = rückwärts, unten = vorwärts\n  tY = y;\n  velTarget = clamp(velTarget + dy*SCROLL_GAIN*1.0, -VEL_MAX, VEL_MAX);\n  e.preventDefault();\n},{passive:false});\nstage.addEventListener('touchend', ()=>{ tActive=false; }, {passive:true});\n\n/* InfoCard tap → Link (Mesh-Klicks sind aus) */\ndocument.getElementById('infoCard').addEventListener('click', ()=>{\n  if (!currentProjectKey) return;\n  const url = PROJECT_URLS[currentProjectKey];\n  if (url) navigateWithFade(url);\n});\n\nfunction animate(){\n  requestAnimationFrame(animate);\n\n  const now = performance.now();\n  if (now - lastWheelAt > NO_WHEEL_TIMEOUT && !tActive) velTarget = 0;\n  vel += (velTarget - vel) * VEL_EASE;\n  if (Math.abs(vel) < VEL_STOP_EPS && velTarget === 0) vel = 0;\n  u = wrap01(u + vel);\n\n  if (pathCurve){\n    const p  = pathCurve.getPointAt(u);\n    const pa = pathCurve.getPointAt(wrap01(u + AHEAD_T));\n    const forward = pa.clone().sub(p).normalize().negate();\n\n    // Mobile: Blick fix nach vorne\n    camera.position.copy(p);\n    camera.lookAt(p.clone().add(forward));\n\n    // Index 1..pathCount\n    const idx = pathCount ? ((Math.floor(u * pathCount) % pathCount) + 1) : 1;\n    updateCardsByOccupancy(idx);\n  }\n\n  renderer.render(scene, camera);\n}\nanimate();\n\n/* Sichtbarkeit / BFCache */\ndocument.addEventListener('visibilitychange', ()=>{\n  if (document.visibilityState === 'hidden'){ saveState(u); }\n  else { resumeAllVideos(); }\n});\nwindow.addEventListener('pageshow', ()=>{ resumeAllVideos(); });\n\n/* Resize */\naddEventListener('resize', ()=>{\n  renderer.setSize(stage.clientWidth, stage.clientHeight, false);\n  camera.aspect = stage.clientWidth / stage.clientHeight;\n  camera.fov = FOV_DEG;  // 120\n  camera.updateProjectionMatrix();\n});\n</script>\n</body>\n</html>","embedURL":""}},"382:6647":{"type":"FRAME","id":"382:6647","name":"Desktop","absoluteBoundingBox":{"x":-24838.0,"y":3674.0,"width":700.0,"height":1080.0},"isolatedAbsoluteRenderBounds":{"x":-24838.0,"y":3674.0,"width":700.0,"height":1080.0},"relativeTransform":[[1.0,0.0,64.0],[0.0,1.0,100.0]],"size":{"x":700.0,"y":1080.0},"fills":[{"opacity":0.0,"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true}],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"cursor":{"cursorGuid":{"sessionID":536,"localID":2897},"hotspotX":0.0,"hotspotY":0.0,"behaviorType":"cursor","cursorFileName":"f2960da792f5a46d7746f0700aada2d59535c52f.png"}},"clipsContent":true,"overflowDirection":"VERTICAL_SCROLLING","layoutMode":"VERTICAL","counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","counterAxisSizingMode":"FIXED","isBreakpointFrame":true,"children":["680:7219","808:11596","805:3563","382:6648","536:7355","807:5326","728:9149"]},"493:33014":{"type":"WIDGET","id":"493:33014","name":"Embed 6","absoluteBoundingBox":{"x":-24074.0,"y":3674.0,"width":375.0,"height":1080.0},"isolatedAbsoluteRenderBounds":{"x":-24074.0,"y":3674.0,"width":375.0,"height":1080.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":375.0,"y":1080.0},"layoutAlign":"STRETCH","layoutGrow":1.0,"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"widgetType":"GENERIC","syncedState":{"embedAllowFullscreen":"false","embedCodeType":"html","embedIframeHtml":"<!DOCTYPE html>\n<html lang=\"de\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" />\n  <title>Portfolio 3D</title>\n  <style>\n    *{box-sizing:border-box}\n    html,body{margin:0;height:100%;overflow:hidden;background:#fff;overscroll-behavior:none}\n    #stage{width:100%;height:100vh;position:relative;touch-action:none}\n    canvas{display:block;width:100%;height:100%}\n    #fade{position:fixed;inset:0;background:#fff;opacity:0;pointer-events:none;z-index:9999;transition:opacity .6s ease}\n\n    @font-face{\n      font-family:\"Space Grotesk\";\n      src:\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@d898380360fa1186a47fb2ab91f1902534642664/SpaceGrotesk[wght].woff2\") format(\"woff2-variations\"),\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@d898380360fa1186a47fb2ab91f1902534642664/SpaceGrotesk-Variable.woff2\") format(\"woff2-variations\");\n      font-weight:300 800;font-style:normal;font-display:swap;\n    }\n    .sg{font-family:\"Space Grotesk\",system-ui,-apple-system,\"Segoe UI\",Roboto,Arial,sans-serif}\n\n    #loading{\n      position:fixed;inset:0;z-index:10000;\n      display:flex;align-items:center;justify-content:center;\n      background:#0022FF;transition:opacity .4s ease;\n    }\n    #loadingText{font-weight:700;font-size:33px;color:#fff}\n\n    #infoCard{\n      position:fixed;left:17px;right:17px;bottom:17px;z-index:9000;\n      padding:14px 17px 17px;\n      background:#eee;color:#000;display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.08);\n      touch-action:manipulation;cursor:pointer;\n    }\n    .ic-title{font-weight:700;font-size:28px;line-height:1.2;margin:0 0 6px 0;white-space:nowrap}\n    .ic-row{display:flex;align-items:center;gap:7px;margin:0 0 7px 0;font-weight:450;font-size:16px;line-height:1.2}\n    .ic-row:last-child{margin-bottom:0}\n    .ic-dot{width:8px;height:8px;border-radius:50%;background:currentColor;opacity:.95;flex-shrink:0}\n\n    #helpCard{\n      position:fixed;left:17px;right:17px;bottom:17px;z-index:9000;\n      padding:14px 17px 17px;\n      background:#02f;color:#fff;display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.12);\n      touch-action:manipulation;\n    }\n    .hc-line{font-weight:700;font-size:19px;line-height:1.2;margin:0;text-align:left}\n\n    #portfolioCard{\n      position:fixed;left:17px;right:17px;bottom:17px;z-index:9000;\n      padding:14px 17px 17px;\n      background:#02f;color:#fff;display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.12);\n      pointer-events:none;\n    }\n    #portfolioCard .p-title{font-weight:700;font-size:24px;line-height:1.2;margin:0 0 8px 0}\n    #portfolioCard .p-sub{font-weight:700;font-size:15px;line-height:1.2;margin:0 0 5px 0}\n    #portfolioCard .p-text{font-weight:450;font-size:14px;line-height:1.4;margin:0 0 12px 0}\n    #portfolioCard .p-cols{display:flex;gap:12px;flex-wrap:wrap}\n    #portfolioCard .p-col{font-weight:450;font-size:13px;line-height:1.3;white-space:nowrap}\n    #portfolioCard .p-col strong{font-weight:700}\n  </style>\n\n  <link rel=\"preload\" as=\"font\" type=\"font/woff2\" crossorigin\n        href=\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@d898380360fa1186a47fb2ab91f1902534642664/SpaceGrotesk[wght].woff2\">\n  <script async src=\"https://cdn.jsdelivr.net/npm/es-module-shims@1/dist/es-module-shims.min.js\"></script>\n  <script type=\"importmap\">\n  {\"imports\":{\n    \"three\":\"https://cdn.jsdelivr.net/npm/three@0.158/build/three.module.js\",\n    \"three/addons/\":\"https://cdn.jsdelivr.net/npm/three@0.158/examples/jsm/\"\n  }}\n  </script>\n</head>\n<body>\n\n<div id=\"stage\"></div>\n<div id=\"fade\"></div>\n\n<div id=\"loading\" class=\"sg\" aria-live=\"polite\">\n  <div id=\"loadingText\">Loading</div>\n</div>\n\n<div id=\"helpCard\" class=\"sg\" aria-live=\"polite\">\n  <div class=\"hc-line\">Swipe up to move forward on a path.</div>\n</div>\n\n<div id=\"infoCard\" class=\"sg\" aria-live=\"polite\">\n  <div class=\"ic-title\" id=\"icTitle\"></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL1\"></span></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL2\"></span></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL3\"></span></div>\n</div>\n\n<div id=\"portfolioCard\" class=\"sg\" aria-live=\"polite\">\n  <div class=\"p-title\">Thanks for exploring my portfolio :)</div>\n  <div class=\"p-sub\">A few things about my portfolio:</div>\n  <div class=\"p-text\">I'm not a web developer — I'm a designer. I had a vision and made it real using today's tools.</div>\n  <div class=\"p-cols\">\n    <div class=\"p-col\"><strong>Blender</strong><br>→ .glb export</div>\n    <div class=\"p-col\"><strong>Photos</strong><br>→ GitHub repos</div>\n    <div class=\"p-col\"><strong>Code</strong><br>→ ChatGPT</div>\n    <div class=\"p-col\"><strong>Site</strong><br>→ Figma Sites</div>\n  </div>\n</div>\n\n<script>\n(function(){\n  const alias={focus:'fototage',interdependence:'bildsprache','speak-type':'nlch','dispo-flomi':'flomi'};\n  const q=new URLSearchParams(location.search);\n  const raw=(q.get('project')||'').toLowerCase();\n  const key=alias[raw]||raw;\n  const color={meetingpoint:'#C1FF6A',bilbao:'#FF3D3D',javelin:'#692FFF',bildsprache:'#180152',dampfzentrale:'#E8FA70',fototage:'#94006C',nlch:'#00A6A9',flomi:'#FF8E25',portfolio:'#02f'}[key];\n  if(color){const el=document.getElementById('loading');if(el)el.style.background=color;}\n  window.addEventListener('pageshow',(e)=>{if(e.persisted){location.reload();}});\n  window.__projectFromURL=key||null;\n})();\n</script>\n\n<script type=\"module\">\nimport * as THREE from 'three';\nimport {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';\n\n/* ── Loader ── */\nconst loading=document.getElementById('loading');\nconst loadingText=document.getElementById('loadingText');\nlet dots=0;\nconst dotTimer=setInterval(()=>{dots=(dots+1)%4;loadingText.textContent='Loading'+'.'.repeat(dots);},200);\nlet loaderHidden=false;\nfunction hideLoader(){\n  if(loaderHidden)return;loaderHidden=true;\n  try{clearInterval(dotTimer);}catch{}\n  loading.style.pointerEvents='none';\n  loading.style.opacity='0';\n  setTimeout(()=>{loading?.remove?.();},500);\n}\nconst loaderSafety=setTimeout(hideLoader,10000);\n\n/* ── Config ── */\nconst ASSET_COMMIT='c20bcb8f4817c9e64ca85f10ee23e500ff037ab1';\nconst CDN_BASE=`https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@${ASSET_COMMIT}`;\nconst GLB_URL=`${CDN_BASE}/xprt260107.glb`;\nconst START_PATH_INDEX=42;\n\nconst PROJECT_URLS={\n  bilbao:'https://www.loic-berger.ch/bilbao',\n  meetingpoint:'https://www.loic-berger.ch/meetingpoint',\n  javelin:'https://www.loic-berger.ch/javelin',\n  bildsprache:'https://www.loic-berger.ch/interdependence',\n  dampfzentrale:'https://www.loic-berger.ch/dampfzentrale',\n  fototage:'https://www.loic-berger.ch/focus',\n  nlch:'https://www.loic-berger.ch/speak-type',\n  flomi:'https://www.loic-berger.ch/dispo-flomi'\n};\n\nconst INFO_BLOCKS=[\n  {key:'meetingpoint',from:1335,to:1522,bg:'#C1FF6A',txt:'#000',title:'Meetingpoint',l1:'VJ and Music Performance',l2:'Video 20:22 min',l3:'2024'},\n  {key:'bilbao',from:1143,to:1270,bg:'#FF3D3D',txt:'#000',title:'Bilbao',l1:'Corporate Identity',l2:'Logo, Posters, Mural, Tote Bag',l3:'2024'},\n  {key:'javelin',from:1032,to:1136,bg:'#692FFF',txt:'#000',title:'Javelin',l1:'Typedesign',l2:'Regular cut, 36 Characters',l3:'2024'},\n  {key:'bildsprache',from:883,to:977,bg:'#180152',txt:'#FFFFFF',title:'Interdependence',l1:'Music, Painting',l2:'Oil on Canvas (each 117 × 85 cm) + Music',l3:'2025'},\n  {key:'dampfzentrale',from:739,to:840,bg:'#E8FA70',txt:'#000',title:'Dampfzentrale',l1:'Series of 4 posters',l2:'Digital print, A0',l3:'2024'},\n  {key:'fototage',from:605,to:700,bg:'#94006C',txt:'#FFFFFF',title:'Focus',l1:'Video Installation',l2:'Mockup of Installation',l3:'2025'},\n  {key:'nlch',from:410,to:573,bg:'#00A6A9',txt:'#000',title:'Speak Type?',l1:'Editorial, Concept',l2:'Brochure, 220 × 280 mm, 31 pages',l3:'2025'},\n  {key:'flomi',from:305,to:390,bg:'#FF8E25',txt:'#000',title:'Dispo Flomi',l1:'Poster',l2:'Poster A3, Poster-System',l3:'2025'},\n  {key:'portfolio',from:77,to:165,bg:'#02f',txt:'#fff',type:'portfolio'}\n];\n\n/* ── Utils ── */\nconst clamp=(n,a,b)=>Math.max(a,Math.min(b,n));\nconst wrap01=t=>((t%1)+1)%1;\nconst norm=s=>(s||'').normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').replace(/\\s+/g,'').toLowerCase();\nfunction fileKey(filename){\n  const base=(filename.split('/').pop()||filename);\n  const i=base.lastIndexOf('.');\n  const stem=i>-1?base.slice(0,i):base;\n  return norm(stem).replace(/[^a-z0-9_-]/g,'');\n}\nfunction cleanMeshKey(origName){\n  if(!origName)return null;\n  let name=origName.trim().replace(/^P_/,'').replace(/^p_/,'');\n  const dot=name.indexOf('.');if(dot>-1)name=name.slice(0,dot);\n  name=name.replace(/(\\d)[a-z]+$/i,'$1');\n  const m=name.match(/^(.+\\d{1,})0{1,}\\d{2,}$/);if(m)name=m[1];\n  const clean=norm(name).replace(/[^a-z0-9_-]/g,'');\n  if(!clean||clean.includes('path')||clean==='scene')return null;\n  return clean;\n}\n\n/* ── Media ── */\nconst texLoader=new THREE.TextureLoader();texLoader.setCrossOrigin('anonymous');\nfunction loadTexture(url){\n  return new Promise((resolve,reject)=>{\n    texLoader.load(url,tx=>{\n      tx.flipY=false;tx.colorSpace=THREE.SRGBColorSpace;\n      tx.minFilter=THREE.LinearMipmapLinearFilter;tx.magFilter=THREE.LinearFilter;tx.generateMipmaps=true;\n      resolve(tx);\n    },undefined,err=>reject(err));\n  });\n}\nasync function applyImage(mesh,url){\n  try{\n    const texture=await loadTexture(url);\n    mesh.material=new THREE.MeshBasicMaterial({color:0xffffff,map:texture,side:THREE.DoubleSide,transparent:true});\n    mesh.material.needsUpdate=true;\n    mesh.userData.imageMat=mesh.material;\n  }catch{}\n}\nfunction createVideoTexture(url){\n  return new Promise((resolve,reject)=>{\n    const vid=document.createElement('video');\n    vid.src=url;vid.crossOrigin='anonymous';vid.loop=true;vid.muted=true;vid.setAttribute('muted','');\n    vid.playsInline=true;vid.setAttribute('webkit-playsinline','');vid.preload='auto';\n    const start=()=>vid.play().catch(()=>{});\n    vid.addEventListener('loadeddata',start,{once:true});\n    vid.addEventListener('canplay',start,{once:true});\n    vid.addEventListener('error',()=>reject(new Error('video error '+url)),{once:true});\n    const vtex=new THREE.VideoTexture(vid);\n    vtex.flipY=false;vtex.colorSpace=THREE.SRGBColorSpace;\n    vtex.minFilter=THREE.LinearFilter;vtex.magFilter=THREE.LinearFilter;\n    vtex.generateMipmaps=false;vtex.needsUpdate=true;\n    vid.play().catch(()=>{});\n    resolve({vtex,vid});\n  });\n}\nasync function fetchManifest(project){\n  const url=`${CDN_BASE}/assets/img/${project}/manifest.json`;\n  const r=await fetch(url,{cache:'no-cache'});\n  if(!r.ok)throw new Error(`manifest missing: ${project}`);\n  const m=await r.json();\n  const base=(m.base||`assets/img/${project}`).replace(/\\/+$/,'');\n  const files=Array.isArray(m.files)?m.files:[];\n  const best=new Map();\n  for(const name of files){\n    const ext=(name.split('.').pop()||'').toLowerCase();\n    const key=fileKey(name);\n    const full=`${CDN_BASE}/${base}/${name}`;\n    const prev=best.get(key)||{project};\n    if(/(webp|jpe?g|png|gif)$/.test(ext)){if(!prev.image)prev.image=full;}\n    else if(/mp4|webm/.test(ext)){\n      const prio=ext==='mp4'?3:2;\n      if(!prev.video||prio>(prev.vprio||0)){prev.video=full;prev.vprio=prio;}\n    }\n    best.set(key,prev);\n  }\n  for(const v of best.values())delete v.vprio;\n  return best;\n}\n\n/* ── Three Setup ── */\nconst stage=document.getElementById('stage');\nconst overlay=document.getElementById('fade');\n\nconst renderer=new THREE.WebGLRenderer({antialias:true});\nrenderer.outputColorSpace=THREE.SRGBColorSpace;\nrenderer.setPixelRatio(Math.min(devicePixelRatio||1,2));\nrenderer.setSize(stage.clientWidth,stage.clientHeight,false);\nstage.appendChild(renderer.domElement);\n\nconst scene=new THREE.Scene();scene.background=new THREE.Color(0xffffff);\nconst camera=new THREE.PerspectiveCamera(75,stage.clientWidth/stage.clientHeight,0.1,5000);\nscene.add(new THREE.AmbientLight(0xffffff,1));\n\nlet pathCurve=null,pathCount=0;\nconst phMeshes=[];\nconst raycaster=new THREE.Raycaster();raycaster.near=0.1;raycaster.far=50;\n\nconst ic=document.getElementById('infoCard');\nconst icTitle=document.getElementById('icTitle');\nconst icL1=document.getElementById('icL1');\nconst icL2=document.getElementById('icL2');\nconst icL3=document.getElementById('icL3');\nconst helpCard=document.getElementById('helpCard');\nconst portfolioCard=document.getElementById('portfolioCard');\n\n/* ── Occupancy ── */\nlet occupancy=null;\nfunction idxNorm(i){return(((i-1)%pathCount)+pathCount)%pathCount+1;}\nfunction markRange(from,to,value){\n  if(!pathCount)return;\n  const a=idxNorm(from),b=idxNorm(to);\n  if(a<=b){for(let i=a;i<=b;i++)occupancy[i]=value;}\n  else{for(let i=a;i<=pathCount;i++)occupancy[i]=value;for(let i=1;i<=b;i++)occupancy[i]=value;}\n}\nfunction buildOccupancy(){\n  occupancy=new Array(pathCount+1).fill(null).map(()=>({type:'none'}));\n  const a=idxNorm(1),b=idxNorm(42);\n  if(a<=b){for(let i=a;i<=b;i++)occupancy[i]={type:'help'};}\n  else{for(let i=a;i<=pathCount;i++)occupancy[i]={type:'help'};for(let i=1;i<=b;i++)occupancy[i]={type:'help'};}\n  for(const block of INFO_BLOCKS){\n    markRange(block.from,block.to,{type:(block.type==='portfolio')?'portfolio':'project',block});\n  }\n}\nfunction getMidIndexForProject(key){\n  const b=INFO_BLOCKS.find(x=>x.key===key);\n  if(!b)return null;\n  return Math.max(1,Math.min(Math.ceil((b.from+b.to)/2),pathCount||1));\n}\nfunction updateCardsByOccupancy(idx){\n  const slot=occupancy?.[idx]||{type:'none'};\n  if(slot.type==='portfolio'){\n    ic.style.display='none';helpCard.style.display='none';portfolioCard.style.display='block';return;\n  }\n  if(slot.type==='project'){\n    const b=slot.block;\n    portfolioCard.style.display='none';ic.style.display='block';helpCard.style.display='none';\n    ic.style.background=b.bg||'#eee';ic.style.color=b.txt||'#000';\n    icTitle.textContent=b.title||'';icL1.textContent=b.l1||'';icL2.textContent=b.l2||'';icL3.textContent=b.l3||'';\n    return;\n  }\n  if(slot.type==='help'){\n    portfolioCard.style.display='none';ic.style.display='none';helpCard.style.display='block';return;\n  }\n  portfolioCard.style.display='none';ic.style.display='none';helpCard.style.display='none';\n}\n\nfunction navigateWithFade(url){\n  if(!url)return;\n  overlay.style.pointerEvents='auto';\n  overlay.style.opacity='1';\n  setTimeout(()=>{window.location.href=url;},600);\n}\n\n/* ── Movement ──\n   VEL_MAX: 4× the desktop max (fast!)\n   TOUCH_GAIN: direct px→vel, very generous\n   Sign: currentY - lastY → swipe UP = negative delta → negate = POSITIVE = forward ✓\n   Momentum FRICTION: 0.88 per frame (~60fps) — glides briefly then stops\n*/\nconst VEL_MAX    = 0.0035/3 * 0.875 * 0.65 * 0.80 * 0.80 * 0.80 * 0.80 * 4.0;\nconst TOUCH_GAIN = 0.0008;  // per px of swipe delta\nconst VEL_EASE   = 0.22;    // snappy response\nconst FRICTION   = 0.88;    // momentum decay per frame after lift\nconst AHEAD_T    = 0.0045;\n\nlet u=0, vel=0, velTarget=0;\nlet lastTouchY=0, touchActive=false;\n\n/* ── Init ── */\n(async function init(){\n  try{\n    const PROJECT_LIST=['javelin','bildsprache','meetingpoint','fototage','dampfzentrale','bilbao','flomi','nlch','portfolio'];\n    const mediaByKey=new Map();\n    const results=await Promise.allSettled(PROJECT_LIST.map(fetchManifest));\n    for(const res of results){\n      if(res.status!=='fulfilled')continue;\n      for(const [k,v] of res.value){\n        if(!mediaByKey.has(k))mediaByKey.set(k,v);\n        else{\n          const prev=mediaByKey.get(k);\n          if(!prev.image&&v.image)prev.image=v.image;\n          if(!prev.video&&v.video)prev.video=v.video;\n          mediaByKey.set(k,prev);\n        }\n      }\n    }\n\n    const gltfLoader=new GLTFLoader();\n    gltfLoader.load(GLB_URL,async(gltf)=>{\n      const root=gltf.scene;scene.add(root);root.updateWorldMatrix(true,true);\n\n      const empties=[];\n      root.traverse(o=>{const m=/^PATH_(\\d+)/i.exec(o.name||'');if(m)empties.push({i:+m[1],obj:o});});\n      if(empties.length){\n        empties.sort((a,b)=>a.i-b.i);\n        const pts=empties.map(n=>n.obj.getWorldPosition(new THREE.Vector3()));\n        const closed=(pts[0].distanceTo(pts[pts.length-1])<1e-5)?pts.slice(0,-1):pts;\n        pathCurve=new THREE.CatmullRomCurve3(closed,true,'catmullrom',0.15);\n        pathCount=pts.length;\n      }else{\n        const pts=[new THREE.Vector3(-120,-40,160),new THREE.Vector3(-60,20,80),\n                   new THREE.Vector3(0,0,0),new THREE.Vector3(40,-20,-80),new THREE.Vector3(90,30,-160)];\n        pathCurve=new THREE.CatmullRomCurve3(pts,true,'catmullrom',0.15);\n        pathCount=pts.length;\n      }\n\n      buildOccupancy();\n\n      const jobs=[];\n      root.traverse(o=>{\n        if(!(o.isMesh&&o.geometry))return;\n        o.material=new THREE.MeshBasicMaterial({color:0xdddddd,side:THREE.DoubleSide,transparent:true});\n        o.userData.basePos=o.position.clone();\n        o.userData.baseScale=o.scale.clone();\n        const key=cleanMeshKey(o.name);\n        if(key){\n          const entry=mediaByKey.get(key);\n          if(entry?.project){\n            o.userData.project=entry.project;\n            if(PROJECT_URLS[entry.project])o.userData.linkURL=PROJECT_URLS[entry.project];\n          }\n          if(entry?.image)jobs.push(applyImage(o,entry.image));\n          if(entry?.video)o.userData.videoURL=entry.video;\n        }\n        phMeshes.push(o);\n      });\n      await Promise.allSettled(jobs);\n\n      const pKey=window.__projectFromURL;\n      const mid=pKey?getMidIndexForProject(pKey):null;\n      if(mid&&pathCount>0){u=(mid-1)/pathCount;}\n      else if(pathCount>0){u=(Math.max(1,Math.min(START_PATH_INDEX,pathCount))-1)/pathCount;}\n\n      const p0=pathCurve.getPointAt(u);\n      const p1=pathCurve.getPointAt(wrap01(u+AHEAD_T));\n      camera.position.copy(p0);\n      camera.lookAt(p0.clone().add(p1.clone().sub(p0).normalize().negate()));\n\n      await Promise.allSettled(phMeshes.map(async m=>{\n        if(m.userData.videoURL){\n          try{\n            const{vtex}=await createVideoTexture(m.userData.videoURL);\n            m.material=new THREE.MeshBasicMaterial({color:0xffffff,map:vtex,side:THREE.DoubleSide,transparent:true});\n            m.material.needsUpdate=true;\n          }catch{}\n        }\n      }));\n\n      /* ── Touch events ── */\n      let tapStartX=0,tapStartY=0,tapStartTime=0;\n\n      stage.addEventListener('touchstart',e=>{\n        const t=e.touches[0];\n        lastTouchY=t.clientY;\n        touchActive=true;\n        velTarget=0; // kill momentum on new touch\n        tapStartX=t.clientX;tapStartY=t.clientY;tapStartTime=Date.now();\n      },{passive:true});\n\n      stage.addEventListener('touchmove',e=>{\n        if(!touchActive||e.touches.length!==1)return;\n        const t=e.touches[0];\n        // swipe UP: t.clientY decreases → dy negative → negate → positive → forward ✓\n        const dy=-(t.clientY-lastTouchY);\n        lastTouchY=t.clientY;\n        velTarget=clamp(velTarget+dy*TOUCH_GAIN,-VEL_MAX,VEL_MAX);\n      },{passive:true});\n\n      stage.addEventListener('touchend',e=>{\n        touchActive=false;\n        // tap detection\n        const dt=Date.now()-tapStartTime;\n        const t=e.changedTouches[0];\n        const dx=Math.abs(t.clientX-tapStartX);\n        const dy=Math.abs(t.clientY-tapStartY);\n        if(dt<250&&dx<14&&dy<14){\n          const r=renderer.domElement.getBoundingClientRect();\n          const nx=((t.clientX-r.left)/r.width)*2-1;\n          const ny=-((t.clientY-r.top)/r.height)*2+1;\n          raycaster.setFromCamera({x:nx,y:ny},camera);\n          const hit=raycaster.intersectObjects(phMeshes,true)[0]?.object||null;\n          const idx=pathCount?((Math.floor(u*pathCount)%pathCount)+1):1;\n          const slot=occupancy?.[idx];\n          let targetUrl=null;\n          if(hit){\n            const proj=hit.userData?.project||null;\n            if(proj&&PROJECT_URLS[proj])targetUrl=PROJECT_URLS[proj];\n            else if(hit.userData?.linkURL)targetUrl=hit.userData.linkURL;\n          }\n          if(!targetUrl&&slot?.type==='project'){\n            const k=slot.block.key;if(PROJECT_URLS[k])targetUrl=PROJECT_URLS[k];\n          }\n          if(targetUrl)navigateWithFade(targetUrl);\n        }\n      },{passive:true});\n\n      stage.addEventListener('touchcancel',()=>{touchActive=false;velTarget=0;},{passive:true});\n\n      ic.addEventListener('click',()=>{\n        const idx=pathCount?((Math.floor(u*pathCount)%pathCount)+1):1;\n        const slot=occupancy?.[idx];\n        if(slot?.type==='project'){\n          const k=slot.block.key;const url=PROJECT_URLS[k];if(url)navigateWithFade(url);\n        }\n      });\n\n      clearTimeout(loaderSafety);\n      hideLoader();\n    },undefined,err=>{console.error(err);clearTimeout(loaderSafety);hideLoader();});\n  }catch(e){\n    console.error(e);clearTimeout(loaderSafety);hideLoader();\n  }\n})();\n\n/* ── Render loop ── */\nfunction animate(){\n  requestAnimationFrame(animate);\n\n  if(!touchActive){\n    // friction-based momentum after finger lift\n    velTarget*=FRICTION;\n    if(Math.abs(velTarget)<1e-9)velTarget=0;\n  }\n\n  vel+=(velTarget-vel)*VEL_EASE;\n  if(Math.abs(vel)<1e-10&&velTarget===0)vel=0;\n\n  u=wrap01(u+vel);\n\n  if(pathCurve){\n    const p=pathCurve.getPointAt(u);\n    const pa=pathCurve.getPointAt(wrap01(u+AHEAD_T));\n    camera.position.copy(p);\n    camera.lookAt(p.clone().add(pa.clone().sub(p).normalize().negate()));\n\n    const idx=pathCount?((Math.floor(u*pathCount)%pathCount)+1):1;\n    updateCardsByOccupancy(idx);\n  }\n\n  renderer.render(scene,camera);\n}\nanimate();\n\n/* ── Resize ── */\naddEventListener('resize',()=>{\n  renderer.setSize(stage.clientWidth,stage.clientHeight,false);\n  camera.aspect=stage.clientWidth/stage.clientHeight;\n  camera.fov=75;\n  camera.updateProjectionMatrix();\n});\n</script>\n</body>\n</html>","embedURL":""}},"485:14099":{"type":"SVG","id":"485:14099","name":"Frame 1447","absoluteBoundingBox":{"x":-26691.0,"y":-507.5,"width":98.6073608398438,"height":61.0000076293945},"isolatedAbsoluteRenderBounds":{"x":-26691.0,"y":-507.5,"width":98.607421875,"height":61.0000076293945},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,1.49999618530273]],"size":{"x":98.6073608398438,"y":61.0000076293945},"fills":[],"strokeAlign":"INSIDE","strokes":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.10000002384186,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.10000002384186,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"hash":"3d9a757f84c0ea946aaa25ec9108349887c539f5"},"25:406":{"type":"TEXT","id":"25:406","name":"Text","absoluteBoundingBox":{"x":0.0,"y":0.0,"width":14.0,"height":12.0},"isolatedAbsoluteRenderBounds":{"x":0.237926155328751,"y":2.72727251052856,"width":12.7166194915771,"height":9.43181800842285},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":14.0,"y":12.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"strokeWeight":0.0,"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Ag","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","style":{"fontFamily":"Inter","fontPostScriptName":null,"fontStyle":"Bold","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":10.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":12.1022720336914,"lineHeightPercent":100.0,"lineHeightUnit":"INTRINSIC_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED","lineHeightPercentFontSize":100},"styleOverrideTable":{}},"234:8016":{"type":"TEXT","id":"234:8016","name":"TEXT","absoluteBoundingBox":{"x":0.0,"y":0.0,"width":26.0,"height":28.0},"isolatedAbsoluteRenderBounds":{"x":0.480000019073486,"y":8.27999877929688,"width":23.3714065551758,"height":17.6400012969971},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":26.0,"y":28.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"strokeWeight":0.0,"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Ag","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","style":{"fontFamily":"Sk-Modernist","fontPostScriptName":"Sk-Modernist-Regular","fontStyle":"Regular","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":20.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":-0.4,"letterSpacingValue":-2.0,"letterSpacingUnit":"PERCENT","lineHeightPx":28.0,"lineHeightPercent":116.666664123535,"lineHeightPercentFontSize":140.0,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"807:5326":{"mainComponentId":"493:33051","type":"INSTANCE","id":"807:5326","name":"Frame 151","absoluteBoundingBox":{"x":-24716.0,"y":3818.0,"width":104.0,"height":56.0},"isolatedAbsoluteRenderBounds":{"x":-24716.0,"y":3818.0,"width":104.0,"height":56.0},"relativeTransform":[[1.0,0.0,122.0],[0.0,1.0,144.0]],"size":{"x":104.0,"y":56.0},"blendMode":"DIFFERENCE","fills":[{"opacity":0.0,"blendMode":"DIFFERENCE","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true}],"visible":false,"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":493,"localID":35848},"event":{"interactionType":"ON_CLICK"},"actions":[{"transitionNodeID":{"sessionID":493,"localID":35769},"connectionType":"INTERNAL_NODE","navigationType":"NAVIGATE","connectionURL":"/menumobile"}],"isDeleted":false,"stateManagementVersion":1}],"behaviors":{"appear":{"otherLayer":{"sessionID":-1,"localID":-1},"trigger":"THIS_LAYER_IN_VIEW","direction":"UP","enterTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"enterState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":0.0},"exitTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"exitState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":1.0},"playsOnce":false,"behaviorType":"appear"}},"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["I807:5326;493:33052"],"componentSetId":"278:13821","componentProperties":{"Property 1":{"value":"Proj Mobile","type":"VARIANT","boundVariables":{}}},"overrides":[]},"482:4643":{"type":"TEXT","id":"482:4643","name":"Javelin","absoluteBoundingBox":{"x":-26662.0,"y":-91.0,"width":111.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26660.943359375,"y":-83.5620040893555,"width":107.5859375,"height":24.0240058898926},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":111.0,"y":40.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.04999995231628,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.04999995231628,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"characterStyleOverrides":[5,5,5,5,5,5,5],"characters":"Javelin","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":33.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{"5":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","hyperlink":{"type":"NODE","nodeID":"/javelin"},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","isOverrideOverTextStyle":true,"fontSize":33.0,"opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","inheritTextStyleId":"341:19602","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"}}},"318:17545":{"key":"1879abf6283567d3b32a3d18e3c6bfd7e8da663b","name":"TXT","styleType":"TEXT","remote":true,"description":"","id":"318:17545","assetId":"StyleId:1879abf6283567d3b32a3d18e3c6bfd7e8da663b/507:18","type":"STYLE","style":{"fontFamily":"Neue Haas Grotesk Display Pro","fontPostScriptName":"NHaasGroteskDSPro-45Lt","fontStyle":"45 Light","textAutoResize":"WIDTH_AND_HEIGHT","fontSize":20.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":32.0,"lineHeightPercent":141.843963623047,"lineHeightPercentFontSize":160.0,"lineHeightUnit":"PIXELS"}},"680:7219":{"type":"TEXT","id":"680:7219","name":"Mobile version in progress The site may experience crashes or instability.","absoluteBoundingBox":{"x":-24688.0,"y":3827.0,"width":192.0,"height":78.0},"isolatedAbsoluteRenderBounds":{"x":-24688.0,"y":3827.0,"width":192.0,"height":78.0},"relativeTransform":[[1.0,0.0,150.0],[0.0,1.0,153.0]],"size":{"x":192.0,"y":78.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"visible":false,"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"characters":"Mobile version in progress\nThe site may experience crashes or instability.","lineIndentations":[0,0],"lineTypes":["NONE","NONE"],"listStartOffsets":[],"lineStyleOverrides":[0,0],"lineTextDirections":null,"textAutoResize":"HEIGHT","textAlignHorizontal":"CENTER","listSpacing":4.0,"style":{"styleIdForText":"StyleId:489:26959","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"HEIGHT","fontVariantPosition":"NORMAL","fontSize":17.0,"textAlignHorizontal":"CENTER","textAlignVertical":"TOP","opentypeFlags":{"SS04":1,"SS01":1,"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":20.4000015258789,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{"1":{"styleIdForText":"StyleId:489:26959","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Medium","fontStyle":"Medium","fontVariations":{"Weight":450.0},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":16.0,"opentypeFlags":{"SS04":1,"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":19.2000007629395,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","inheritTextStyleId":"489:26958","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"}}},"382:6648":{"type":"WIDGET","id":"382:6648","name":"Embed 6","absoluteBoundingBox":{"x":-24838.0,"y":3674.0,"width":700.0,"height":1080.0},"isolatedAbsoluteRenderBounds":{"x":-24838.0,"y":3674.0,"width":700.0,"height":1080.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":700.0,"y":1080.0},"layoutAlign":"STRETCH","layoutGrow":1.0,"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"widgetType":"GENERIC","syncedState":{"embedAllowFullscreen":"false","embedCodeType":"html","embedIframeHtml":"<!DOCTYPE html>\n<html lang=\"de\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <title>Portfolio 3D</title>\n  <style>\n    html,body{margin:0;height:100%;overflow:hidden;background:#fff;overscroll-behavior:none}\n    #stage{width:100%;height:100vh;position:relative;touch-action:none}\n    canvas{display:block;width:100%;height:100%}\n    #fade{position:fixed;inset:0;background:#fff;opacity:0;pointer-events:none;z-index:9999;transition:opacity .6s ease}\n\n    @font-face{\n      font-family:\"Space Grotesk\";\n      src:\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@d898380360fa1186a47fb2ab91f1902534642664/SpaceGrotesk[wght].woff2\") format(\"woff2-variations\"),\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@d898380360fa1186a47fb2ab91f1902534642664/SpaceGrotesk-Variable.woff2\") format(\"woff2-variations\"),\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@d898380360fa1186a47fb2ab91f1902534642664/assets/fonts/SpaceGrotesk-Variable.woff2\") format(\"woff2-variations\");\n      font-weight:300 800; font-style:normal; font-display:swap;\n    }\n    .sg{font-family:\"Space Grotesk\",system-ui,-apple-system,\"Segoe UI\",Roboto,Arial,sans-serif}\n\n    /* Loader */\n    #loading{\n      position:fixed; inset:0; z-index:10000;\n      display:flex; align-items:center; justify-content:center;\n      background:#0022FF; transition:opacity .4s ease;\n    }\n    #loadingText{ font-weight:700; font-size:33px; color:#fff }\n\n    /* Karten ohne Rundungen */\n    #infoCard,#helpCard,#portfolioCard{ border-radius:0 }\n\n    /* Info-Card */\n    #infoCard{\n      position:fixed; left:50px; bottom:20px; z-index:9000;\n      width:345px; padding:14px 14px 10px;\n      background:#eee; color:#000; display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.08);\n      cursor:pointer;\n    }\n    .ic-title{ font-weight:700; font-size:33px; line-height:1.2; margin:0 0 4px 0; white-space:nowrap }\n    .ic-row{ display:flex; align-items:center; gap:7px; margin:0 0 5px 0; font-weight:450; font-size:19px; line-height:1.2 }\n    .ic-dot{ width:8px; height:8px; border-radius:50%; background:currentColor; opacity:.95 }\n\n    /* Help-Card */\n    #helpCard{\n      position:fixed; left:50px; bottom:20px; z-index:9000;\n      width:345px; padding:14px;\n      background:#02f; color:#fff; display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.12);\n    }\n    .hc-row{ display:flex; align-items:center; margin:0 0 7px 0; line-height:1.2 }\n    #helpCard .hc-row:last-child{ margin-bottom:0 }\n    .hc-title{ font-weight:700; font-size:19px; white-space:nowrap }\n    .hc-text{ font-weight:450; font-size:19px }\n\n    /* Portfolio-Card */\n    #portfolioCard{\n      position:fixed; left:50px; bottom:20px; z-index:9000;\n      width:clamp(699px, calc(100vw - 100px), 1200px);\n      padding:14px 50px 20px 50px;\n      background:#02f; color:#fff; display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.12);\n      pointer-events:none;\n    }\n    #portfolioCard .p-title{ font-weight:700; font-size:33px; line-height:1.2; margin:0 0 10px 0; white-space:nowrap }\n    #portfolioCard .p-sub{ font-weight:700; font-size:19px; line-height:1.2; margin:0 0 6px 0 }\n    #portfolioCard .p-text{ font-weight:450; font-size:19px; line-height:1.2; margin:0 0 25px 0 }\n    #portfolioCard .p-cols{ display:flex; gap:20px; flex-wrap:wrap }\n    #portfolioCard .p-col{ font-weight:450; font-size:19px; line-height:1.2; white-space:nowrap }\n    #portfolioCard .p-col strong{ font-weight:700 }\n\n    /* ===== UI Bottom Right (Music / Tour) ===== */\n    #uiBar{\n      position:fixed;\n      right:0; bottom:0;\n      z-index:9600;\n      display:flex;\n      align-items:stretch;\n      gap:0;\n      pointer-events:auto;\n    }\n    .uiBtn{\n      border:0;\n      background:transparent;\n      color:#000;\n      font-size:19px;\n      font-weight:450; /* Regular */\n      line-height:1.2;\n      cursor:pointer;\n      user-select:none;\n      transform-origin:100% 100%;\n      transition:transform .12s ease;\n      font-family:\"Space Grotesk\",system-ui,-apple-system,\"Segoe UI\",Roboto,Arial,sans-serif;\n    }\n    .uiBtn:hover{ transform:scale(1.1) }\n    .uiBtn:active{ transform:scale(1.08) }\n    .uiState{ font-weight:700 } /* On/Off bold */\n\n    /* spezifische Padding-Wünsche */\n    #tourBtn{ padding:20px }\n    #musicBtn{ padding:20px 50px 20px 20px } /* top right bottom left */\n\n  </style>\n\n  <link rel=\"preload\" as=\"font\" type=\"font/woff2\" crossorigin\n        href=\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@d898380360fa1186a47fb2ab91f1902534642664/SpaceGrotesk[wght].woff2\">\n\n  <script async src=\"https://cdn.jsdelivr.net/npm/es-module-shims@1/dist/es-module-shims.min.js\"></script>\n  <script type=\"importmap\">\n  { \"imports\": {\n    \"three\": \"https://cdn.jsdelivr.net/npm/three@0.158/build/three.module.js\",\n    \"three/addons/\": \"https://cdn.jsdelivr.net/npm/three@0.158/examples/jsm/\"\n  }}\n  </script>\n</head>\n\n<body>\n<div id=\"stage\"></div>\n<div id=\"fade\"></div>\n\n<!-- Loader -->\n<div id=\"loading\" class=\"sg\" aria-live=\"polite\">\n  <div id=\"loadingText\">Loading</div>\n</div>\n\n<script>\n  (function(){\n    const alias = { focus:'fototage', interdependence:'bildsprache', 'speak-type':'nlch', 'dispo-flomi':'flomi' };\n    const q = new URLSearchParams(location.search);\n    const raw = (q.get('project')||'').toLowerCase();\n    const key = alias[raw] || raw;\n    const color = {\n      meetingpoint:'#C1FF6A', bilbao:'#FF3D3D', javelin:'#692FFF',\n      bildsprache:'#180152', dampfzentrale:'#E8FA70', fototage:'#94006C',\n      nlch:'#00A6A9', flomi:'#FF8E25', portfolio:'#02f'\n    }[key];\n    if (color) { const el=document.getElementById('loading'); if (el) el.style.background=color; }\n    window.addEventListener('pageshow', (e)=>{ if(e.persisted){ location.reload(); }});\n    window.__projectFromURL = key || null;\n  })();\n</script>\n\n<!-- UI Buttons -->\n<div id=\"uiBar\">\n  <button id=\"tourBtn\" class=\"uiBtn\" type=\"button\" aria-label=\"Toggle tour\">\n    Tour: <span id=\"tourState\" class=\"uiState\">Off</span>\n  </button>\n  <button id=\"musicBtn\" class=\"uiBtn\" type=\"button\" aria-label=\"Toggle music\">\n    Music: <span id=\"musicState\" class=\"uiState\">Off</span>\n  </button>\n</div>\n\n<!-- Audio -->\n<audio id=\"bgm\" preload=\"none\" loop></audio>\n\n<!-- Help-Card -->\n<div id=\"helpCard\" class=\"sg\" aria-live=\"polite\">\n  <div class=\"hc-row\"><span class=\"hc-title\">Scroll to move forward on a path.</span></div>\n  <div class=\"hc-row\"><div class=\"hc-text\"><strong>Want to look around?</strong><br>Move your cursor.</div></div>\n  <div class=\"hc-row\"><div class=\"hc-text\"><strong>Want to open a project?</strong><br>Click on it.</div></div>\n</div>\n\n<!-- Projekt-Info-Card -->\n<div id=\"infoCard\" class=\"sg\" aria-live=\"polite\">\n  <div class=\"ic-title\" id=\"icTitle\"></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL1\"></span></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL2\"></span></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL3\"></span></div>\n</div>\n\n<!-- Portfolio-Card -->\n<div id=\"portfolioCard\" class=\"sg\" aria-live=\"polite\">\n  <div class=\"p-title\">Thanks for exploring my portfolio:)</div>\n  <div class=\"p-sub\">Here I have a few things to say about my portfolio:</div>\n  <div class=\"p-text\">\n    <p class=\"mb-0\">I’m not a web developer. I’m a designer.</p>\n    <p class=\"mb-0\">I had a vision for my portfolio—and I knew I wanted to make it real. I believe that, as a designer, I should get the most out of today’s tools and possibilities.</p>\n    <p class=\"mb-0\">I wasn’t familiar with this world of code, GitHub, branches, deployments, Blender, .json, and Figma Sites. It was a trial-and-error process that took me a huge amount of time—and I definitely underestimated it at first.</p>\n    <p>But I’m happy I made it :)</p>\n  </div>\n  <div class=\"p-cols\">\n    <div class=\"p-col\"><strong>Blender</strong><br>→ .glb export</div>\n    <div class=\"p-col\"><strong>Photo upload</strong><br>→ GitHub repos</div>\n    <div class=\"p-col\"><strong>glb to .json + other code</strong><br>→ ChatGPT</div>\n    <div class=\"p-col\"><strong>Website build</strong><br>→ Figma Sites</div>\n  </div>\n</div>\n\n<script type=\"module\">\nimport * as THREE from 'three';\nimport { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\n\n/* ===== Loader: 1 Controller, Fail-Safe ===== */\nconst loading = document.getElementById('loading');\nconst loadingText = document.getElementById('loadingText');\nlet dots = 0;\nconst dotTimer = setInterval(()=>{ dots=(dots+1)%4; loadingText.textContent='Loading'+'.'.repeat(dots); },200);\nlet loaderHidden=false;\nfunction hideLoader(){\n  if(loaderHidden) return; loaderHidden=true;\n  try{clearInterval(dotTimer);}catch{}\n  loading.style.pointerEvents='none';\n  loading.style.opacity='0';\n  setTimeout(()=>{ loading?.remove?.(); },500);\n}\nconst loaderSafety = setTimeout(hideLoader, 10000);\n\n/* ===== KONFIG ===== */\nconst ASSET_COMMIT = 'c20bcb8f4817c9e64ca85f10ee23e500ff037ab1';\nconst MUSIC_COMMIT = '63d9bb7c4fdca3cff72757e20a9c4493f9f1f6a3';\nconst CDN_BASE   = `https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@${ASSET_COMMIT}`;\nconst MUSIC_BASE = `https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@${MUSIC_COMMIT}`;\nconst GLB_URL    = `${CDN_BASE}/xprt260107.glb`;\nconst START_PATH_INDEX = 42;\n\nconst PROJECT_URLS = {\n  bilbao:        'https://www.loic-berger.ch/bilbao',\n  meetingpoint:  'https://www.loic-berger.ch/meetingpoint',\n  javelin:       'https://www.loic-berger.ch/javelin',\n  bildsprache:   'https://www.loic-berger.ch/interdependence',\n  dampfzentrale: 'https://www.loic-berger.ch/dampfzentrale',\n  fototage:      'https://www.loic-berger.ch/focus',\n  nlch:          'https://www.loic-berger.ch/speak-type',\n  flomi:         'https://www.loic-berger.ch/dispo-flomi'\n};\n\nconst INFO_BLOCKS = [\n  { key:'meetingpoint', from:1335, to:1522, bg:'#C1FF6A', txt:'#000', title:'Meetingpoint', l1:'VJ and Music Performance', l2:'Video 20:22 min', l3:'2024' },\n  { key:'bilbao', from:1143, to:1270, bg:'#FF3D3D', txt:'#000', title:'Bilbao', l1:'Corporate Identity', l2:'Logo, Posters, Mural, Tote Bag', l3:'2024' },\n  { key:'javelin', from:1032, to:1136, bg:'#692FFF', txt:'#000', title:'Javelin', l1:'Typedesign', l2:'Regular cut, 36 Characters', l3:'2024' },\n  { key:'bildsprache', from:883, to:977, bg:'#180152', txt:'#FFFFFF', title:'Interdependence', l1:'Music, Painting', l2:'Oil on Canvas (each 117 × 85 cm) + Music', l3:'2025' },\n  { key:'dampfzentrale', from:739, to:840, bg:'#E8FA70', txt:'#000', title:'Dampfzentrale', l1:'Series of 4 posters', l2:'Digital print, A0', l3:'2024' },\n  { key:'fototage', from:605, to:700, bg:'#94006C', txt:'#FFFFFF', title:'Focus', l1:'Video Installation', l2:'Mockup of Installation', l3:'2025' },\n  { key:'nlch', from:410, to:573, bg:'#00A6A9', txt:'#000', title:'Speak Type?', l1:'Editorial, Concept', l2:'Brochure, 220 × 280 mm, 31 pages', l3:'2025' },\n  { key:'flomi', from:305, to:390, bg:'#FF8E25', txt:'#000', title:'Dispo Flomi', l1:'Poster', l2:'Poster A3, Poster-System', l3:'2025' },\n  { key:'portfolio', from:77, to:165, bg:'#02f', txt:'#fff', type:'portfolio' }\n];\n\n/* ===== UI Elements ===== */\nconst musicBtn = document.getElementById('musicBtn');\nconst tourBtn  = document.getElementById('tourBtn');\nconst musicStateEl = document.getElementById('musicState');\nconst tourStateEl  = document.getElementById('tourState');\n\nfunction setState(el, on){\n  el.textContent = on ? 'On' : 'Off';\n}\n/* ===== Audio + Tour (stabil auf Chrome & Safari) =====\n   - Audio: KEIN WebAudio-Routing (das bricht oft in Chrome bei CORS/CDN)\n   - Unlock: nur AudioContext.resume() + ein einmaliges audio.load()\n   - Toggle: play/pause NUR im Button-Click (User Gesture)\n   - Tour: setzt autoTourOn zuverlässig + State\n*/\n\nlet autoTourOn = false;\n\n/* ===== Audio ===== */\nconst audio = document.getElementById('bgm');\naudio.src = `${MUSIC_BASE}/UNIVERSEV1.mp3`;\naudio.preload = 'metadata';\naudio.crossOrigin = 'anonymous';\naudio.loop = true;\n\nlet primed = false;\nlet manualPaused = true;\nlet ac = null;\n\nfunction updateMusicState(){\n  setState(musicStateEl, !audio.paused && !audio.ended);\n}\n\n// optional: hilft beim Debuggen\naudio.addEventListener('error', () => console.log('AUDIO ERROR', audio.error, audio.currentSrc));\n\nasync function primeAudioOnce(){\n  if (primed) return;\n\n  // 1) Safari/Chrome: AudioContext “entsperren” (ohne MediaElementSource!)\n  try{\n    ac = ac || new (window.AudioContext || window.webkitAudioContext)();\n    if (ac.state === 'suspended') await ac.resume();\n  }catch(_){}\n\n  // 2) einmalig laden\n  try{ audio.load(); }catch(_){}\n  primed = true;\n}\n\nmusicBtn.addEventListener('click', async (e)=>{\n  e.preventDefault();\n  e.stopPropagation();\n\n  try{\n    await primeAudioOnce();\n\n    if (audio.paused){\n      manualPaused = false;\n      await audio.play();        // muss direkt im Click passieren\n    }else{\n      manualPaused = true;\n      audio.pause();\n    }\n  }catch(err){\n    console.log('AUDIO TOGGLE FAILED', err);\n  }\n\n  updateMusicState();\n});\n\n// Wenn Browser/Loop irgendwo wieder \"play\" triggert obwohl wir Off wollten -> sofort stoppen\naudio.addEventListener('play', ()=>{\n  if (manualPaused){\n    audio.pause();\n  }\n  updateMusicState();\n});\naudio.addEventListener('pause', updateMusicState);\naudio.addEventListener('ended', updateMusicState);\n\n/* ===== Tour ===== */\ntourBtn.addEventListener('click', (e)=>{\n  e.preventDefault();\n  e.stopPropagation();\n\n  autoTourOn = !autoTourOn;\n  setState(tourStateEl, autoTourOn);\n\n  // optional: damit Tour sofort greift, auch wenn NO_WHEEL_TIMEOUT noch aktiv ist\n  lastWheelAt = 0;\n});\n/* ===== Utils ===== */\nconst clamp=(n,a,b)=>Math.max(a,Math.min(b,n));\nconst wrap01=t=>((t%1)+1)%1;\nconst norm = s => (s||'').normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').replace(/\\s+/g,'').toLowerCase();\nfunction fileKey(filename){\n  const base=(filename.split('/').pop()||filename);\n  const i=base.lastIndexOf('.');\n  const stem=i>-1?base.slice(0,i):base;\n  return norm(stem).replace(/[^a-z0-9_-]/g,'');\n}\nfunction cleanMeshKey(origName){\n  if (!origName) return null;\n  let name = origName.trim().replace(/^P_/,'').replace(/^p_/,'');\n  const dot=name.indexOf('.'); if (dot>-1) name=name.slice(0,dot);\n  name=name.replace(/(\\d)[a-z]+$/i,'$1');\n  const m=name.match(/^(.+\\d{1,})0{1,}\\d{2,}$/); if(m) name=m[1];\n  const clean=norm(name).replace(/[^a-z0-9_-]/g,'');\n  if(!clean || clean.includes('path') || clean==='scene') return null;\n  return clean;\n}\n\n/* ===== Media Loader ===== */\nconst texLoader = new THREE.TextureLoader(); texLoader.setCrossOrigin('anonymous');\nfunction loadTexture(url){\n  return new Promise((resolve,reject)=>{\n    texLoader.load(url, tx=>{\n      tx.flipY=false; tx.colorSpace=THREE.SRGBColorSpace;\n      tx.minFilter=THREE.LinearMipmapLinearFilter; tx.magFilter=THREE.LinearFilter; tx.generateMipmaps=true;\n      resolve(tx);\n    }, undefined, err=>reject(err));\n  });\n}\nasync function applyImage(mesh, url){\n  try{\n    const texture = await loadTexture(url);\n    mesh.material = new THREE.MeshBasicMaterial({ color:0xffffff, map:texture, side:THREE.DoubleSide, transparent:true });\n    mesh.material.needsUpdate = true;\n    mesh.userData.imageMat = mesh.material;\n  }catch{}\n}\nfunction createVideoTexture(url){\n  return new Promise((resolve, reject)=>{\n    const vid = document.createElement('video');\n    vid.src=url; vid.crossOrigin='anonymous'; vid.loop=true; vid.muted=true; vid.setAttribute('muted','');\n    vid.playsInline=true; vid.setAttribute('webkit-playsinline',''); vid.preload='auto';\n    const start = ()=> vid.play().catch(()=>{});\n    vid.addEventListener('loadeddata', start, {once:true});\n    vid.addEventListener('canplay',     start, {once:true});\n    vid.addEventListener('error', ()=>reject(new Error('video error '+url)), {once:true});\n    const vtex = new THREE.VideoTexture(vid);\n    vtex.flipY=false; vtex.colorSpace=THREE.SRGBColorSpace;\n    vtex.minFilter=THREE.LinearFilter; vtex.magFilter=THREE.LinearFilter;\n    vtex.generateMipmaps=false; vtex.needsUpdate=true;\n    vid.play().catch(()=>{});\n    resolve({ vtex, vid });\n  });\n}\n\n/* ===== Manifest mit Projekt-Zuordnung (FIX für Fototage/Focus/near360 etc.) ===== */\nasync function fetchManifest(project){\n  const url = `${CDN_BASE}/assets/img/${project}/manifest.json`;\n  const r = await fetch(url, { cache:'no-cache' });\n  if (!r.ok) throw new Error(`manifest missing: ${project}`);\n  const m = await r.json();\n  const base = (m.base || `assets/img/${project}`).replace(/\\/+$/,'');\n  const files = Array.isArray(m.files) ? m.files : [];\n  const best = new Map(); // key -> { image?, video?, project }\n  for (const name of files){\n    const ext=(name.split('.').pop()||'').toLowerCase();\n    const key=fileKey(name);\n    const full=`${CDN_BASE}/${base}/${name}`;\n    const prev=best.get(key)||{ project };\n\n    if (/(webp|jpe?g|png|gif)$/.test(ext)){\n      if (!prev.image) prev.image=full;\n    } else if (/mp4|webm/.test(ext)){\n      const prio=ext==='mp4'?3:2;\n      if (!prev.video || prio>(prev.vprio||0)){\n        prev.video=full; prev.vprio=prio;\n      }\n    }\n    best.set(key, prev);\n  }\n  for (const v of best.values()) delete v.vprio;\n  return best;\n}\n\n/* ===== Three Setup ===== */\nconst stage = document.getElementById('stage');\nconst overlay = document.getElementById('fade');\n\nconst renderer = new THREE.WebGLRenderer({ antialias:true });\nrenderer.outputColorSpace = THREE.SRGBColorSpace;\nrenderer.setPixelRatio(Math.min(devicePixelRatio||1,2));\nrenderer.setSize(stage.clientWidth, stage.clientHeight, false);\nstage.appendChild(renderer.domElement);\n\nconst scene = new THREE.Scene(); scene.background = new THREE.Color(0xffffff);\nconst camera = new THREE.PerspectiveCamera(60, stage.clientWidth/stage.clientHeight, 0.1, 5000);\nscene.add(new THREE.AmbientLight(0xffffff,1));\n\nlet pathCurve=null, pathCount=0;\nconst phMeshes=[];\nconst raycaster = new THREE.Raycaster(); raycaster.near=0.1; raycaster.far=50;\nlet hovered=null;\nconst HOVER_PUSH=0.09, HOVER_SCALE=1.0008, HOVER_EASE=0.28;\n\n/* Cards */\nconst ic=document.getElementById('infoCard');\nconst icTitle=document.getElementById('icTitle');\nconst icL1=document.getElementById('icL1');\nconst icL2=document.getElementById('icL2');\nconst icL3=document.getElementById('icL3');\nconst helpCard=document.getElementById('helpCard');\nconst portfolioCard=document.getElementById('portfolioCard');\n\n/* Occupancy */\nlet occupancy=null;\nfunction idxNorm(i){ return (((i-1)%pathCount)+pathCount)%pathCount+1; }\nfunction markRange(from,to,value){\n  if(!pathCount) return;\n  const a=idxNorm(from), b=idxNorm(to);\n  if(a<=b){ for(let i=a;i<=b;i++) occupancy[i]=value; }\n  else { for(let i=a;i<=pathCount;i++) occupancy[i]=value; for(let i=1;i<=b;i++) occupancy[i]=value; }\n}\nfunction buildOccupancy(){\n  occupancy=new Array(pathCount+1).fill(null).map(()=>({type:'none'}));\n  const HELP_RANGE={from:1,to:42};\n  const a=idxNorm(HELP_RANGE.from), b=idxNorm(HELP_RANGE.to);\n  if(a<=b){ for(let i=a;i<=b;i++) occupancy[i]={type:'help'}; }\n  else { for(let i=a;i<=pathCount;i++) occupancy[i]={type:'help'}; for(let i=1;i<=b;i++) occupancy[i]={type:'help'}; }\n  for(const block of INFO_BLOCKS){\n    markRange(block.from, block.to, {type:(block.type==='portfolio')?'portfolio':'project', block});\n  }\n}\nfunction getMidIndexForProject(key){\n  const b=INFO_BLOCKS.find(x=>x.key===key);\n  if(!b) return null;\n  const mid=Math.ceil((b.from+b.to)/2);\n  return Math.max(1, Math.min(mid, pathCount||mid));\n}\nfunction updateCardsByOccupancy(idx){\n  const slot=occupancy?.[idx]||{type:'none'};\n  if (slot.type==='portfolio'){\n    ic.style.display='none'; helpCard.style.display='none'; portfolioCard.style.display='block'; return;\n  }\n  if (slot.type==='project'){\n    const b=slot.block;\n    portfolioCard.style.display='none'; ic.style.display='block'; helpCard.style.display='none';\n    ic.style.background=b.bg||'#eee'; ic.style.color=b.txt||'#000';\n    icTitle.textContent=b.title||''; icL1.textContent=b.l1||''; icL2.textContent=b.l2||''; icL3.textContent=b.l3||'';\n    return;\n  }\n  if (slot.type==='help'){\n    portfolioCard.style.display='none'; ic.style.display='none'; helpCard.style.display='block'; return;\n  }\n  portfolioCard.style.display='none'; ic.style.display='none'; helpCard.style.display='none';\n}\n\n/* Navigation */\nfunction navigateWithFade(url){\n  if(!url) return;\n  overlay.style.pointerEvents='auto';\n  overlay.style.opacity='1';\n  setTimeout(()=>{ window.location.href=url; },600);\n}\n\n/* ===== Init ===== */\n(async function init(){\n  try{\n    const PROJECT_LIST = ['javelin','bildsprache','meetingpoint','fototage','dampfzentrale','bilbao','flomi','nlch','portfolio'];\n\n    // key -> {image?, video?, project}\n    const mediaByKey = new Map();\n\n    const results = await Promise.allSettled(PROJECT_LIST.map(fetchManifest));\n    for(const res of results){\n      if(res.status!=='fulfilled') continue;\n      for(const [k,v] of res.value){\n        if(!mediaByKey.has(k)) mediaByKey.set(k, v);\n        else{\n          // wenn schon da: nur fehlende Felder ergänzen, Projekt bleibt erstes\n          const prev = mediaByKey.get(k);\n          if(!prev.image && v.image) prev.image = v.image;\n          if(!prev.video && v.video) prev.video = v.video;\n          mediaByKey.set(k, prev);\n        }\n      }\n    }\n\n    const gltfLoader = new GLTFLoader();\n    gltfLoader.load(`${GLB_URL}`, async (gltf)=>{\n      const root=gltf.scene; scene.add(root); root.updateWorldMatrix(true,true);\n\n      const empties=[];\n      root.traverse(o=>{ const m=/^PATH_(\\d+)/i.exec(o.name||''); if(m) empties.push({i:+m[1],obj:o}); });\n      if(empties.length){\n        empties.sort((a,b)=>a.i-b.i);\n        const pts=empties.map(n=> n.obj.getWorldPosition(new THREE.Vector3()));\n        const closed=(pts[0].distanceTo(pts[pts.length-1])<1e-5)?pts.slice(0,-1):pts;\n        pathCurve=new THREE.CatmullRomCurve3(closed,true,'catmullrom',0.15);\n        pathCount=pts.length;\n      }else{\n        const pts=[ new THREE.Vector3(-120,-40,160), new THREE.Vector3(-60,20,80),\n                    new THREE.Vector3(0,0,0), new THREE.Vector3(40,-20,-80), new THREE.Vector3(90,30,-160) ];\n        pathCurve=new THREE.CatmullRomCurve3(pts,true,'catmullrom',0.15);\n        pathCount=pts.length;\n      }\n\n      buildOccupancy();\n\n      const jobs=[];\n      root.traverse(o=>{\n        if(!(o.isMesh && o.geometry)) return;\n        o.material=new THREE.MeshBasicMaterial({ color:0xdddddd, side:THREE.DoubleSide, transparent:true });\n        o.userData.basePos=o.position.clone();\n        o.userData.baseScale=o.scale.clone();\n        o.userData.hovT=0; o.userData.hovTarget=0;\n\n        const key=cleanMeshKey(o.name);\n\n        if(key){\n          // Projekt wird NICHT mehr via Prefix erraten, sondern aus dem Manifest (fix für near360 etc.)\n          const entry = mediaByKey.get(key);\n\n          if(entry?.project){\n            o.userData.project = entry.project;\n            if(PROJECT_URLS[entry.project]) o.userData.linkURL = PROJECT_URLS[entry.project];\n          }\n\n          if(entry?.image) jobs.push(applyImage(o, entry.image));\n          if(entry?.video) o.userData.videoURL = entry.video;\n        }\n\n        phMeshes.push(o);\n      });\n      await Promise.allSettled(jobs);\n\n      const pKey = window.__projectFromURL;\n      const mid = pKey ? getMidIndexForProject(pKey) : null;\n      let u0=0;\n      if (mid && pathCount>0){ u0=(mid-1)/pathCount; }\n      else if (pathCount>0){ u0=(Math.max(1,Math.min(START_PATH_INDEX,pathCount))-1)/pathCount; }\n      u=u0;\n\n      const p0=pathCurve.getPointAt(u);\n      const p1=pathCurve.getPointAt(wrap01(u+0.0045));\n      const forward=p1.clone().sub(p0).normalize().negate();\n      camera.position.copy(p0); camera.lookAt(p0.clone().add(forward));\n\n      await Promise.allSettled(phMeshes.map(async m=>{\n        if (m.userData.videoURL){\n          try{\n            const { vtex } = await createVideoTexture(m.userData.videoURL);\n            m.material = new THREE.MeshBasicMaterial({ color:0xffffff, map:vtex, side:THREE.DoubleSide, transparent:true });\n            m.material.needsUpdate=true;\n          }catch{}\n        }\n      }));\n\n      /* ===== CLICK: Projekte öffnen ===== */\n      stage.addEventListener('click', (e)=>{\n        const r=renderer.domElement.getBoundingClientRect();\n        const nx=((e.clientX-r.left)/r.width)*2-1;\n        const ny=-((e.clientY-r.top)/r.height)*2+1;\n        raycaster.setFromCamera({x:nx,y:ny}, camera);\n        const hit=raycaster.intersectObjects(phMeshes,true)[0]?.object || null;\n\n        const idx = pathCount ? ((Math.floor(u * pathCount) % pathCount) + 1) : 1;\n        const slot = occupancy?.[idx];\n\n        let targetUrl=null;\n        if (hit){\n          const proj = hit.userData?.project || null;\n          if (proj && PROJECT_URLS[proj]) targetUrl=PROJECT_URLS[proj];\n          else if (hit.userData?.linkURL) targetUrl=hit.userData.linkURL;\n        }\n        if (!targetUrl && slot?.type==='project'){\n          const k=slot.block.key; if (PROJECT_URLS[k]) targetUrl=PROJECT_URLS[k];\n        }\n        if (!targetUrl && slot?.type==='portfolio'){ return; }\n        if (targetUrl) navigateWithFade(targetUrl);\n      });\n\n      /* InfoCard klickbar */\n      ic.addEventListener('click', ()=>{\n        const idx = pathCount ? ((Math.floor(u * pathCount) % pathCount) + 1) : 1;\n        const slot = occupancy?.[idx];\n        if (slot?.type==='project'){\n          const k=slot.block.key; const url=PROJECT_URLS[k]; if(url) navigateWithFade(url);\n        }\n      });\n\n      clearTimeout(loaderSafety);\n      hideLoader();\n    }, undefined, err=>{ console.error(err); clearTimeout(loaderSafety); hideLoader(); });\n  }catch(e){\n    console.error(e);\n    clearTimeout(loaderSafety);\n    hideLoader();\n  }\n})();\n\n/* ===== Bewegung/Look/Hover =====\n   - MaxScroll: 20% langsamer (gegenüber deinem aktuellen Setup: VEL_MAX * 0.8)\n   - Tour: bleibt andere Richtung (negativ)\n*/\nconst BASE_SCROLL_GAIN = 0.00006/2;\nconst BASE_VEL_MAX     = 0.0035/3;\n\nconst SCROLL_GAIN = BASE_SCROLL_GAIN * 0.875;\n\n// vorher (dein Stand): *0.875*0.65*0.80*0.80*0.80\n// jetzt: nochmal ca. 20% langsamer -> *0.80 zusätzlich\nconst VEL_MAX = BASE_VEL_MAX * 0.875 * 0.65 * 0.80 * 0.80 * 0.80 * 0.80;\n\nconst TOUR_VEL = -VEL_MAX * 0.15;\n\nconst VEL_EASE=0.12, VEL_STOP_EPS=1e-6, NO_WHEEL_TIMEOUT=140, AHEAD_T=0.0045;\nconst YAW_MAX=Math.PI/2, PITCH_MAX=100*Math.PI/180, YAW_GAMMA=1.25, PITCH_GAMMA=1.35, LOOK_EASE=0.18;\nconst RECENTER_STEP=0.06, RECENTER_DECAY=0.985;\n\nlet u=0, vel=0, velTarget=0, lastWheelAt=0;\nlet yawAngle=0, pitchAngle=0, yawTarget=0, pitchTarget=0, recenterMix=0;\nconst pointer={x:0,y:0};\n\nstage.addEventListener('wheel', e=>{\n  e.preventDefault(); if(e.ctrlKey||e.metaKey) return;\n  const dy=e.deltaY||0; if(Math.abs(dy)<0.01) return;\n  lastWheelAt=performance.now();\n\n  const base = autoTourOn ? TOUR_VEL : 0;\n  velTarget = clamp(base + (velTarget - base) + dy*SCROLL_GAIN, -VEL_MAX, VEL_MAX);\n\n  recenterMix=Math.min(1, recenterMix + RECENTER_STEP);\n},{passive:false});\nstage.addEventListener('mousemove', e=>{\n  const r=renderer.domElement.getBoundingClientRect();\n  pointer.x=((e.clientX-r.left)/r.width)*2-1;\n  pointer.y=((e.clientY-r.top )/r.height)*2-1;\n},{passive:true});\n\nfunction powSigned(n,g){ return Math.sign(n)*Math.pow(Math.abs(n),g); }\nfunction lookWithYawPitch(forward, yaw, pitch){\n  const qYaw=new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0),yaw);\n  const yawed=forward.clone().applyQuaternion(qYaw);\n  const right=new THREE.Vector3().crossVectors(yawed,new THREE.Vector3(0,1,0)).normalize();\n  const qPitch=new THREE.Quaternion().setFromAxisAngle(right,pitch);\n  return yawed.clone().applyQuaternion(qPitch).normalize();\n}\nfunction hoverOffsetLocal(mesh, push){\n  const parent=mesh.parent;\n  const baseLocal=mesh.userData.basePos;\n  const baseWorld=baseLocal.clone().applyMatrix4(parent.matrixWorld);\n  const dir=camera.position.clone().sub(baseWorld).normalize();\n  const worldDelta=dir.multiplyScalar(push);\n  const aLocal=parent.worldToLocal(baseWorld.clone());\n  const bLocal=parent.worldToLocal(baseWorld.clone().add(worldDelta));\n  return bLocal.sub(aLocal);\n}\n\nfunction animate(){\n  requestAnimationFrame(animate);\n\n  const now=performance.now();\n  const base = autoTourOn ? TOUR_VEL : 0;\n\n  if(now-lastWheelAt>NO_WHEEL_TIMEOUT){\n    velTarget = base;\n  }else{\n    velTarget = clamp(velTarget, -VEL_MAX, VEL_MAX);\n  }\n\n  vel += (velTarget-vel)*VEL_EASE;\n  if(Math.abs(vel)<VEL_STOP_EPS && velTarget===0) vel=0;\n\n  u=wrap01(u+vel);\n  if(recenterMix>0) recenterMix*=RECENTER_DECAY;\n\n  if(pathCurve){\n    const p = pathCurve.getPointAt(u);\n    const pa= pathCurve.getPointAt(wrap01(u+AHEAD_T));\n    const forward=pa.clone().sub(p).normalize().negate();\n\n    const yawTargetRaw=YAW_MAX*powSigned(-pointer.x,YAW_GAMMA);\n    const pitchTargetRaw=PITCH_MAX*powSigned(-pointer.y,PITCH_GAMMA);\n    yawTarget=THREE.MathUtils.clamp(yawTargetRaw, -YAW_MAX, YAW_MAX);\n    pitchTarget=THREE.MathUtils.clamp(pitchTargetRaw, -PITCH_MAX, PITCH_MAX);\n\n    const yawBlend=yawTarget*(1-recenterMix), pitchBlend=pitchTarget*(1-recenterMix);\n    yawAngle+= (yawBlend-yawAngle)*LOOK_EASE;\n    pitchAngle+=(pitchBlend-pitchAngle)*LOOK_EASE;\n\n    const look=lookWithYawPitch(forward,yawAngle,pitchAngle);\n    camera.position.copy(p);\n    camera.lookAt(p.clone().add(look));\n\n    const idx = pathCount ? ((Math.floor(u * pathCount) % pathCount) + 1) : 1;\n    updateCardsByOccupancy(idx);\n\n    raycaster.setFromCamera({x:(pointer.x), y:(-pointer.y)}, camera);\n    const hit=raycaster.intersectObjects(phMeshes,true)[0]?.object || null;\n    if(hit!==hovered){\n      if(hovered) hovered.userData.hovTarget=0;\n      hovered=hit;\n      if(hovered) hovered.userData.hovTarget=1;\n    }\n    for(const m of phMeshes){\n      const ud=m.userData;\n      if (ud.project === 'portfolio'){\n        ud.hovT = 0;\n        m.position.copy(ud.basePos);\n        m.scale.copy(ud.baseScale);\n        continue;\n      }\n      ud.hovT += (ud.hovTarget-ud.hovT)*HOVER_EASE;\n      if(ud.hovT>1e-3){\n        const deltaLocal=hoverOffsetLocal(m,HOVER_PUSH);\n        m.position.copy(ud.basePos).addScaledVector(deltaLocal, ud.hovT);\n        const s=1+(HOVER_SCALE-1)*ud.hovT;\n        m.scale.copy(ud.baseScale).multiplyScalar(s);\n      }else{\n        m.position.copy(ud.basePos);\n        m.scale.copy(ud.baseScale);\n      }\n    }\n  }\n\n  renderer.render(scene,camera);\n}\nanimate();\n\n/* Resize */\naddEventListener('resize', ()=>{\n  renderer.setSize(stage.clientWidth, stage.clientHeight, false);\n  camera.aspect = stage.clientWidth / stage.clientHeight;\n  camera.fov = 60;\n  camera.updateProjectionMatrix();\n});\n</script>\n</body>\n</html>","embedURL":""}},"482:4642":{"type":"FRAME","id":"482:4642","name":"Frame 174","absoluteBoundingBox":{"x":-26662.0,"y":-91.0,"width":274.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":-91.0,"width":274.0,"height":40.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,80.0]],"size":{"x":274.0,"y":40.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":4.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["482:4643"]},"808:11596":{"type":"WIDGET","id":"808:11596","name":"Embed 6","absoluteBoundingBox":{"x":-24336.0,"y":3427.0,"width":375.0,"height":1080.0},"isolatedAbsoluteRenderBounds":{"x":-24336.0,"y":3427.0,"width":375.0,"height":1080.0},"relativeTransform":[[1.0,0.0,502.0],[0.0,1.0,-247.0]],"size":{"x":375.0,"y":1080.0},"visible":false,"layoutAlign":"STRETCH","layoutGrow":1.0,"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"widgetType":"GENERIC","syncedState":{"embedAllowFullscreen":"false","embedCodeType":"html","embedIframeHtml":"<!DOCTYPE html>\n<html lang=\"de\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" />\n  <title>Portfolio 3D</title>\n  <style>\n    html,body{margin:0;height:100%;overflow:hidden;background:#fff;overscroll-behavior:none}\n    #stage{width:100%;height:100vh;position:relative;touch-action:none}\n    canvas{display:block;width:100%;height:100%}\n    #fade{position:fixed;inset:0;background:#fff;opacity:0;pointer-events:none;z-index:9999;transition:opacity .6s ease}\n\n    @font-face{\n      font-family:\"Space Grotesk\";\n      src:\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@d898380360fa1186a47fb2ab91f1902534642664/SpaceGrotesk[wght].woff2\") format(\"woff2-variations\"),\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@d898380360fa1186a47fb2ab91f1902534642664/SpaceGrotesk-Variable.woff2\") format(\"woff2-variations\");\n      font-weight:300 800; font-style:normal; font-display:swap;\n    }\n    .sg{font-family:\"Space Grotesk\",system-ui,-apple-system,\"Segoe UI\",Roboto,Arial,sans-serif}\n\n    /* Loader */\n    #loading{\n      position:fixed; inset:0; z-index:10000;\n      display:flex; align-items:center; justify-content:center;\n      background:#0022FF; transition:opacity .4s ease;\n    }\n    #loadingText{ font-weight:700; font-size:33px; color:#fff }\n\n    /* No rounded corners */\n    #infoCard,#helpCard,#portfolioCard{ border-radius:0 }\n\n    /* Info-Card (mobile): full width, bottom, no rounded corners */\n    #infoCard{\n      position:fixed; left:17px; right:17px; bottom:17px; z-index:9000;\n      padding:14px 17px 17px;\n      background:#eee; color:#000; display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.08);\n      touch-action:manipulation;\n      cursor:pointer;\n    }\n    .ic-title{ font-weight:700; font-size:28px; line-height:1.2; margin:0 0 6px 0; white-space:nowrap }\n    .ic-row{ display:flex; align-items:center; gap:7px; margin:0 0 7px 0; font-weight:450; font-size:16px; line-height:1.2 }\n    .ic-row:last-child{ margin-bottom:0 }\n    .ic-dot{ width:8px; height:8px; border-radius:50%; background:currentColor; opacity:.95; flex-shrink:0 }\n\n    /* Help-Card (mobile): full width, bottom, no rounded corners */\n    #helpCard{\n      position:fixed; left:17px; right:17px; bottom:17px; z-index:9000;\n      padding:14px 17px 17px;\n      background:#02f; color:#fff; display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.12);\n      touch-action:manipulation;\n    }\n    .hc-line{ font-weight:700; font-size:19px; line-height:1.2; margin:0; white-space:nowrap; text-align:left }\n\n    /* Portfolio-Card (mobile): full width, bottom, no rounded corners */\n    #portfolioCard{\n      position:fixed; left:17px; right:17px; bottom:17px; z-index:9000;\n      padding:14px 17px 17px;\n      background:#02f; color:#fff; display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.12);\n      pointer-events:none;\n      touch-action:manipulation;\n    }\n    #portfolioCard .p-title{ font-weight:700; font-size:24px; line-height:1.2; margin:0 0 8px 0 }\n    #portfolioCard .p-sub{ font-weight:700; font-size:15px; line-height:1.2; margin:0 0 5px 0 }\n    #portfolioCard .p-text{ font-weight:450; font-size:14px; line-height:1.4; margin:0 0 12px 0 }\n    #portfolioCard .p-cols{ display:flex; gap:12px; flex-wrap:wrap }\n    #portfolioCard .p-col{ font-weight:450; font-size:13px; line-height:1.3; white-space:nowrap }\n    #portfolioCard .p-col strong{ font-weight:700 }\n  </style>\n\n  <link rel=\"preload\" as=\"font\" type=\"font/woff2\" crossorigin\n        href=\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@d898380360fa1186a47fb2ab91f1902534642664/SpaceGrotesk[wght].woff2\">\n\n  <script async src=\"https://cdn.jsdelivr.net/npm/es-module-shims@1/dist/es-module-shims.min.js\"></script>\n  <script type=\"importmap\">\n  { \"imports\": {\n    \"three\": \"https://cdn.jsdelivr.net/npm/three@0.158/build/three.module.js\",\n    \"three/addons/\": \"https://cdn.jsdelivr.net/npm/three@0.158/examples/jsm/\"\n  }}\n  </script>\n</head>\n\n<body>\n<div id=\"stage\"></div>\n<div id=\"fade\"></div>\n\n<!-- Loader -->\n<div id=\"loading\" class=\"sg\" aria-live=\"polite\">\n  <div id=\"loadingText\">Loading</div>\n</div>\n\n<!-- Help-Card -->\n<div id=\"helpCard\" class=\"sg\" aria-live=\"polite\">\n  <div class=\"hc-line\">Scroll to move forward on a path.</div>\n</div>\n\n<!-- Projekt-Info-Card -->\n<div id=\"infoCard\" class=\"sg\" aria-live=\"polite\">\n  <div class=\"ic-title\" id=\"icTitle\"></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL1\"></span></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL2\"></span></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL3\"></span></div>\n</div>\n\n<!-- Portfolio-Card -->\n<div id=\"portfolioCard\" class=\"sg\" aria-live=\"polite\">\n  <div class=\"p-title\">Thanks for exploring my portfolio :)</div>\n  <div class=\"p-sub\">A few things about my portfolio:</div>\n  <div class=\"p-text\">\n    I'm not a web developer — I'm a designer. I had a vision and made it real using today's tools.\n  </div>\n  <div class=\"p-cols\">\n    <div class=\"p-col\"><strong>Blender</strong><br>→ .glb export</div>\n    <div class=\"p-col\"><strong>Photos</strong><br>→ GitHub repos</div>\n    <div class=\"p-col\"><strong>Code</strong><br>→ ChatGPT</div>\n    <div class=\"p-col\"><strong>Site</strong><br>→ Figma Sites</div>\n  </div>\n</div>\n\n<script>\n  (function(){\n    const alias = { focus:'fototage', interdependence:'bildsprache', 'speak-type':'nlch', 'dispo-flomi':'flomi' };\n    const q = new URLSearchParams(location.search);\n    const raw = (q.get('project')||'').toLowerCase();\n    const key = alias[raw] || raw;\n    const color = {\n      meetingpoint:'#C1FF6A', bilbao:'#FF3D3D', javelin:'#692FFF',\n      bildsprache:'#180152', dampfzentrale:'#E8FA70', fototage:'#94006C',\n      nlch:'#00A6A9', flomi:'#FF8E25', portfolio:'#02f'\n    }[key];\n    if (color) { const el=document.getElementById('loading'); if (el) el.style.background=color; }\n    window.addEventListener('pageshow', (e)=>{ if(e.persisted){ location.reload(); }});\n    window.__projectFromURL = key || null;\n  })();\n</script>\n\n<script type=\"module\">\nimport * as THREE from 'three';\nimport { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\n\n/* ===== Loader ===== */\nconst loading = document.getElementById('loading');\nconst loadingText = document.getElementById('loadingText');\nlet dots = 0;\nconst dotTimer = setInterval(()=>{ dots=(dots+1)%4; loadingText.textContent='Loading'+'.'.repeat(dots); },200);\nlet loaderHidden=false;\nfunction hideLoader(){\n  if(loaderHidden) return; loaderHidden=true;\n  try{clearInterval(dotTimer);}catch{}\n  loading.style.pointerEvents='none';\n  loading.style.opacity='0';\n  setTimeout(()=>{ loading?.remove?.(); },500);\n}\nconst loaderSafety = setTimeout(hideLoader, 10000);\n\n/* ===== KONFIG ===== */\nconst ASSET_COMMIT = 'c20bcb8f4817c9e64ca85f10ee23e500ff037ab1';\nconst CDN_BASE   = `https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@${ASSET_COMMIT}`;\nconst GLB_URL    = `${CDN_BASE}/xprt260107.glb`;\nconst START_PATH_INDEX = 42;\n\nconst PROJECT_URLS = {\n  bilbao:        'https://www.loic-berger.ch/bilbao',\n  meetingpoint:  'https://www.loic-berger.ch/meetingpoint',\n  javelin:       'https://www.loic-berger.ch/javelin',\n  bildsprache:   'https://www.loic-berger.ch/interdependence',\n  dampfzentrale: 'https://www.loic-berger.ch/dampfzentrale',\n  fototage:      'https://www.loic-berger.ch/focus',\n  nlch:          'https://www.loic-berger.ch/speak-type',\n  flomi:         'https://www.loic-berger.ch/dispo-flomi'\n};\n\nconst INFO_BLOCKS = [\n  { key:'meetingpoint', from:1335, to:1522, bg:'#C1FF6A', txt:'#000', title:'Meetingpoint', l1:'VJ and Music Performance', l2:'Video 20:22 min', l3:'2024' },\n  { key:'bilbao', from:1143, to:1270, bg:'#FF3D3D', txt:'#000', title:'Bilbao', l1:'Corporate Identity', l2:'Logo, Posters, Mural, Tote Bag', l3:'2024' },\n  { key:'javelin', from:1032, to:1136, bg:'#692FFF', txt:'#000', title:'Javelin', l1:'Typedesign', l2:'Regular cut, 36 Characters', l3:'2024' },\n  { key:'bildsprache', from:883, to:977, bg:'#180152', txt:'#FFFFFF', title:'Interdependence', l1:'Music, Painting', l2:'Oil on Canvas (each 117 × 85 cm) + Music', l3:'2025' },\n  { key:'dampfzentrale', from:739, to:840, bg:'#E8FA70', txt:'#000', title:'Dampfzentrale', l1:'Series of 4 posters', l2:'Digital print, A0', l3:'2024' },\n  { key:'fototage', from:605, to:700, bg:'#94006C', txt:'#FFFFFF', title:'Focus', l1:'Video Installation', l2:'Mockup of Installation', l3:'2025' },\n  { key:'nlch', from:410, to:573, bg:'#00A6A9', txt:'#000', title:'Speak Type?', l1:'Editorial, Concept', l2:'Brochure, 220 × 280 mm, 31 pages', l3:'2025' },\n  { key:'flomi', from:305, to:390, bg:'#FF8E25', txt:'#000', title:'Dispo Flomi', l1:'Poster', l2:'Poster A3, Poster-System', l3:'2025' },\n  { key:'portfolio', from:77, to:165, bg:'#02f', txt:'#fff', type:'portfolio' }\n];\n\n/* ===== Utils ===== */\nconst clamp=(n,a,b)=>Math.max(a,Math.min(b,n));\nconst wrap01=t=>((t%1)+1)%1;\nconst norm = s => (s||'').normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').replace(/\\s+/g,'').toLowerCase();\nfunction fileKey(filename){\n  const base=(filename.split('/').pop()||filename);\n  const i=base.lastIndexOf('.');\n  const stem=i>-1?base.slice(0,i):base;\n  return norm(stem).replace(/[^a-z0-9_-]/g,'');\n}\nfunction cleanMeshKey(origName){\n  if (!origName) return null;\n  let name = origName.trim().replace(/^P_/,'').replace(/^p_/,'');\n  const dot=name.indexOf('.'); if (dot>-1) name=name.slice(0,dot);\n  name=name.replace(/(\\d)[a-z]+$/i,'$1');\n  const m=name.match(/^(.+\\d{1,})0{1,}\\d{2,}$/); if(m) name=m[1];\n  const clean=norm(name).replace(/[^a-z0-9_-]/g,'');\n  if(!clean || clean.includes('path') || clean==='scene') return null;\n  return clean;\n}\n\n/* ===== Media Loader ===== */\nconst texLoader = new THREE.TextureLoader(); texLoader.setCrossOrigin('anonymous');\nfunction loadTexture(url){\n  return new Promise((resolve,reject)=>{\n    texLoader.load(url, tx=>{\n      tx.flipY=false; tx.colorSpace=THREE.SRGBColorSpace;\n      tx.minFilter=THREE.LinearMipmapLinearFilter; tx.magFilter=THREE.LinearFilter; tx.generateMipmaps=true;\n      resolve(tx);\n    }, undefined, err=>reject(err));\n  });\n}\nasync function applyImage(mesh, url){\n  try{\n    const texture = await loadTexture(url);\n    mesh.material = new THREE.MeshBasicMaterial({ color:0xffffff, map:texture, side:THREE.DoubleSide, transparent:true });\n    mesh.material.needsUpdate = true;\n    mesh.userData.imageMat = mesh.material;\n  }catch{}\n}\nfunction createVideoTexture(url){\n  return new Promise((resolve, reject)=>{\n    const vid = document.createElement('video');\n    vid.src=url; vid.crossOrigin='anonymous'; vid.loop=true; vid.muted=true; vid.setAttribute('muted','');\n    vid.playsInline=true; vid.setAttribute('webkit-playsinline',''); vid.preload='auto';\n    const start = ()=> vid.play().catch(()=>{});\n    vid.addEventListener('loadeddata', start, {once:true});\n    vid.addEventListener('canplay',     start, {once:true});\n    vid.addEventListener('error', ()=>reject(new Error('video error '+url)), {once:true});\n    const vtex = new THREE.VideoTexture(vid);\n    vtex.flipY=false; vtex.colorSpace=THREE.SRGBColorSpace;\n    vtex.minFilter=THREE.LinearFilter; vtex.magFilter=THREE.LinearFilter;\n    vtex.generateMipmaps=false; vtex.needsUpdate=true;\n    vid.play().catch(()=>{});\n    resolve({ vtex, vid });\n  });\n}\n\nasync function fetchManifest(project){\n  const url = `${CDN_BASE}/assets/img/${project}/manifest.json`;\n  const r = await fetch(url, { cache:'no-cache' });\n  if (!r.ok) throw new Error(`manifest missing: ${project}`);\n  const m = await r.json();\n  const base = (m.base || `assets/img/${project}`).replace(/\\/+$/,'');\n  const files = Array.isArray(m.files) ? m.files : [];\n  const best = new Map();\n  for (const name of files){\n    const ext=(name.split('.').pop()||'').toLowerCase();\n    const key=fileKey(name);\n    const full=`${CDN_BASE}/${base}/${name}`;\n    const prev=best.get(key)||{ project };\n    if (/(webp|jpe?g|png|gif)$/.test(ext)){\n      if (!prev.image) prev.image=full;\n    } else if (/mp4|webm/.test(ext)){\n      const prio=ext==='mp4'?3:2;\n      if (!prev.video || prio>(prev.vprio||0)){ prev.video=full; prev.vprio=prio; }\n    }\n    best.set(key, prev);\n  }\n  for (const v of best.values()) delete v.vprio;\n  return best;\n}\n\n/* ===== Three Setup ===== */\nconst stage = document.getElementById('stage');\nconst overlay = document.getElementById('fade');\n\nconst renderer = new THREE.WebGLRenderer({ antialias:true });\nrenderer.outputColorSpace = THREE.SRGBColorSpace;\nrenderer.setPixelRatio(Math.min(devicePixelRatio||1,2));\nrenderer.setSize(stage.clientWidth, stage.clientHeight, false);\nstage.appendChild(renderer.domElement);\n\nconst scene = new THREE.Scene(); scene.background = new THREE.Color(0xffffff);\nconst camera = new THREE.PerspectiveCamera(60, stage.clientWidth/stage.clientHeight, 0.1, 5000);\nscene.add(new THREE.AmbientLight(0xffffff,1));\n\nlet pathCurve=null, pathCount=0;\nconst phMeshes=[];\nconst raycaster = new THREE.Raycaster(); raycaster.near=0.1; raycaster.far=50;\n\n/* Cards */\nconst ic=document.getElementById('infoCard');\nconst icTitle=document.getElementById('icTitle');\nconst icL1=document.getElementById('icL1');\nconst icL2=document.getElementById('icL2');\nconst icL3=document.getElementById('icL3');\nconst helpCard=document.getElementById('helpCard');\nconst portfolioCard=document.getElementById('portfolioCard');\n\n/* Occupancy */\nlet occupancy=null;\nfunction idxNorm(i){ return (((i-1)%pathCount)+pathCount)%pathCount+1; }\nfunction markRange(from,to,value){\n  if(!pathCount) return;\n  const a=idxNorm(from), b=idxNorm(to);\n  if(a<=b){ for(let i=a;i<=b;i++) occupancy[i]=value; }\n  else { for(let i=a;i<=pathCount;i++) occupancy[i]=value; for(let i=1;i<=b;i++) occupancy[i]=value; }\n}\nfunction buildOccupancy(){\n  occupancy=new Array(pathCount+1).fill(null).map(()=>({type:'none'}));\n  const HELP_RANGE={from:1,to:42};\n  const a=idxNorm(HELP_RANGE.from), b=idxNorm(HELP_RANGE.to);\n  if(a<=b){ for(let i=a;i<=b;i++) occupancy[i]={type:'help'}; }\n  else { for(let i=a;i<=pathCount;i++) occupancy[i]={type:'help'}; for(let i=1;i<=b;i++) occupancy[i]={type:'help'}; }\n  for(const block of INFO_BLOCKS){\n    markRange(block.from, block.to, {type:(block.type==='portfolio')?'portfolio':'project', block});\n  }\n}\nfunction getMidIndexForProject(key){\n  const b=INFO_BLOCKS.find(x=>x.key===key);\n  if(!b) return null;\n  const mid=Math.ceil((b.from+b.to)/2);\n  return Math.max(1, Math.min(mid, pathCount||mid));\n}\nfunction updateCardsByOccupancy(idx){\n  const slot=occupancy?.[idx]||{type:'none'};\n  if (slot.type==='portfolio'){\n    ic.style.display='none'; helpCard.style.display='none'; portfolioCard.style.display='block'; return;\n  }\n  if (slot.type==='project'){\n    const b=slot.block;\n    portfolioCard.style.display='none'; ic.style.display='block'; helpCard.style.display='none';\n    ic.style.background=b.bg||'#eee'; ic.style.color=b.txt||'#000';\n    icTitle.textContent=b.title||''; icL1.textContent=b.l1||''; icL2.textContent=b.l2||''; icL3.textContent=b.l3||'';\n    return;\n  }\n  if (slot.type==='help'){\n    portfolioCard.style.display='none'; ic.style.display='none'; helpCard.style.display='block'; return;\n  }\n  portfolioCard.style.display='none'; ic.style.display='none'; helpCard.style.display='none';\n}\n\nfunction navigateWithFade(url){\n  if(!url) return;\n  overlay.style.pointerEvents='auto';\n  overlay.style.opacity='1';\n  setTimeout(()=>{ window.location.href=url; },600);\n}\n\n/* ===== Init ===== */\n(async function init(){\n  try{\n    const PROJECT_LIST = ['javelin','bildsprache','meetingpoint','fototage','dampfzentrale','bilbao','flomi','nlch','portfolio'];\n    const mediaByKey = new Map();\n    const results = await Promise.allSettled(PROJECT_LIST.map(fetchManifest));\n    for(const res of results){\n      if(res.status!=='fulfilled') continue;\n      for(const [k,v] of res.value){\n        if(!mediaByKey.has(k)) mediaByKey.set(k, v);\n        else{\n          const prev = mediaByKey.get(k);\n          if(!prev.image && v.image) prev.image = v.image;\n          if(!prev.video && v.video) prev.video = v.video;\n          mediaByKey.set(k, prev);\n        }\n      }\n    }\n\n    const gltfLoader = new GLTFLoader();\n    gltfLoader.load(`${GLB_URL}`, async (gltf)=>{\n      const root=gltf.scene; scene.add(root); root.updateWorldMatrix(true,true);\n\n      const empties=[];\n      root.traverse(o=>{ const m=/^PATH_(\\d+)/i.exec(o.name||''); if(m) empties.push({i:+m[1],obj:o}); });\n      if(empties.length){\n        empties.sort((a,b)=>a.i-b.i);\n        const pts=empties.map(n=> n.obj.getWorldPosition(new THREE.Vector3()));\n        const closed=(pts[0].distanceTo(pts[pts.length-1])<1e-5)?pts.slice(0,-1):pts;\n        pathCurve=new THREE.CatmullRomCurve3(closed,true,'catmullrom',0.15);\n        pathCount=pts.length;\n      }else{\n        const pts=[ new THREE.Vector3(-120,-40,160), new THREE.Vector3(-60,20,80),\n                    new THREE.Vector3(0,0,0), new THREE.Vector3(40,-20,-80), new THREE.Vector3(90,30,-160) ];\n        pathCurve=new THREE.CatmullRomCurve3(pts,true,'catmullrom',0.15);\n        pathCount=pts.length;\n      }\n\n      buildOccupancy();\n\n      const jobs=[];\n      root.traverse(o=>{\n        if(!(o.isMesh && o.geometry)) return;\n        o.material=new THREE.MeshBasicMaterial({ color:0xdddddd, side:THREE.DoubleSide, transparent:true });\n        o.userData.basePos=o.position.clone();\n        o.userData.baseScale=o.scale.clone();\n\n        const key=cleanMeshKey(o.name);\n        if(key){\n          const entry = mediaByKey.get(key);\n          if(entry?.project){\n            o.userData.project = entry.project;\n            if(PROJECT_URLS[entry.project]) o.userData.linkURL = PROJECT_URLS[entry.project];\n          }\n          if(entry?.image) jobs.push(applyImage(o, entry.image));\n          if(entry?.video) o.userData.videoURL = entry.video;\n        }\n        phMeshes.push(o);\n      });\n      await Promise.allSettled(jobs);\n\n      const pKey = window.__projectFromURL;\n      const mid = pKey ? getMidIndexForProject(pKey) : null;\n      let u0=0;\n      if (mid && pathCount>0){ u0=(mid-1)/pathCount; }\n      else if (pathCount>0){ u0=(Math.max(1,Math.min(START_PATH_INDEX,pathCount))-1)/pathCount; }\n      u=u0;\n\n      const p0=pathCurve.getPointAt(u);\n      const p1=pathCurve.getPointAt(wrap01(u+0.0045));\n      const forward=p1.clone().sub(p0).normalize().negate();\n      camera.position.copy(p0); camera.lookAt(p0.clone().add(forward));\n\n      await Promise.allSettled(phMeshes.map(async m=>{\n        if (m.userData.videoURL){\n          try{\n            const { vtex } = await createVideoTexture(m.userData.videoURL);\n            m.material = new THREE.MeshBasicMaterial({ color:0xffffff, map:vtex, side:THREE.DoubleSide, transparent:true });\n            m.material.needsUpdate=true;\n          }catch{}\n        }\n      }));\n\n      /* ===== TOUCH TAP: open project ===== */\n      let touchStartX=0, touchStartY=0, touchStartTime=0;\n\n      stage.addEventListener('touchstart', e=>{\n        const t=e.touches[0];\n        touchStartX=t.clientX; touchStartY=t.clientY; touchStartTime=Date.now();\n      },{passive:true});\n\n      stage.addEventListener('touchend', e=>{\n        const dt=Date.now()-touchStartTime;\n        const t=e.changedTouches[0];\n        const dx=Math.abs(t.clientX-touchStartX);\n        const dy=Math.abs(t.clientY-touchStartY);\n        // Only treat as tap if short, small movement\n        if(dt<300 && dx<12 && dy<12){\n          const r=renderer.domElement.getBoundingClientRect();\n          const nx=((t.clientX-r.left)/r.width)*2-1;\n          const ny=-((t.clientY-r.top)/r.height)*2+1;\n          raycaster.setFromCamera({x:nx,y:ny}, camera);\n          const hit=raycaster.intersectObjects(phMeshes,true)[0]?.object || null;\n\n          const idx = pathCount ? ((Math.floor(u * pathCount) % pathCount) + 1) : 1;\n          const slot = occupancy?.[idx];\n\n          let targetUrl=null;\n          if(hit){\n            const proj = hit.userData?.project || null;\n            if(proj && PROJECT_URLS[proj]) targetUrl=PROJECT_URLS[proj];\n            else if(hit.userData?.linkURL) targetUrl=hit.userData.linkURL;\n          }\n          if(!targetUrl && slot?.type==='project'){\n            const k=slot.block.key; if(PROJECT_URLS[k]) targetUrl=PROJECT_URLS[k];\n          }\n          if(targetUrl) navigateWithFade(targetUrl);\n        }\n      },{passive:true});\n\n      /* InfoCard tap */\n      ic.addEventListener('click', ()=>{\n        const idx = pathCount ? ((Math.floor(u * pathCount) % pathCount) + 1) : 1;\n        const slot = occupancy?.[idx];\n        if(slot?.type==='project'){\n          const k=slot.block.key; const url=PROJECT_URLS[k]; if(url) navigateWithFade(url);\n        }\n      });\n\n      clearTimeout(loaderSafety);\n      hideLoader();\n    }, undefined, err=>{ console.error(err); clearTimeout(loaderSafety); hideLoader(); });\n  }catch(e){\n    console.error(e);\n    clearTimeout(loaderSafety);\n    hideLoader();\n  }\n})();\n\n/* ===== Movement: touch-scroll optimized, no look-around =====\n   Same max speed as desktop, but driven by touch swipe.\n   No cursor/pointer-based look-around on mobile.\n*/\nconst BASE_SCROLL_GAIN = 0.00006/2;\nconst BASE_VEL_MAX     = 0.0035/3;\nconst SCROLL_GAIN      = BASE_SCROLL_GAIN * 0.875;\nconst VEL_MAX          = BASE_VEL_MAX * 0.875 * 0.65 * 0.80 * 0.80 * 0.80 * 0.80;\n\n// Touch-specific tuning\nconst TOUCH_GAIN       = SCROLL_GAIN * 2.2;  // touch px → velocity, tuned for finger feel\nconst TOUCH_MOMENTUM   = 0.94;               // momentum decay after finger lift\nconst TOUCH_STOP_EPS   = 1e-7;\n\nconst VEL_EASE=0.12, VEL_STOP_EPS=1e-6, NO_WHEEL_TIMEOUT=140, AHEAD_T=0.0045;\n\nlet u=0, vel=0, velTarget=0, lastWheelAt=0;\n\n// Touch state\nlet touchY=0, touchActive=false, lastTouchY=0, touchVelRaw=0;\n\nstage.addEventListener('wheel', e=>{\n  e.preventDefault(); if(e.ctrlKey||e.metaKey) return;\n  const dy=e.deltaY||0; if(Math.abs(dy)<0.01) return;\n  lastWheelAt=performance.now();\n  velTarget = clamp(velTarget + dy*SCROLL_GAIN, -VEL_MAX, VEL_MAX);\n},{passive:false});\n\n/* Touch scroll */\nstage.addEventListener('touchstart', e=>{\n  const t=e.touches[0];\n  touchY=t.clientY; lastTouchY=t.clientY; touchActive=true; touchVelRaw=0;\n},{passive:true});\n\nstage.addEventListener('touchmove', e=>{\n  if(!touchActive) return;\n  const t=e.touches[0];\n  const dy=lastTouchY - t.clientY; // swipe up = forward\n  lastTouchY=t.clientY;\n  touchVelRaw=dy;\n  velTarget=clamp(velTarget + dy*TOUCH_GAIN, -VEL_MAX, VEL_MAX);\n  lastWheelAt=performance.now();\n},{passive:true});\n\nstage.addEventListener('touchend', ()=>{\n  touchActive=false;\n  // momentum is handled by letting velTarget decay naturally\n},{passive:true});\nstage.addEventListener('touchcancel', ()=>{ touchActive=false; },{passive:true});\n\nfunction animate(){\n  requestAnimationFrame(animate);\n  const now=performance.now();\n\n  if(now-lastWheelAt>NO_WHEEL_TIMEOUT){\n    velTarget=0;\n  } else {\n    velTarget=clamp(velTarget, -VEL_MAX, VEL_MAX);\n  }\n\n  vel += (velTarget-vel)*VEL_EASE;\n  if(Math.abs(vel)<VEL_STOP_EPS && velTarget===0) vel=0;\n\n  u=wrap01(u+vel);\n\n  if(pathCurve){\n    const p  = pathCurve.getPointAt(u);\n    const pa = pathCurve.getPointAt(wrap01(u+AHEAD_T));\n    const forward = pa.clone().sub(p).normalize().negate();\n\n    camera.position.copy(p);\n    camera.lookAt(p.clone().add(forward));\n\n    const idx = pathCount ? ((Math.floor(u * pathCount) % pathCount) + 1) : 1;\n    updateCardsByOccupancy(idx);\n  }\n\n  renderer.render(scene, camera);\n}\nanimate();\n\n/* Resize */\naddEventListener('resize', ()=>{\n  renderer.setSize(stage.clientWidth, stage.clientHeight, false);\n  camera.aspect = stage.clientWidth / stage.clientHeight;\n  camera.fov = 90;\n  camera.updateProjectionMatrix();\n});\n</script>\n</body>\n</html>","embedURL":""}},"354:1888":{"key":"a091171a5938396568b5d8a5e0486f948cfd337b","name":"TEXT BOLD V3","styleType":"TEXT","remote":false,"description":"","id":"354:1888","assetId":"StyleId:354:1888","type":"STYLE","style":{"fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontSize":19.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":23.75,"lineHeightPercent":97.9623794555664,"lineHeightPercentFontSize":125.0,"lineHeightUnit":"FONT_SIZE_%"}},"482:4824":{"type":"COMPONENT","id":"482:4824","name":"Property 1=Bilbao01","absoluteBoundingBox":{"x":-26682.0,"y":-211.0,"width":395.0,"height":420.0},"isolatedAbsoluteRenderBounds":{"x":-26682.0,"y":-211.0,"width":395.0,"height":420.0},"relativeTransform":[[1.0,0.0,110.0],[0.0,1.0,527.0]],"size":{"x":395.0,"y":420.0},"fills":[],"constraints":{"vertical":"TOP","horizontal":"RIGHT"},"constraintValues":{"right":{"pixelOffset":-659.0,"sizeFraction":1.0},"top":{"pixelOffset":527.0,"sizeFraction":0.0}},"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":482,"localID":4880},"event":{"interactionType":"MOUSE_LEAVE"},"actions":[{"transitionNodeID":{"sessionID":278,"localID":13822},"transitionType":"INSTANT_TRANSITION","transitionDuration":0.300000011920929,"easingType":"OUT_CUBIC","easingFunction":[0.0,0.0,0.579999983310699,1.0],"connectionType":"INTERNAL_NODE","navigationType":"SWAP_STATE","transitionResetVideoPosition":false,"stateGroupContext":"278:13821"}],"isDeleted":false,"stateManagementVersion":1}],"paddingTop":20.0,"paddingRight":50.0,"clipsContent":true,"layoutMode":"HORIZONTAL","itemSpacing":7.0,"primaryAxisAlignItems":"MAX","children":["482:4635"],"componentSetId":"278:13821"},"493:33051":{"type":"COMPONENT","id":"493:33051","name":"Property 1=Proj Mobile","absoluteBoundingBox":{"x":-25803.0,"y":-562.0,"width":104.0,"height":56.0},"isolatedAbsoluteRenderBounds":{"x":-25803.0,"y":-562.0,"width":104.0,"height":56.0},"relativeTransform":[[1.0,0.0,989.0],[0.0,1.0,176.0]],"size":{"x":104.0,"y":56.0},"blendMode":"DIFFERENCE","fills":[{"opacity":0.0,"blendMode":"DIFFERENCE","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true}],"constraintValues":{"left":{"pixelOffset":989.0,"sizeFraction":0.0},"top":{"pixelOffset":176.0,"sizeFraction":0.0}},"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":493,"localID":35848},"event":{"interactionType":"ON_CLICK"},"actions":[{"transitionNodeID":{"sessionID":493,"localID":35769},"connectionType":"INTERNAL_NODE","navigationType":"NAVIGATE","connectionURL":"/menumobile"}],"isDeleted":false,"stateManagementVersion":1}],"behaviors":{"appear":{"otherLayer":{"sessionID":-1,"localID":-1},"trigger":"THIS_LAYER_IN_VIEW","direction":"UP","enterTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"enterState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":0.0},"exitTransition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.600000023841858,"delay":0.0},"exitState":{"transform":{"m00":1.0,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.0,"m12":0.0},"opacity":1.0},"playsOnce":false,"behaviorType":"appear"}},"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["493:33052"],"componentSetId":"278:13821"},"805:3563":{"type":"WIDGET","id":"805:3563","name":"Embed 6","absoluteBoundingBox":{"x":-23335.0,"y":3674.0,"width":375.0,"height":1080.0},"isolatedAbsoluteRenderBounds":{"x":-23335.0,"y":3674.0,"width":375.0,"height":1080.0},"relativeTransform":[[1.0,0.0,1503.0],[0.0,1.0,0.0]],"size":{"x":375.0,"y":1080.0},"visible":false,"layoutAlign":"STRETCH","layoutGrow":1.0,"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"widgetType":"GENERIC","syncedState":{"embedAllowFullscreen":"false","embedCodeType":"html","embedIframeHtml":"<!DOCTYPE html>\n<html lang=\"de\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <title>Portfolio 3D — Mobile</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\" />\n  <style>\n    html,body{margin:0;height:100%;overflow:hidden;background:#fff;overscroll-behavior:none;-webkit-tap-highlight-color:transparent}\n    #stage{width:100%;height:100vh;position:relative;touch-action:none}\n    canvas{display:block;width:100%;height:100%}\n    /* Weißes Fade-Overlay */\n    #fade{position:fixed;inset:0;background:#fff;opacity:0;pointer-events:none;z-index:9999;transition:opacity .6s ease}\n\n    /* Font: SpaceGrotesk[wght].woff2 */\n    @font-face{\n      font-family:\"Space Grotesk\";\n      src:\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@2eb12abf61bddfeeebcd4edad67a8dd1acb1f9ac/SpaceGrotesk%5Bwght%5D.woff2\") format(\"woff2-variations\"),\n        url(\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@2eb12abf61bddfeeebcd4edad67a8dd1acb1f9ac/SpaceGrotesk%5Bwght%5D.woff2\") format(\"woff2\");\n      font-weight:300 800; font-style:normal; font-display:swap;\n    }\n    .sg{font-family:\"Space Grotesk\",system-ui,-apple-system,\"Segoe UI\",Roboto,Arial,sans-serif}\n\n    /* Projekt-Info-Card (mobile): full width, unten, padding 17 */\n    #infoCard{\n      position:fixed; left:17px; right:17px; bottom:17px; z-index:9000;\n      border-radius:10px; padding:14px 17px 17px;\n      background:#eee; color:#000; display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.08)\n    }\n    .ic-title{ font-weight:700; font-size:28px; line-height:1.2; margin:0 0 6px 0; white-space:nowrap }\n    .ic-row{ display:flex; align-items:center; gap:7px; margin:0 0 7px 0; font-weight:450; font-size:16px; line-height:1.2 }\n    .ic-row:last-child{ margin-bottom:0 }\n    .ic-dot{ width:8px; height:8px; border-radius:50%; background:currentColor; opacity:.95 }\n\n    /* Help-Card (nur 1 Zeile), gleiche Position wie Info-Card */\n    #helpCard{\n      position:fixed; left:17px; right:17px; bottom:17px; z-index:9000;\n      border-radius:10px; padding:14px 17px 17px;\n      background:#02f; color:#fff; display:none;\n      box-shadow:0 6px 24px rgba(0,0,0,.12)\n    }\n    .hc-line{ font-weight:700; font-size:19px; line-height:1.2; margin:0; white-space:nowrap; text-align:left }\n\n    /* Größere Touch-Ziele */\n    #infoCard, #helpCard{ touch-action:manipulation }\n  </style>\n\n  <!-- Preload Font -->\n  <link rel=\"preload\" as=\"font\" type=\"font/woff2\" crossorigin\n        href=\"https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@2eb12abf61bddfeeebcd4edad67a8dd1acb1f9ac/SpaceGrotesk%5Bwght%5D.woff2\">\n</head>\n<body>\n<div id=\"stage\"></div>\n<div id=\"fade\"></div>\n\n<!-- Help-Card (nur 1 Zeile) -->\n<div id=\"helpCard\" class=\"sg\" aria-live=\"polite\">\n  <p class=\"hc-line\">scroll to move</p>\n</div>\n\n<!-- Projekt-Info-Card (tapbar) -->\n<div id=\"infoCard\" class=\"sg\" aria-live=\"polite\" role=\"button\">\n  <div class=\"ic-title\" id=\"icTitle\"></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL1\"></span></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL2\"></span></div>\n  <div class=\"ic-row\"><span class=\"ic-dot\"></span><span id=\"icL3\"></span></div>\n</div>\n\n<!-- Import-Map Polyfill -->\n<script async src=\"https://cdn.jsdelivr.net/npm/es-module-shims@1/dist/es-module-shims.min.js\"></script>\n<script type=\"importmap\">\n{ \"imports\": {\n  \"three\": \"https://cdn.jsdelivr.net/npm/three@0.158/build/three.module.js\",\n  \"three/addons/\": \"https://cdn.jsdelivr.net/npm/three@0.158/examples/jsm/\"\n}}\n</script>\n\n<script type=\"module\">\nimport * as THREE from 'three';\nimport { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\n\nconst IS_TOUCH = ('ontouchstart' in window) || (navigator.maxTouchPoints>0);\n\n/* ======= KONFIG ======= */\nconst ASSET_COMMIT = '2eb12abf61bddfeeebcd4edad67a8dd1acb1f9ac';\nconst CDN_BASE     = `https://cdn.jsdelivr.net/gh/Loic-23/portfolio-assets@${ASSET_COMMIT}`;\nconst GLB_URL      = `${CDN_BASE}/xprt260105.glb`;\nconst PROJECTS     = ['javelin','bildsprache','meetingpoint','fototage','dampfzentrale','bilbao','flomi','nlch'];\nconst START_PATH_INDEX = 20;\n\n/* Zielseiten */\nconst PROJECT_URLS = {\n  bilbao:        'https://loicberger.figma.site/bilbao',\n  meetingpoint:  'https://loicberger.figma.site/meetingpoint',\n  javelin:       'https://loicberger.figma.site/javelin',\n  bildsprache:   'https://loicberger.figma.site/interdependence',\n  dampfzentrale: 'https://loicberger.figma.site/dampfzentrale',\n  fototage:      'https://loicberger.figma.site/focus',\n  nlch:          'https://loicberger.figma.site/speak-type',\n  flomi:         'https://loicberger.figma.site/dispo-flomi'\n};\n\n/* ======= Info-Karten ======= */\nconst INFO_BLOCKS = [\n  { key:'dampfzentrale', from:325, to:375, bg:'#E8FA70', txt:'#000',\n    title:'Dampfzentrale', l1:'Poster Series', l2:'4 Posters', l3:'2024' },\n  { key:'bilbao', from:538, to:600, bg:'#FF3D3D', txt:'#000',\n    title:'Bilbao', l1:'Corporate Identity', l2:'Logo, Posters, Mural, Tote Bag', l3:'2024' },\n  { key:'javelin', from:477, to:524, bg:'#692FFF', txt:'#000',\n    title:'Javelin', l1:'Typeface', l2:'Regular 30 ...', l3:'2024' },\n  { key:'focus', from:263, to:300, bg:'#94006C', txt:'#FFFFFF',\n    title:'Focus', l1:'Video Installation', l2:'Mockup Installation', l3:'2025' },\n  { key:'meetingpoint', from:650, to:746, bg:'#C1FF6A', txt:'#000',\n    title:'Meetingpoint', l1:'VJ and Music Performance', l2:'Video 20:22 min', l3:'2024' },\n  { key:'bildsprache', from:395, to:448, bg:'#180152', txt:'#FFFFFF',\n    title:'Interdependence', l1:'Music, Painting', l2:'Painting 200 × 100 mm + Music', l3:'2025' },\n  { key:'flomi', from:117, to:160, bg:'#FF8E25', txt:'#000',\n    title:'Dispo Flomi', l1:'Poster, Poster-System', l2:'', l3:'2025' },\n  { key:'nlch', from:173, to:240, bg:'#00A6A9', txt:'#000',\n    title:'Speak Type?', l1:'Editorial, Concept', l2:'Brochure, 220 × 280 mm, 31 pages', l3:'2025' }\n];\n/* Help-Range über 0 hinweg: 793–30 */\nconst HELP_RANGE = { from:793, to:30 };\n\n/* ======= Persistenter State ======= */\nconst saveState = (u)=>{ try{ sessionStorage.setItem('camState@home', JSON.stringify({u,ts:Date.now()})); }catch{} };\nconst loadState = ()=>{ try{ const s=sessionStorage.getItem('camState@home'); return s?JSON.parse(s):null; }catch{return null;} };\n\n/* ======= Utils ======= */\nconst clamp=(n,a,b)=>Math.max(a,Math.min(b,n));\nconst wrap01=t=>((t%1)+1)%1;\nconst WORLD_UP = new THREE.Vector3(0,1,0);\nconst norm = s => (s||'').normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').replace(/\\s+/g,'').toLowerCase();\n\nfunction cleanMeshKey(origName){\n  if (!origName) return null;\n  let name = origName.trim().replace(/^P_/,'').replace(/^p_/,'');\n  const dot = name.indexOf('.'); if (dot>-1) name = name.slice(0,dot);\n  name = name.replace(/(\\d)[a-z]+$/i, '$1');\n  const m = name.match(/^(.+\\d{1,})0{1,}\\d{2,}$/);\n  if (m) name = m[1];\n  const clean = norm(name).replace(/[^a-z0-9_-]/g,'');\n  if (!clean || clean.includes('path') || clean==='scene') return null;\n  return clean;\n}\nfunction detectProjectFromKey(key){\n  for (const p of PROJECTS){ if (key.startsWith(p)) return p; }\n  return null;\n}\nfunction fileKey(filename){\n  const base = filename.split('/').pop() || filename;\n  const i = base.lastIndexOf('.'); const stem = i>-1 ? base.slice(0,i) : base;\n  return norm(stem).replace(/[^a-z0-9_-]/g,'');\n}\n\n/* ======= Loader ======= */\nconst texLoader = new THREE.TextureLoader(); texLoader.setCrossOrigin('anonymous');\nfunction loadTexture(url){\n  return new Promise((resolve,reject)=>{\n    texLoader.load(url, tx=>{\n      tx.flipY = false;\n      tx.colorSpace = THREE.SRGBColorSpace;\n      tx.minFilter  = THREE.LinearMipmapLinearFilter;\n      tx.magFilter  = THREE.LinearFilter;\n      tx.generateMipmaps = true;\n      resolve(tx);\n    }, undefined, err=>reject(err));\n  });\n}\nasync function applyImage(mesh, url){\n  try{\n    const texture = await loadTexture(url);\n    const mat = new THREE.MeshBasicMaterial({ color:0xffffff, map:texture, side:THREE.DoubleSide, transparent:true });\n    mesh.material = mat; mesh.material.needsUpdate = true;\n    mesh.userData.imageMat = mat;\n  }catch{}\n}\n\n/* ——— Video (EXAKT wie in der Desktop-Version) ——— */\nfunction createVideoTexture(url){\n  return new Promise((resolve, reject)=>{\n    const vid = document.createElement('video');\n    vid.src = url;\n    vid.crossOrigin = 'anonymous';\n    vid.loop = true;\n    vid.muted = true;\n    vid.setAttribute('muted','');\n    vid.playsInline = true;\n    vid.setAttribute('webkit-playsinline','');\n    vid.preload = 'auto';\n\n    const start = ()=> vid.play().catch(()=>{});\n    vid.addEventListener('loadeddata', start, {once:true});\n    vid.addEventListener('canplay',     start, {once:true});\n    vid.addEventListener('error', ()=>reject(new Error('video error '+url)), {once:true});\n\n    const vtex = new THREE.VideoTexture(vid);\n    vtex.flipY = false;\n    vtex.colorSpace = THREE.SRGBColorSpace;\n    vtex.minFilter  = THREE.LinearFilter;\n    vtex.magFilter  = THREE.LinearFilter;\n    vtex.generateMipmaps = false;\n    vtex.needsUpdate = true;\n\n    vid.play().catch(()=>{});\n    resolve({ vtex, vid });\n  });\n}\n\n/* ======= Manifest ======= */\nasync function fetchManifest(project){\n  const url = `${CDN_BASE}/assets/img/${project}/manifest.json`;\n  const r = await fetch(url, { cache:'no-cache' });\n  if (!r.ok) throw new Error(`manifest missing: ${project}`);\n  const m = await r.json();\n  const base = (m.base || `assets/img/${project}`).replace(/\\/+$/,'');\n  const files = Array.isArray(m.files) ? m.files : [];\n\n  const best = new Map();\n  for (const name of files){\n    const ext  = (name.split('.').pop()||'').toLowerCase();\n    const key  = fileKey(name);\n    const full = `${CDN_BASE}/${base}/${name}`;\n    const prev = best.get(key) || {};\n    if (/(webp|jpe?g|png|gif)$/.test(ext)){\n      if (!prev.image) prev.image = full;\n    } else if (/mp4|webm/.test(ext)){\n      const prio = ext==='mp4' ? 3 : 2;\n      if (!prev.video || prio > (prev.vprio||0)){ prev.video = full; prev.vprio = prio; }\n    }\n    best.set(key, prev);\n  }\n  for (const v of best.values()) delete v.vprio;\n  return best;\n}\n\n/* ======= Three Setup ======= */\nconst stage = document.getElementById('stage');\nconst overlay = document.getElementById('fade');\n\nconst renderer = new THREE.WebGLRenderer({ antialias:true, powerPreference:'high-performance' });\nrenderer.outputColorSpace = THREE.SRGBColorSpace;\nrenderer.setPixelRatio(Math.min(devicePixelRatio||1,2));\nrenderer.setSize(stage.clientWidth, stage.clientHeight, false);\nstage.appendChild(renderer.domElement);\n\nconst scene = new THREE.Scene(); scene.background = new THREE.Color(0xffffff);\nconst FOV_DEG = 120;                                    /* großer Kamerawinkel */\nconst camera = new THREE.PerspectiveCamera(FOV_DEG, stage.clientWidth/stage.clientHeight, 0.1, 5000);\nscene.add(new THREE.AmbientLight(0xffffff,1));\n\n/* ======= Pfad & Media ======= */\nlet pathCurve=null, pathCount=0;\nconst loader = new GLTFLoader();\n\nconst phMeshes = [];\nconst raycaster = new THREE.Raycaster(); raycaster.near=0.1; raycaster.far=50;\n\n/* Karten-Refs */\nconst ic = document.getElementById('infoCard');\nconst icTitle = document.getElementById('icTitle');\nconst icL1 = document.getElementById('icL1');\nconst icL2 = document.getElementById('icL2');\nconst icL3 = document.getElementById('icL3');\nconst helpCard = document.getElementById('helpCard');\n\n/* Belegungs-Map */\nlet occupancy = null;\nfunction idxNorm(i){ return (((i-1) % pathCount) + pathCount) % pathCount + 1; }\nfunction markRange(from, to, value){\n  if (!pathCount) return;\n  const a = idxNorm(from), b = idxNorm(to);\n  if (a<=b){ for (let i=a;i<=b;i++) occupancy[i]=value; }\n  else { for (let i=a;i<=pathCount;i++) occupancy[i]=value; for (let i=1;i<=b;i++) occupancy[i]=value; }\n}\nfunction buildOccupancy(){\n  occupancy = new Array(pathCount+1).fill(null).map(()=>({type:'none'}));\n  markRange(HELP_RANGE.from, HELP_RANGE.to, {type:'help'});\n  for (const block of INFO_BLOCKS){ markRange(block.from, block.to, {type:'project', block}); }\n}\n\n/* Navigation mit Fade (weiß) */\nfunction navigateWithFade(url){\n  if (!url) return;\n  saveState(u);\n  overlay.style.pointerEvents = 'auto';\n  overlay.style.opacity = '1';\n  setTimeout(()=>{ window.location.href = url; }, 600);\n}\n\n/* Videos */\nasync function ensureVideoForMesh(mesh){\n  if (!mesh.userData.videoURL) return;\n  if (mesh.userData._videoEl && mesh.userData._videoTex){\n    try{ mesh.userData._videoEl.play().catch(()=>{}); }catch{}\n    return;\n  }\n  try{\n    const { vtex, vid } = await createVideoTexture(mesh.userData.videoURL);\n    mesh.userData._videoEl  = vid;\n    mesh.userData._videoTex = vtex;\n    mesh.material = new THREE.MeshBasicMaterial({ color:0xffffff, map:vtex, side:THREE.DoubleSide, transparent:true });\n    mesh.material.needsUpdate = true;\n  }catch{}\n}\nasync function startAllVideos(){ await Promise.allSettled(phMeshes.map(m => ensureVideoForMesh(m))); }\nfunction resumeAllVideos(){\n  for (const m of phMeshes){\n    const v = m.userData._videoEl;\n    if (v && v.paused) { try{ v.play().catch(()=>{}); }catch{} }\n  }\n}\n\n/* Anzeige-Logik + InfoCard-Linking */\nlet currentProjectKey = null;\nfunction updateCardsByOccupancy(idx){\n  if (!occupancy) return;\n  const slot = occupancy[idx] || {type:'none'};\n\n  if (slot.type === 'project'){\n    const b = slot.block;\n    ic.style.display = 'block';\n    helpCard.style.display = 'none';\n    ic.style.background = b.bg || '#eee';\n    ic.style.color = b.txt || '#000';\n    icTitle.textContent = b.title || '';\n    icL1.textContent = b.l1 || '';\n    icL2.textContent = b.l2 || '';\n    icL3.textContent = b.l3 || '';\n    currentProjectKey = b.key || null;\n    return;\n  }\n  if (slot.type === 'help'){\n    ic.style.display = 'none';\n    helpCard.style.display = 'block';\n    currentProjectKey = null;\n    return;\n  }\n  ic.style.display = 'none';\n  helpCard.style.display = 'none';\n  currentProjectKey = null;\n}\n\n/* ======= Init ======= */\n(async function init(){\n  // Manifeste\n  const results = await Promise.allSettled(PROJECTS.map(fetchManifest));\n  const imagesByKey = new Map();  const videosByKey = new Map();\n  for (const res of results){\n    if (res.status!=='fulfilled') continue;\n    for (const [k,v] of res.value){\n      if (v.image && !imagesByKey.has(k)) imagesByKey.set(k, v.image);\n      if (v.video && !videosByKey.has(k)) videosByKey.set(k, v.video);\n    }\n  }\n\n  loader.load(GLB_URL, async (gltf)=>{\n    const root = gltf.scene; scene.add(root);\n    root.updateWorldMatrix(true,true);\n\n    // PATH\n    const empties=[];\n    root.traverse(o=>{ const m=/^PATH_(\\d+)/i.exec(o.name||''); if(m) empties.push({i:+m[1],obj:o}); });\n    if (empties.length){\n      empties.sort((a,b)=>a.i-b.i);\n      const pts = empties.map(n=> n.obj.getWorldPosition(new THREE.Vector3()));\n      const closedPts = (pts[0].distanceTo(pts[pts.length-1])<1e-5)?pts.slice(0,-1):pts;\n      pathCurve = new THREE.CatmullRomCurve3(closedPts, true, 'catmullrom', 0.15);\n      pathCount = pts.length;\n    } else {\n      const pts = [\n        new THREE.Vector3(-120,-40,160),\n        new THREE.Vector3(-60,20,80),\n        new THREE.Vector3(0,0,0),\n        new THREE.Vector3(40,-20,-80),\n        new THREE.Vector3(90,30,-160)\n      ];\n      pathCurve = new THREE.CatmullRomCurve3(pts, true, 'catmullrom', 0.15);\n      pathCount = pts.length;\n    }\n\n    buildOccupancy();\n\n    // Meshes + Medien\n    const jobs=[];\n    root.traverse(o=>{\n      if (!(o.isMesh && o.geometry)) return;\n\n      // Platzhalter\n      o.material = new THREE.MeshBasicMaterial({ color:0xdddddd, side:THREE.DoubleSide, transparent:true });\n\n      // Basis-Transform (Hover auf Mobile nicht benutzt)\n      o.userData.basePos   = o.position.clone();\n      o.userData.baseScale = o.scale.clone();\n\n      const key = cleanMeshKey(o.name);\n      if (key){\n        const project = detectProjectFromKey(key);\n        o.userData.project = project || null;\n        // Mobile: Mesh-Klicks deaktiviert\n        o.userData.linkURL = null;\n\n        const img = imagesByKey.get(key);\n        if (img) jobs.push(applyImage(o, img));\n\n        const vid = videosByKey.get(key);\n        if (vid) o.userData.videoURL = vid;\n      }\n      phMeshes.push(o);\n    });\n    await Promise.allSettled(jobs);\n\n    // Kamera-Start (Restore bevorzugt, sonst PATH_20), Blick 180°\n    const saved = loadState();\n    let u0=0;\n    if (saved && typeof saved.u==='number'){\n      u0 = wrap01(saved.u);\n    } else if (pathCount>0){\n      const clamped = Math.max(1, Math.min(START_PATH_INDEX, pathCount));\n      u0 = (clamped - 1) / pathCount;\n    }\n    u = u0;\n\n    const p0 = pathCurve.getPointAt(u);\n    const p1 = pathCurve.getPointAt(wrap01(u + 0.0045));\n    const forward = p1.clone().sub(p0).normalize().negate();\n    camera.position.copy(p0);\n    camera.lookAt(p0.clone().add(forward));\n\n    // Videos\n    await startAllVideos();\n  }, undefined, err=>{ console.error(err); });\n})();\n\n/* ======= Bewegung (ohne Look), Touch/Wheel ======= */\n/* vorher: BASE/4 * 2 → jetzt deutlich schneller: *4 (≈4× schneller als zuvor) */\nconst BASE_SCROLL_GAIN = 0.00006 / 4;\nconst BASE_VEL_MAX     = 0.0035  / 4;\nconst SPEED_FACTOR     = 4.0;\nconst SCROLL_GAIN = BASE_SCROLL_GAIN * SPEED_FACTOR;\nconst VEL_MAX     = BASE_VEL_MAX     * SPEED_FACTOR;\n\nconst VEL_EASE=0.12, VEL_STOP_EPS=1e-6, NO_WHEEL_TIMEOUT=140, AHEAD_T=0.0045;\n\nlet u=0, vel=0, velTarget=0, lastWheelAt=0;\n\n/* Wheel: Richtung invertiert (wie gewünscht) */\nstage.addEventListener('wheel', e=>{\n  e.preventDefault(); if(e.ctrlKey||e.metaKey) return;\n  const dy=e.deltaY||0; if(Math.abs(dy)<0.01) return;\n  lastWheelAt=performance.now();\n  velTarget=clamp(velTarget - dy*SCROLL_GAIN, -VEL_MAX, VEL_MAX);\n},{passive:false});\n\n/* Touch-Scroll (Mobile) – Richtung invertiert */\nlet tY=0, tActive=false;\nstage.addEventListener('touchstart', e=>{\n  if (!IS_TOUCH) return;\n  if (!e.touches || e.touches.length===0) return;\n  tActive=true; tY=e.touches[0].clientY;\n},{passive:true});\nstage.addEventListener('touchmove', e=>{\n  if (!IS_TOUCH || !tActive) return;\n  const y=e.touches[0].clientY;\n  const dy = (y - tY);      // invertiert: wischen nach oben = rückwärts, unten = vorwärts\n  tY = y;\n  velTarget = clamp(velTarget + dy*SCROLL_GAIN*1.0, -VEL_MAX, VEL_MAX);\n  e.preventDefault();\n},{passive:false});\nstage.addEventListener('touchend', ()=>{ tActive=false; }, {passive:true});\n\n/* InfoCard tap → Link (Mesh-Klicks sind aus) */\ndocument.getElementById('infoCard').addEventListener('click', ()=>{\n  if (!currentProjectKey) return;\n  const url = PROJECT_URLS[currentProjectKey];\n  if (url) navigateWithFade(url);\n});\n\nfunction animate(){\n  requestAnimationFrame(animate);\n\n  const now = performance.now();\n  if (now - lastWheelAt > NO_WHEEL_TIMEOUT && !tActive) velTarget = 0;\n  vel += (velTarget - vel) * VEL_EASE;\n  if (Math.abs(vel) < VEL_STOP_EPS && velTarget === 0) vel = 0;\n  u = wrap01(u + vel);\n\n  if (pathCurve){\n    const p  = pathCurve.getPointAt(u);\n    const pa = pathCurve.getPointAt(wrap01(u + AHEAD_T));\n    const forward = pa.clone().sub(p).normalize().negate();\n\n    // Mobile: Blick fix nach vorne\n    camera.position.copy(p);\n    camera.lookAt(p.clone().add(forward));\n\n    // Index 1..pathCount\n    const idx = pathCount ? ((Math.floor(u * pathCount) % pathCount) + 1) : 1;\n    updateCardsByOccupancy(idx);\n  }\n\n  renderer.render(scene, camera);\n}\nanimate();\n\n/* Sichtbarkeit / BFCache */\ndocument.addEventListener('visibilitychange', ()=>{\n  if (document.visibilityState === 'hidden'){ saveState(u); }\n  else { resumeAllVideos(); }\n});\nwindow.addEventListener('pageshow', ()=>{ resumeAllVideos(); });\n\n/* Resize */\naddEventListener('resize', ()=>{\n  renderer.setSize(stage.clientWidth, stage.clientHeight, false);\n  camera.aspect = stage.clientWidth / stage.clientHeight;\n  camera.fov = FOV_DEG;  // 120\n  camera.updateProjectionMatrix();\n});\n</script>\n</body>\n</html>","embedURL":""}},"0:3":{"type":"WEBPAGE","id":"0:3","name":"/","absoluteBoundingBox":{"x":-24902.0,"y":3574.0,"width":1267.0,"height":1244.0},"isolatedAbsoluteRenderBounds":{"x":-24902.0,"y":3574.0,"width":1267.0,"height":1244.0},"relativeTransform":[[1.0,0.0,-24902.0],[0.0,1.0,3574.0]],"size":{"x":1267.0,"y":1244.0},"fills":[{"opacity":0.0470588244497776,"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true}],"strokeAlign":"INSIDE","strokes":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"children":["382:6647","493:33013"]},"489:26959":{"key":"9bdcd6469049ea2722ef8427c1c42a5a2dcbf4d3","name":"TEXT BOLD MOBILE","styleType":"TEXT","remote":false,"description":"","id":"489:26959","assetId":"StyleId:489:26959","type":"STYLE","style":{"fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontSize":17.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1,"SS01":1,"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":20.4000015258789,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%"}},"234:8014":{"type":"TEXT","id":"234:8014","name":"TEXT BOLD","absoluteBoundingBox":{"x":0.0,"y":0.0,"width":27.0,"height":28.0},"isolatedAbsoluteRenderBounds":{"x":0.560000002384186,"y":8.27999877929688,"width":24.6884384155273,"height":17.7200012207031},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":27.0,"y":28.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"strokeWeight":0.0,"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Ag","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","style":{"fontFamily":"Sk-Modernist","fontPostScriptName":"Sk-Modernist-Bold","fontStyle":"Bold","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":20.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":-0.4,"letterSpacingValue":-2.0,"letterSpacingUnit":"PERCENT","lineHeightPx":28.0,"lineHeightPercent":116.666664123535,"lineHeightPercentFontSize":140.0,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"728:9149":{"mainComponentId":"485:13993","type":"INSTANCE","id":"728:9149","name":"Frame 1495","absoluteBoundingBox":{"x":-24838.0,"y":3674.0,"width":243.607360839844,"height":64.0},"isolatedAbsoluteRenderBounds":{"x":-24838.0,"y":3674.0,"width":243.607421875,"height":64.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":243.607360839844,"y":64.0},"fills":[],"constraintValues":{"left":{"pixelOffset":0.0,"sizeFraction":0.0},"top":{"pixelOffset":0.0,"sizeFraction":0.0}},"strokeAlign":"INSIDE","layoutPositioning":"ABSOLUTE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","counterAxisAlignItems":"MAX","children":["I728:9149;485:13994"],"componentSetId":"278:13821","componentProperties":{"Property 1":{"value":"Variant4","type":"VARIANT","boundVariables":{}}},"overrides":[{"key":[],"value":{"blendMode":null,"layoutPositioning":"ABSOLUTE","name":"Frame 1495","size":{"x":243.607360839844,"y":64.0}}},{"key":["Frame 1540","Frame 14470"],"value":{"interactions":[{"id":{"sessionID":728,"localID":9519},"event":{"interactionType":"ON_CLICK"},"actions":[{"connectionType":"URL","connectionURL":"https://www.loic-berger.ch/?project=home","openUrlInNewTab":false}],"isDeleted":false,"stateManagementVersion":1}]}},{"key":["Frame 1540","Frame 14480","Loïc Berger0"],"value":{"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}]}}]},"234:8015":{"type":"TEXT","id":"234:8015","name":"HEADING1","absoluteBoundingBox":{"x":0.0,"y":0.0,"width":50.0,"height":49.0},"isolatedAbsoluteRenderBounds":{"x":1.06400001049042,"y":13.9319982528687,"width":46.9460296630859,"height":33.6679992675781},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":50.0,"y":49.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"strokeWeight":0.0,"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Ag","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","style":{"fontFamily":"Sk-Modernist","fontPostScriptName":"Sk-Modernist-Bold","fontStyle":"Bold","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":38.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":-0.721999990940094,"letterSpacingValue":-1.89999997615814,"letterSpacingUnit":"PERCENT","lineHeightPx":49.3999977111816,"lineHeightPercent":108.33332824707,"lineHeightPercentFontSize":130.0,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"25:408":{"type":"TEXT","id":"25:408","name":"Text","absoluteBoundingBox":{"x":0.0,"y":0.0,"width":14.0,"height":12.0},"isolatedAbsoluteRenderBounds":{"x":0.248579546809196,"y":2.72727251052856,"width":12.1271305084229,"height":9.43181800842285},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":14.0,"y":12.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"strokeWeight":0.0,"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Ag","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","style":{"fontFamily":"Inter","fontPostScriptName":"Inter-Medium","fontStyle":"Medium","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":10.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":12.1022720336914,"lineHeightPercent":100.0,"lineHeightUnit":"INTRINSIC_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED","lineHeightPercentFontSize":100},"styleOverrideTable":{}},"493:33052":{"type":"FRAME","id":"493:33052","name":"Frame 119","absoluteBoundingBox":{"x":-25803.0,"y":-562.0,"width":104.0,"height":56.0},"isolatedAbsoluteRenderBounds":{"x":-25803.0,"y":-562.0,"width":104.0,"height":56.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":104.0,"y":56.0},"fills":[],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"paddingTop":18.0,"paddingRight":18.0,"paddingBottom":18.0,"paddingLeft":18.0,"layoutMode":"HORIZONTAL","counterAxisAlignItems":"CENTER","children":["493:33053"]},"110:444":{"key":"8ac84f4a921e2a56abe10ef528db335300bae0bf","name":"Text Bold","styleType":"TEXT","remote":true,"description":"","id":"110:444","assetId":"StyleId:8ac84f4a921e2a56abe10ef528db335300bae0bf/228:199","type":"STYLE","style":{"fontFamily":"Sk-Modernist","fontPostScriptName":"Sk-Modernist-Bold","fontStyle":"Bold","textAutoResize":"WIDTH_AND_HEIGHT","fontSize":20.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":-0.379999995231628,"letterSpacingValue":-1.89999997615814,"letterSpacingUnit":"PERCENT","lineHeightPx":28.0,"lineHeightPercent":116.666664123535,"lineHeightPercentFontSize":140.0,"lineHeightUnit":"FONT_SIZE_%"}},"482:4646":{"type":"FRAME","id":"482:4646","name":"Frame 178","absoluteBoundingBox":{"x":-26662.0,"y":-11.0,"width":274.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":-11.0,"width":274.0,"height":40.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,160.0]],"size":{"x":274.0,"y":40.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":4.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["482:4647"]},"318:18134":{"type":"TEXT","id":"318:18134","name":"TXT BOLD","absoluteBoundingBox":{"x":0.0,"y":0.0,"width":25.0,"height":32.0},"isolatedAbsoluteRenderBounds":{"x":0.0,"y":0.0,"width":25.0,"height":32.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":25.0,"y":32.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"visible":false,"strokeAlign":"INSIDE","strokes":[],"strokeWeight":0.0,"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Ag","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","style":{"fontFamily":"Neue Haas Grotesk Display Pro","fontPostScriptName":"NHaasGroteskDSPro-75Bd","fontStyle":"75 Bold","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":20.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":32.0,"lineHeightPercent":133.555938720703,"lineHeightPercentFontSize":160.0,"lineHeightUnit":"PIXELS","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"318:18142":{"key":"73882a9d3e9f79faf4bfbe0bb7296df9f79e989c","name":"Light Medium","styleType":"TEXT","remote":true,"description":"","id":"318:18142","assetId":"StyleId:73882a9d3e9f79faf4bfbe0bb7296df9f79e989c/507:21","type":"STYLE","style":{"fontFamily":"Neue Haas Grotesk Display Pro","fontPostScriptName":"NHaasGroteskDSPro-45Lt","fontStyle":"45 Light","textAutoResize":"WIDTH_AND_HEIGHT","fontSize":40.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":45.120002746582,"lineHeightPercent":100.0,"lineHeightUnit":"INTRINSIC_%"}},"278:13821":{"type":"COMPONENT_SET","id":"278:13821","name":"Frame 151","absoluteBoundingBox":{"x":-26792.0,"y":-738.0,"width":1164.0,"height":1193.0},"isolatedAbsoluteRenderBounds":{"x":-26792.0,"y":-738.0,"width":1164.0,"height":1193.0},"relativeTransform":[[1.0,0.0,-26792.0],[0.0,1.0,-738.0]],"size":{"x":1164.0,"y":1193.0},"fills":[],"strokeAlign":"INSIDE","strokeDashes":[10.0,5.0],"strokes":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.541176497936249,"g":0.219607844948769,"b":0.960784316062927,"a":1.0},"visible":true,"opacity":1.0}],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"children":["278:13822","493:33051","487:25373","485:13993","482:4824"],"componentPropertyDefinitions":{"Property 1":{"type":"VARIANT","defaultValue":"Loïc","variantOptions":["Variant2","Bilbao01","Loïc","Variant4","LoicMOBILE","UN Loïc Mobile","Proj Mobile"]}}},"317:17542":{"type":"TEXT","id":"317:17542","name":"textV2","absoluteBoundingBox":{"x":0.0,"y":0.0,"width":19.0,"height":22.0},"isolatedAbsoluteRenderBounds":{"x":0.160000011324883,"y":3.70399951934814,"width":18.1517505645752,"height":14.960000038147},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":19.0,"y":22.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"strokeWeight":0.0,"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Ag","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","style":{"fontFamily":"Berthold Akzidenz Grotesk","fontPostScriptName":"AkzidenzGroteskBE-Regular","fontStyle":"Regular","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":16.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":22.3999996185303,"lineHeightPercent":117.2529296875,"lineHeightPercentFontSize":140.0,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"341:19595":{"key":"6eaa628a0dddc0d6afc43c76554e178cb06e9f3d","name":"TEXTV3","styleType":"TEXT","remote":false,"description":"","id":"341:19595","assetId":"StyleId:341:19595","type":"STYLE","style":{"fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Medium","fontStyle":"Medium","fontVariations":{"Weight":450.0},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontSize":19.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1,"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":23.75,"lineHeightPercent":97.9623794555664,"lineHeightPercentFontSize":125.0,"lineHeightUnit":"FONT_SIZE_%"}},"482:4636":{"type":"FRAME","id":"482:4636","name":"Frame 1446","absoluteBoundingBox":{"x":-26662.0,"y":-171.0,"width":274.0,"height":360.0},"isolatedAbsoluteRenderBounds":{"x":-26662.0,"y":-171.0,"width":274.0,"height":360.0},"relativeTransform":[[1.0,0.0,20.0],[0.0,1.0,20.0]],"size":{"x":274.0,"y":360.0},"fills":[],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":25.0,"children":["482:4637"]},"110:443":{"key":"c6e2e3de879389bc8f0787941972b4e98548a602","name":"Text","styleType":"TEXT","remote":true,"description":"","id":"110:443","assetId":"StyleId:c6e2e3de879389bc8f0787941972b4e98548a602/228:212","type":"STYLE","style":{"fontFamily":"Sk-Modernist","fontPostScriptName":"Sk-Modernist-Regular","fontStyle":"Regular","textAutoResize":"WIDTH_AND_HEIGHT","fontSize":20.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":-0.4,"letterSpacingValue":-2.0,"letterSpacingUnit":"PERCENT","lineHeightPx":28.0,"lineHeightPercent":116.666664123535,"lineHeightPercentFontSize":140.0,"lineHeightUnit":"FONT_SIZE_%"}},"318:18133":{"type":"TEXT","id":"318:18133","name":"Abstand","absoluteBoundingBox":{"x":0.0,"y":0.0,"width":23.0,"height":10.0},"isolatedAbsoluteRenderBounds":{"x":0.0,"y":0.0,"width":23.0,"height":10.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":23.0,"y":10.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"visible":false,"strokeAlign":"INSIDE","strokes":[],"strokeWeight":0.0,"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Ag","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","style":{"fontFamily":"Neue Haas Grotesk Display Pro","fontPostScriptName":"NHaasGroteskDSPro-45Lt","fontStyle":"45 Light","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":20.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":10.0,"lineHeightPercent":44.3262405395508,"lineHeightPercentFontSize":50.0,"lineHeightUnit":"PIXELS","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"489:26958":{"key":"d2cc133f6b09062d35e3802a861fa78815810f00","name":"TEXT MOBILE","styleType":"TEXT","remote":false,"description":"","id":"489:26958","assetId":"StyleId:489:26958","type":"STYLE","style":{"fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Medium","fontStyle":"Medium","fontVariations":{"Weight":450.0},"listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontSize":16.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1,"LIGA":0},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":19.2000007629395,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%"}},"489:26939":{"key":"5b7a9fe565f6cef98033d6e5f0ac0657c4a9bf61","name":"HEADING MOBILE","styleType":"TEXT","remote":false,"description":"","id":"489:26939","assetId":"StyleId:489:26939","type":"STYLE","style":{"fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Medium","fontStyle":"Medium","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontSize":28.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":33.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%"}},"482:4645":{"type":"TEXT","id":"482:4645","name":"Interdependence","absoluteBoundingBox":{"x":-26662.0,"y":-51.0,"width":274.0,"height":40.0},"isolatedAbsoluteRenderBounds":{"x":-26659.822265625,"y":-43.1000022888184,"width":270.357421875,"height":29.7000026702881},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":274.0,"y":40.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.0,"b":0.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"behaviors":{"hover":{"transition":{"easingType":"OUT_CUBIC","easingFunction":[0.215000003576279,0.610000014305115,0.354999989271164,1.0],"transitionDuration":0.100000001490116,"delay":0.0},"state":{"transform":{"m00":1.04999995231628,"m01":0.0,"m02":0.0,"m10":0.0,"m11":1.04999995231628,"m12":0.0},"opacity":1.0},"behaviorType":"hover"}},"characterStyleOverrides":[],"characters":"Interdependence","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"textAutoResize":"WIDTH_AND_HEIGHT","listSpacing":4.0,"style":{"styleIdForText":"StyleId:341:19602","fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":33.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%","paragraphSpacing":0,"paragraphIndent":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED"},"styleOverrideTable":{}},"341:19602":{"key":"efbfcc6053aebb3459380ed6879ca6c8a128cc7a","name":"HEADING V3","styleType":"TEXT","remote":false,"description":"","id":"341:19602","assetId":"StyleId:341:19602","type":"STYLE","style":{"fontFamily":"Space Grotesk","fontPostScriptName":"SpaceGrotesk-Bold","fontStyle":"Bold","listSpacing":4.0,"textAutoResize":"WIDTH_AND_HEIGHT","fontSize":33.0,"textAlignHorizontal":"LEFT","textAlignVertical":"TOP","opentypeFlags":{"SS04":1},"letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":39.6000022888184,"lineHeightPercent":94.0438919067383,"lineHeightPercentFontSize":120.000007629395,"lineHeightUnit":"FONT_SIZE_%"}}},"assetIdToGuid":{"VariableCollectionId:361:5468":"361:5468","VariableID:680:6579":"680:6579","StyleId:489:26959":"489:26959","StyleId:1879abf6283567d3b32a3d18e3c6bfd7e8da663b/507:18":"318:17545","StyleId:341:19595":"341:19595","StyleId:73882a9d3e9f79faf4bfbe0bb7296df9f79e989c/507:21":"318:18142","StyleId:c6e2e3de879389bc8f0787941972b4e98548a602/228:212":"110:443","StyleId:489:26958":"489:26958","StyleId:8ac84f4a921e2a56abe10ef528db335300bae0bf/228:199":"110:444","StyleId:354:1888":"354:1888","StyleId:341:19602":"341:19602","StyleId:489:26939":"489:26939"},"guidToUrl":{"0:3":"/","322:18474":"/dampfzentrale-4","402:2410":"/dispo-flomi","493:35769":"/menumobile","277:13457":"/about-me","275:3447":"/meetingpoint","355:2064":"/meetingpoint-2","275:4261":"/focus","355:2206":"/javelin-2","355:1934":"/focus-2","275:4882":"/bilbao","275:4612":"/dampfzentrale","388:6924":"/interdependence","341:19695":"/bilbao-3","395:1732":"/speak-type","483:6573":"/javelin","652:4927":"/page","696:756":"/page-2"},"fonts":{"Inter:Bold":{"id":"Inter_1","url":"/_woff/v2/Inter_1/Inter_1.woff2","source":1,"italic":false,"weight":700,"variationAxes":[{"tag":"wght","value":700.0,"name":"Weight"},{"tag":"slnt","value":0.0,"name":"Slant"}],"subsets":{"baseUrl":"/_woff/v2/Inter_1/","subsetMappings":[{"unicodeRange":"U+0000-00A0,U+00A2-00A9,U+00AC-00AE,U+00B0-00B7,U+00B9-00BA,U+00BC-00BE,U+00D7,U+00F7,U+2000-206F,U+2074,U+20AC,U+2122,U+2190-21BB,U+2212,U+2215,U+F8FF,U+FEFF,U+FFFD","file":"Inter_1-english.woff2"},{"unicodeRange":"U+00A1,U+00AA-00AB,U+00AF,U+00B8,U+00BB,U+00BF-00D6,U+00D8-00F6,U+00F8-00FF,U+0131,U+0152-0153,U+02B0-02FF","file":"Inter_1-rest-latin.woff2"},{"unicodeRange":"U+0100-0130,U+0132-0151,U+0154-017F","file":"Inter_1-latin-extended-a.woff2"},{"unicodeRange":"U+0180-024F","file":"Inter_1-latin-extended-b.woff2"},{"unicodeRange":"U+1E00-1EFF","file":"Inter_1-latin-extended-additional.woff2"},{"unicodeRange":"U+0250-02AF,U+0300-1DFF,U+1F00-1FFF,U+2070-2073,U+2075-20AB,U+20AD-2121,U+2123-218F,U+21BC-2211,U+2213-2214,U+2216-F8FE,U+F900-FEFE,U+FF00-FFFC,U+FFFE-FFFF","file":"Inter_1-rest.woff2"}]}},"Inter:Medium":{"id":"Inter_1","url":"/_woff/v2/Inter_1/Inter_1.woff2","source":1,"italic":false,"weight":500,"variationAxes":[{"tag":"wght","value":500.0,"name":"Weight"},{"tag":"slnt","value":0.0,"name":"Slant"}],"subsets":{"baseUrl":"/_woff/v2/Inter_1/","subsetMappings":[{"unicodeRange":"U+0000-00A0,U+00A2-00A9,U+00AC-00AE,U+00B0-00B7,U+00B9-00BA,U+00BC-00BE,U+00D7,U+00F7,U+2000-206F,U+2074,U+20AC,U+2122,U+2190-21BB,U+2212,U+2215,U+F8FF,U+FEFF,U+FFFD","file":"Inter_1-english.woff2"},{"unicodeRange":"U+00A1,U+00AA-00AB,U+00AF,U+00B8,U+00BB,U+00BF-00D6,U+00D8-00F6,U+00F8-00FF,U+0131,U+0152-0153,U+02B0-02FF","file":"Inter_1-rest-latin.woff2"},{"unicodeRange":"U+0100-0130,U+0132-0151,U+0154-017F","file":"Inter_1-latin-extended-a.woff2"},{"unicodeRange":"U+0180-024F","file":"Inter_1-latin-extended-b.woff2"},{"unicodeRange":"U+1E00-1EFF","file":"Inter_1-latin-extended-additional.woff2"},{"unicodeRange":"U+0250-02AF,U+0300-1DFF,U+1F00-1FFF,U+2070-2073,U+2075-20AB,U+20AD-2121,U+2123-218F,U+21BC-2211,U+2213-2214,U+2216-F8FE,U+F900-FEFE,U+FF00-FFFC,U+FFFE-FFFF","file":"Inter_1-rest.woff2"}]}},"Space Grotesk:Bold":{"id":"b4509f04a2bed1ec04e7f9ff4dc33c69e8e263ec","url":"b4509f04a2bed1ec04e7f9ff4dc33c69e8e263ec","source":2,"italic":false,"weight":700,"variationAxes":[{"tag":"wght","value":700.0,"name":"Weight"}]},"Space Grotesk:Medium":{"id":"b4509f04a2bed1ec04e7f9ff4dc33c69e8e263ec","url":"b4509f04a2bed1ec04e7f9ff4dc33c69e8e263ec","source":2,"italic":false,"weight":500,"variationAxes":[{"tag":"wght","value":500.0,"name":"Weight"}]}},"assets":{"67890705b206676b315ecc841606590e1ba56cf9":{"type":"PAINT_ASSET","url":"67890705b206676b315ecc841606590e1ba56cf9.png","size":{"x":0.0,"y":0.0}},"17425eb8a8bf737cffe650d82add6c9e91ecc898":{"type":"PAINT_ASSET","url":"17425eb8a8bf737cffe650d82add6c9e91ecc898.png","size":{"x":0.0,"y":0.0}},"7032336f7045c70cbc3fbe4ed1fc57278094f462":{"type":"PAINT_ASSET","url":"7032336f7045c70cbc3fbe4ed1fc57278094f462.png","size":{"x":0.0,"y":0.0}},"3d9a757f84c0ea946aaa25ec9108349887c539f5":{"type":"GENERATED_ASSET","url":"3d9a757f84c0ea946aaa25ec9108349887c539f5.svg","size":{"x":98.6073608398438,"y":61.0000076293945},"offsets":{"left":{"value":0.0,"unit":"PERCENT"},"right":{"value":0.0,"unit":"PERCENT"},"top":{"value":0.0,"unit":"PERCENT"},"bottom":{"value":0.0,"unit":"PERCENT"}},"format":"SVG"},"3f7d448959dae19faeb609f1c0c788d7478015b2":{"type":"PAINT_ASSET","url":"3f7d448959dae19faeb609f1c0c788d7478015b2.png","size":{"x":0.0,"y":0.0}},"6b375e2403993378a7d7cbee70ac0ebdf4bcd0b1":{"type":"PAINT_ASSET","url":"6b375e2403993378a7d7cbee70ac0ebdf4bcd0b1.png","size":{"x":0.0,"y":0.0}},"ac7c10cbd0fdc6abb1879adcbde109153c705a99":{"type":"GENERATED_ASSET","url":"ac7c10cbd0fdc6abb1879adcbde109153c705a99.svg","size":{"x":98.6073608398438,"y":61.0000076293945},"offsets":{"left":{"value":0.0,"unit":"PERCENT"},"right":{"value":0.0,"unit":"PERCENT"},"top":{"value":0.0,"unit":"PERCENT"},"bottom":{"value":0.0,"unit":"PERCENT"}},"format":"SVG"},"f2960da792f5a46d7746f0700aada2d59535c52f":{"type":"GENERATED_ASSET","url":"f2960da792f5a46d7746f0700aada2d59535c52f.png","size":{"x":18.0,"y":23.2113246917725},"offsets":{"top":{"value":0,"unit":"PIXELS"},"right":{"value":0,"unit":"PIXELS"},"bottom":{"value":0,"unit":"PIXELS"},"left":{"value":0,"unit":"PIXELS"}},"format":"PNG"},"d5bc026e2060048c0c5dabc57be40d827f000c61":{"type":"PAINT_ASSET","url":"d5bc026e2060048c0c5dabc57be40d827f000c61.png","size":{"x":0.0,"y":0.0}},"29fac907fecb0fba6aca7ca37ca6a656e839498a":{"type":"PAINT_ASSET","url":"29fac907fecb0fba6aca7ca37ca6a656e839498a.png","size":{"x":0.0,"y":0.0}},"ea74c6f71c8ce68ac8cbe134a44026b88d968241":{"type":"PAINT_ASSET","url":"ea74c6f71c8ce68ac8cbe134a44026b88d968241.png","size":{"x":0.0,"y":0.0}},"e723a3a921b7364a9fd5c9987650e639e5e37b99":{"type":"PAINT_ASSET","url":"e723a3a921b7364a9fd5c9987650e639e5e37b99.png","size":{"x":0.0,"y":0.0}},"7917312122cac4a37bf54b7ebfa8ee849e6a80cb":{"type":"PAINT_ASSET","url":"7917312122cac4a37bf54b7ebfa8ee849e6a80cb.png","size":{"x":0.0,"y":0.0}},"a2676ac4c97959766263b3fcdec052ca8952fcc4":{"type":"GENERATED_ASSET","url":"a2676ac4c97959766263b3fcdec052ca8952fcc4.png","size":{"x":110.0,"y":110.0},"offsets":{"top":{"value":0,"unit":"PIXELS"},"right":{"value":0,"unit":"PIXELS"},"bottom":{"value":0,"unit":"PIXELS"},"left":{"value":0,"unit":"PIXELS"}},"format":"PNG"}},"stablePathToAssetInfo":{"485:14099":{"hash":"3d9a757f84c0ea946aaa25ec9108349887c539f5"},"I728:9160;485:14099":{"hash":"3d9a757f84c0ea946aaa25ec9108349887c539f5"},"I728:9149;485:14099":{"hash":"ac7c10cbd0fdc6abb1879adcbde109153c705a99"},"536:2897":{"hash":"f2960da792f5a46d7746f0700aada2d59535c52f"},"557:4992":{"hash":"a2676ac4c97959766263b3fcdec052ca8952fcc4"}},"animateRootIds":[],"siteSettings":{"title":"Portfolio Loïc Berger","scalingMode":"REFLOW","blockSearchIndexing":true,"faviconFilename":"a2676ac4c97959766263b3fcdec052ca8952fcc4.png","socialImageFilename":"a2676ac4c97959766263b3fcdec052ca8952fcc4.png","labs":{"E5FBBA911B2B7A09E649D4BE6CDF8591EAEFC881":false}},"sourceCodeHash":""}