{"id":384,"date":"2020-03-23T08:36:03","date_gmt":"2020-03-23T08:36:03","guid":{"rendered":"https:\/\/www.rchfullstack.com\/index.php\/home\/"},"modified":"2026-07-17T10:50:35","modified_gmt":"2026-07-17T08:50:35","slug":"home","status":"publish","type":"page","link":"https:\/\/www.rchfullstack.com\/","title":{"rendered":"Home"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"384\" class=\"elementor elementor-384\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-540f8b2 elementor-section-stretched elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"540f8b2\" data-element_type=\"section\" data-e-type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;video&quot;,&quot;stretch_section&quot;:&quot;section-stretched&quot;,&quot;background_video_link&quot;:&quot;https:\\\/\\\/www.rchfullstack.com\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/rchfullstack_intro_l.mp4&quot;,&quot;background_play_on_mobile&quot;:&quot;yes&quot;}\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-background-video-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<video class=\"elementor-background-video-hosted\" role=\"presentation\" autoplay muted playsinline loop><\/video>\n\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t\t<div class=\"elementor-background-overlay\"><\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-no\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-d793f28\" data-id=\"d793f28\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-inner-section elementor-element elementor-element-b0b8684 elementor-section-height-min-height elementor-section-boxed elementor-section-height-default\" data-id=\"b0b8684\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-no\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-inner-column elementor-element elementor-element-1e508ec\" data-id=\"1e508ec\" data-element_type=\"column\" data-e-type=\"column\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t<div class=\"elementor-background-overlay\"><\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-69733e7 e-flex e-con-boxed e-con e-parent\" data-id=\"69733e7\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-463f125 elementor-widget__width-initial elementor-widget elementor-widget-heading\" data-id=\"463f125\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">V\u00c4LKOMMEN TILL<br>RCH FULLSTACK!<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8c8815e elementor-widget elementor-widget-heading\" data-id=\"8c8815e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">Jag hj\u00e4lper dig l\u00f6sa problemet \u2013 enkelt, direkt och med r\u00e4tt AI-verktyg\n\n<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-eea2e1d elementor-mobile-align-left elementor-widget__width-initial elementor-align-left elementor-widget-mobile__width-auto elementor-widget elementor-widget-button\" data-id=\"eea2e1d\" data-element_type=\"widget\" data-e-type=\"widget\" id=\"takontaktknappett\" data-widget_type=\"button.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<div class=\"elementor-button-wrapper\">\n\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-md\" href=\"#kontakt\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">Ta kontakt<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c6b8dac elementor-mobile-align-left elementor-widget__width-initial elementor-align-left elementor-widget-mobile__width-auto elementor-widget elementor-widget-button\" data-id=\"c6b8dac\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"button.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<div class=\"elementor-button-wrapper\">\n\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-sm\" href=\"#chat\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">Prata med RCH FULLSTACK<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-inner-column elementor-element elementor-element-7fb00dd\" data-id=\"7fb00dd\" data-element_type=\"column\" data-e-type=\"column\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f43ea38 e-flex e-con-boxed e-con e-parent\" data-id=\"f43ea38\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-c5c2f38 elementor-widget elementor-widget-html\" data-id=\"c5c2f38\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<style>\r\nmodel-viewer {\r\n  width: 100%;\r\n  height: 600px;\r\n  background-color: #ffffff01;\r\n\r\n}\r\n<\/style>\r\n<model-viewer src=\"https:\/\/www.rchfullstack.com\/wp-content\/uploads\/2023\/07\/rch_fullstack_logo_v1.glb\" ar ar-modes=\"webxr scene-viewer quick-look\" camera-controls poster=\"poster.webp\" shadow-intensity=\"1\" auto-rotate> \r\n<div class=\"progress-bar hide\" slot=\"progress-bar\">\r\n<div class=\"update-bar\"><\/div>\r\n<\/model-viewer>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t<div class=\"elementor-element elementor-element-45e8bd8 e-flex e-con-boxed e-con e-parent\" data-id=\"45e8bd8\" data-element_type=\"container\" data-e-type=\"container\" id=\"ommig\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f5135d1 elementor-widget elementor-widget-heading\" data-id=\"f5135d1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Om mig<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1bb7a55 elementor-widget elementor-widget-text-editor\" data-id=\"1bb7a55\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>RCH Fullstack hj\u00e4lper dig att bygga skr\u00e4ddarsydda l\u00f6sningar och automatisera arbetsfl\u00f6den med r\u00e4tt AI-verktyg.<\/p><p>\u00a0<\/p><p>AI f\u00f6r\u00e4ndrar hur f\u00f6retag arbetar \u2013 snabbare \u00e4n de flesta hinner anpassa sig. Jag hj\u00e4lper dig ligga steget f\u00f6re.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-41632a2 elementor-section-stretched elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"41632a2\" data-element_type=\"section\" data-e-type=\"section\" id=\"losningar\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;,&quot;stretch_section&quot;:&quot;section-stretched&quot;}\">\n\t\t\t\t\t\t\t<div class=\"elementor-background-overlay\"><\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-no\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-643cba7\" data-id=\"643cba7\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-inner-section elementor-element elementor-element-c53aabc losningar elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"c53aabc\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-inner-column elementor-element elementor-element-2535e90\" data-id=\"2535e90\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-4f2aff5 elementor-widget elementor-widget-heading\" data-id=\"4f2aff5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">L\u00f6sningar<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b8030ba elementor-widget elementor-widget-text-editor\" data-id=\"b8030ba\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p><strong>Med erfarenhet inom b\u00e5de front-end och back-end hittar jag r\u00e4tt l\u00f6sning f\u00f6r just ditt behov.<\/strong><\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-inner-section elementor-element elementor-element-66c24a2 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"66c24a2\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-33 elementor-inner-column elementor-element elementor-element-efbcc0b\" data-id=\"efbcc0b\" data-element_type=\"column\" data-e-type=\"column\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-d5068cb elementor-view-default elementor-widget elementor-widget-icon\" data-id=\"d5068cb\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"icon.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-icon-wrapper\">\n\t\t\t<div class=\"elementor-icon\">\n\t\t\t<svg aria-hidden=\"true\" class=\"e-font-icon-svg e-fas-user-lock\" viewBox=\"0 0 640 512\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M224 256A128 128 0 1 0 96 128a128 128 0 0 0 128 128zm96 64a63.08 63.08 0 0 1 8.1-30.5c-4.8-.5-9.5-1.5-14.5-1.5h-16.7a174.08 174.08 0 0 1-145.8 0h-16.7A134.43 134.43 0 0 0 0 422.4V464a48 48 0 0 0 48 48h280.9a63.54 63.54 0 0 1-8.9-32zm288-32h-32v-80a80 80 0 0 0-160 0v80h-32a32 32 0 0 0-32 32v160a32 32 0 0 0 32 32h224a32 32 0 0 0 32-32V320a32 32 0 0 0-32-32zM496 432a32 32 0 1 1 32-32 32 32 0 0 1-32 32zm32-144h-64v-80a32 32 0 0 1 64 0z\"><\/path><\/svg>\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ab291d2 elementor-widget elementor-widget-heading\" data-id=\"ab291d2\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h4 class=\"elementor-heading-title elementor-size-default\">S\u00e4kerhet<\/h4>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-574aea5 elementor-widget elementor-widget-text-editor\" data-id=\"574aea5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>S\u00e4kerhetskontroll med AI \u2013 identifierar s\u00e5rbarheter i kod och system som annars missas.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-33 elementor-inner-column elementor-element elementor-element-102249d\" data-id=\"102249d\" data-element_type=\"column\" data-e-type=\"column\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-bb6c83f elementor-view-default elementor-widget elementor-widget-icon\" data-id=\"bb6c83f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"icon.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-icon-wrapper\">\n\t\t\t<div class=\"elementor-icon\">\n\t\t\t<svg aria-hidden=\"true\" class=\"e-font-icon-svg e-fas-compress-alt\" viewBox=\"0 0 448 512\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M4.686 427.314L104 328l-32.922-31.029C55.958 281.851 66.666 256 88.048 256h112C213.303 256 224 266.745 224 280v112c0 21.382-25.803 32.09-40.922 16.971L152 376l-99.314 99.314c-6.248 6.248-16.379 6.248-22.627 0L4.686 449.941c-6.248-6.248-6.248-16.379 0-22.627zM443.314 84.686L344 184l32.922 31.029c15.12 15.12 4.412 40.971-16.97 40.971h-112C234.697 256 224 245.255 224 232V120c0-21.382 25.803-32.09 40.922-16.971L296 136l99.314-99.314c6.248-6.248 16.379-6.248 22.627 0l25.373 25.373c6.248 6.248 6.248 16.379 0 22.627z\"><\/path><\/svg>\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c417e6d elementor-widget elementor-widget-heading\" data-id=\"c417e6d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h4 class=\"elementor-heading-title elementor-size-default\">API<\/h4>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5f00764 elementor-widget elementor-widget-text-editor\" data-id=\"5f00764\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Koppla ihop dina system med r\u00e4tt API \u2013 spara tid och resurser genom att anv\u00e4nda det som redan finns.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-33 elementor-inner-column elementor-element elementor-element-fdcf86c\" data-id=\"fdcf86c\" data-element_type=\"column\" data-e-type=\"column\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-0ac5935 elementor-view-default elementor-widget elementor-widget-icon\" data-id=\"0ac5935\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"icon.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-icon-wrapper\">\n\t\t\t<div class=\"elementor-icon\">\n\t\t\t<svg aria-hidden=\"true\" class=\"e-font-icon-svg e-fas-robot\" viewBox=\"0 0 640 512\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M32,224H64V416H32A31.96166,31.96166,0,0,1,0,384V256A31.96166,31.96166,0,0,1,32,224Zm512-48V448a64.06328,64.06328,0,0,1-64,64H160a64.06328,64.06328,0,0,1-64-64V176a79.974,79.974,0,0,1,80-80H288V32a32,32,0,0,1,64,0V96H464A79.974,79.974,0,0,1,544,176ZM264,256a40,40,0,1,0-40,40A39.997,39.997,0,0,0,264,256Zm-8,128H192v32h64Zm96,0H288v32h64ZM456,256a40,40,0,1,0-40,40A39.997,39.997,0,0,0,456,256Zm-8,128H384v32h64ZM640,256V384a31.96166,31.96166,0,0,1-32,32H576V224h32A31.96166,31.96166,0,0,1,640,256Z\"><\/path><\/svg>\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2e58b3b elementor-widget elementor-widget-heading\" data-id=\"2e58b3b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h4 class=\"elementor-heading-title elementor-size-default\">AI &amp; Agenter<\/h4>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2d07621 elementor-widget elementor-widget-text-editor\" data-id=\"2d07621\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Fr\u00e5n analys till handling \u2013 AI och agenter som arbetar \u00e5t dig dygnet runt, tar beslut och levererar resultat medans du fokuserar p\u00e5 tillv\u00e4xt.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t<div class=\"elementor-element elementor-element-df89238 e-con-full animated-slow e-flex elementor-invisible e-con e-parent\" data-id=\"df89238\" data-element_type=\"container\" data-e-type=\"container\" id=\"case\" data-settings=\"{&quot;animation&quot;:&quot;slideInLeft&quot;}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-36a97f7 elementor-widget elementor-widget-heading\" data-id=\"36a97f7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">FRONT-END<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4c46d0c elementor-widget__width-initial elementor-arrows-position-inside elementor-widget elementor-widget-image-carousel\" data-id=\"4c46d0c\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;slides_to_show&quot;:&quot;3&quot;,&quot;slides_to_show_tablet&quot;:&quot;1&quot;,&quot;slides_to_show_mobile&quot;:&quot;1&quot;,&quot;navigation&quot;:&quot;arrows&quot;,&quot;image_spacing_custom&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:0,&quot;sizes&quot;:[]},&quot;autoplay&quot;:&quot;yes&quot;,&quot;pause_on_hover&quot;:&quot;yes&quot;,&quot;pause_on_interaction&quot;:&quot;yes&quot;,&quot;autoplay_speed&quot;:5000,&quot;infinite&quot;:&quot;yes&quot;,&quot;speed&quot;:500}\" data-widget_type=\"image-carousel.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-image-carousel-wrapper swiper\" role=\"region\" aria-roledescription=\"carousel\" aria-label=\"Bildkarusell\" dir=\"ltr\">\n\t\t\t<div class=\"elementor-image-carousel swiper-wrapper\" aria-live=\"off\">\n\t\t\t\t\t\t\t\t<div class=\"swiper-slide\" role=\"group\" aria-roledescription=\"slide\" aria-label=\"1 av 4\"><figure class=\"swiper-slide-inner\"><img decoding=\"async\" class=\"swiper-slide-image\" src=\"https:\/\/www.rchfullstack.com\/wp-content\/uploads\/2023\/04\/rhc_rubotic_w_logo.png\" alt=\"rhc_rubotic_w_logo\" \/><\/figure><\/div><div class=\"swiper-slide\" role=\"group\" aria-roledescription=\"slide\" aria-label=\"2 av 4\"><figure class=\"swiper-slide-inner\"><img decoding=\"async\" class=\"swiper-slide-image\" src=\"https:\/\/www.rchfullstack.com\/wp-content\/uploads\/2023\/04\/rhc_rubotnic_w_logo.png\" alt=\"rhc_rubotnic_w_logo\" \/><\/figure><\/div><div class=\"swiper-slide\" role=\"group\" aria-roledescription=\"slide\" aria-label=\"3 av 4\"><figure class=\"swiper-slide-inner\"><img decoding=\"async\" class=\"swiper-slide-image\" src=\"https:\/\/www.rchfullstack.com\/wp-content\/uploads\/2023\/04\/rhc_savetellus_w_logo.png\" alt=\"rhc_savetellus_w_logo\" \/><\/figure><\/div><div class=\"swiper-slide\" role=\"group\" aria-roledescription=\"slide\" aria-label=\"4 av 4\"><figure class=\"swiper-slide-inner\"><img decoding=\"async\" class=\"swiper-slide-image\" src=\"https:\/\/www.rchfullstack.com\/wp-content\/uploads\/2023\/04\/rhc_xgene_w_logo.png\" alt=\"rhc_xgene_w_logo\" \/><\/figure><\/div>\t\t\t<\/div>\n\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"elementor-swiper-button elementor-swiper-button-prev\" role=\"button\" tabindex=\"0\">\n\t\t\t\t\t\t<svg aria-hidden=\"true\" class=\"e-font-icon-svg e-fas-chevron-left\" viewBox=\"0 0 320 512\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z\"><\/path><\/svg>\t\t\t\t\t<\/div>\n\t\t\t\t\t<div class=\"elementor-swiper-button elementor-swiper-button-next\" role=\"button\" tabindex=\"0\">\n\t\t\t\t\t\t<svg aria-hidden=\"true\" class=\"e-font-icon-svg e-fas-chevron-right\" viewBox=\"0 0 320 512\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"><\/path><\/svg>\t\t\t\t\t<\/div>\n\t\t\t\t\n\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-38986cc e-con-full animated-slow e-flex elementor-invisible e-con e-parent\" data-id=\"38986cc\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;animation&quot;:&quot;fadeInRight&quot;}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a656d34 elementor-widget elementor-widget-heading\" data-id=\"a656d34\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">BACK-END<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4bdcd06 elementor-widget__width-initial elementor-arrows-position-inside elementor-widget elementor-widget-image-carousel\" data-id=\"4bdcd06\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;slides_to_show&quot;:&quot;3&quot;,&quot;slides_to_show_tablet&quot;:&quot;1&quot;,&quot;slides_to_show_mobile&quot;:&quot;1&quot;,&quot;navigation&quot;:&quot;arrows&quot;,&quot;image_spacing_custom&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:0,&quot;sizes&quot;:[]},&quot;autoplay&quot;:&quot;yes&quot;,&quot;pause_on_hover&quot;:&quot;yes&quot;,&quot;pause_on_interaction&quot;:&quot;yes&quot;,&quot;autoplay_speed&quot;:5000,&quot;infinite&quot;:&quot;yes&quot;,&quot;speed&quot;:500}\" data-widget_type=\"image-carousel.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-image-carousel-wrapper swiper\" role=\"region\" aria-roledescription=\"carousel\" aria-label=\"Bildkarusell\" dir=\"ltr\">\n\t\t\t<div class=\"elementor-image-carousel swiper-wrapper\" aria-live=\"off\">\n\t\t\t\t\t\t\t\t<div class=\"swiper-slide\" role=\"group\" aria-roledescription=\"slide\" aria-label=\"1 av 4\"><figure class=\"swiper-slide-inner\"><img decoding=\"async\" class=\"swiper-slide-image\" src=\"https:\/\/www.rchfullstack.com\/wp-content\/uploads\/2023\/04\/rhc_xgene_w_logo.png\" alt=\"rhc_xgene_w_logo\" \/><\/figure><\/div><div class=\"swiper-slide\" role=\"group\" aria-roledescription=\"slide\" aria-label=\"2 av 4\"><figure class=\"swiper-slide-inner\"><img decoding=\"async\" class=\"swiper-slide-image\" src=\"https:\/\/www.rchfullstack.com\/wp-content\/uploads\/2023\/04\/rhc_savetellus_w_logo.png\" alt=\"rhc_savetellus_w_logo\" \/><\/figure><\/div><div class=\"swiper-slide\" role=\"group\" aria-roledescription=\"slide\" aria-label=\"3 av 4\"><figure class=\"swiper-slide-inner\"><img decoding=\"async\" class=\"swiper-slide-image\" src=\"https:\/\/www.rchfullstack.com\/wp-content\/uploads\/2023\/04\/rhc_rubotnic_w_logo.png\" alt=\"rhc_rubotnic_w_logo\" \/><\/figure><\/div><div class=\"swiper-slide\" role=\"group\" aria-roledescription=\"slide\" aria-label=\"4 av 4\"><figure class=\"swiper-slide-inner\"><img decoding=\"async\" class=\"swiper-slide-image\" src=\"https:\/\/www.rchfullstack.com\/wp-content\/uploads\/2023\/04\/rhc_rubotic_w_logo.png\" alt=\"rhc_rubotic_w_logo\" \/><\/figure><\/div>\t\t\t<\/div>\n\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"elementor-swiper-button elementor-swiper-button-prev\" role=\"button\" tabindex=\"0\">\n\t\t\t\t\t\t<svg aria-hidden=\"true\" class=\"e-font-icon-svg e-fas-chevron-left\" viewBox=\"0 0 320 512\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z\"><\/path><\/svg>\t\t\t\t\t<\/div>\n\t\t\t\t\t<div class=\"elementor-swiper-button elementor-swiper-button-next\" role=\"button\" tabindex=\"0\">\n\t\t\t\t\t\t<svg aria-hidden=\"true\" class=\"e-font-icon-svg e-fas-chevron-right\" viewBox=\"0 0 320 512\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"><\/path><\/svg>\t\t\t\t\t<\/div>\n\t\t\t\t\n\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-808fded e-flex e-con-boxed e-con e-parent\" data-id=\"808fded\" data-element_type=\"container\" data-e-type=\"container\" id=\"chat\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-dbe75fa elementor-widget elementor-widget-heading\" data-id=\"dbe75fa\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Klicka p\u00e5 sf\u00e4ren<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4cd2340 elementor-widget elementor-widget-html\" data-id=\"4cd2340\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html>\r\n<head>\r\n  <style>\r\n    #energy-sphere-container {\r\n      width: 100%;\r\n      height: 500px;\r\n      position: relative;\r\n      overflow: hidden;\r\n      background-color: #ffffff00;\r\n      cursor: pointer;\r\n      \/* Mobil: f\u00f6rhindra dubbeltrycks-zoom (som annars b\u00e5de zoomar\r\n         sidan och f\u00f6rdr\u00f6jer klick ~300ms), textmarkering vid snabba\r\n         tryck och den gr\u00e5 blinkrutan vid tapp *\/\r\n      touch-action: manipulation;\r\n      user-select: none;\r\n      -webkit-user-select: none;\r\n      -webkit-tap-highlight-color: transparent;\r\n    }\r\n    #sphere-status {\r\n      position: absolute;\r\n      bottom: 16px;\r\n      left: 0;\r\n      right: 0;\r\n      text-align: center;\r\n      font-family: system-ui, sans-serif;\r\n      font-size: 14px;\r\n      color: #0E5287;\r\n      pointer-events: none;\r\n      opacity: 0.85;\r\n    }\r\n    #sphere-controls {\r\n      position: absolute;\r\n      top: 12px;\r\n      left: 12px;\r\n      z-index: 10;\r\n      display: flex;\r\n      gap: 8px;\r\n    }\r\n    #sphere-controls button {\r\n      padding: 6px 12px;\r\n      border: 1px solid #0E5287;\r\n      border-radius: 6px;\r\n      background: rgba(255, 255, 255, 0.85);\r\n      color: #0E5287;\r\n      font-family: system-ui, sans-serif;\r\n      font-size: 13px;\r\n      cursor: pointer;\r\n    }\r\n    #sphere-controls button:hover { background: #0E5287; color: #fff; }\r\n\r\n    \/* ---------- Helsk\u00e4rmsl\u00e4ge (mobil & desktop) ---------- *\/\r\n    \/* CSS-baserat s\u00e5 det fungerar \u00e4ven p\u00e5 iPhone, d\u00e4r Fullscreen API\r\n       inte st\u00f6djs f\u00f6r vanliga element. 100dvh tar h\u00e4nsyn till mobilens\r\n       adressf\u00e4lt; 100vh \u00e4r fallback f\u00f6r \u00e4ldre webbl\u00e4sare. *\/\r\n    #energy-sphere-container.fs-active {\r\n      position: fixed;\r\n      inset: 0;\r\n      width: 100vw;\r\n      height: 100vh;\r\n      height: 100dvh;\r\n      z-index: 9999;\r\n      background: radial-gradient(ellipse at 50% 38%,\r\n        #0d2137 0%, #071527 55%, #030a14 100%);\r\n    }\r\n    #energy-sphere-container.fs-active #sphere-status {\r\n      color: #8fc3e8;\r\n      text-shadow: 0 0 14px rgba(30, 106, 159, 0.9);\r\n      bottom: calc(12px + env(safe-area-inset-bottom, 0px));\r\n    }\r\n    #energy-sphere-container.fs-active #sphere-controls {\r\n      top: calc(12px + env(safe-area-inset-top, 0px));\r\n      left: calc(12px + env(safe-area-inset-left, 0px));\r\n    }\r\n    #energy-sphere-container.fs-active #sphere-controls button {\r\n      border: 1px solid rgba(143, 195, 232, 0.35);\r\n      border-radius: 999px;\r\n      background: rgba(255, 255, 255, 0.07);\r\n      color: #a8d4f0;\r\n      backdrop-filter: blur(6px);\r\n      -webkit-backdrop-filter: blur(6px);\r\n    }\r\n    #energy-sphere-container.fs-active #sphere-controls button:hover {\r\n      background: rgba(143, 195, 232, 0.22);\r\n      color: #ffffff;\r\n    }\r\n\r\n    \/* ---------- PiP-f\u00f6nstrets utseende ---------- *\/\r\n    \/* Sf\u00e4rens partiklar anv\u00e4nder additiv blending och \u00e4r designade f\u00f6r\r\n       m\u00f6rk bakgrund \u2014 p\u00e5 vit blir de urvattnade. M\u00f6rk gradient + hidden\r\n       overflow (tar bort scrollbaren) + glasknapp. *\/\r\n    .pip-mode {\r\n      margin: 0;\r\n      overflow: hidden;\r\n      background: radial-gradient(ellipse at 50% 38%,\r\n        #0d2137 0%, #071527 55%, #030a14 100%);\r\n    }\r\n    .pip-mode #energy-sphere-container {\r\n      width: 100vw;\r\n      height: 100vh;\r\n    }\r\n    .pip-mode #sphere-status {\r\n      color: #8fc3e8;\r\n      font-size: 12.5px;\r\n      letter-spacing: 0.3px;\r\n      text-shadow: 0 0 14px rgba(30, 106, 159, 0.9);\r\n      bottom: 12px;\r\n    }\r\n    \/* Helsk\u00e4rm \u00e4r inte meningsfullt inne i PiP-rutan *\/\r\n    .pip-mode #fullscreen-toggle { display: none; }\r\n    .pip-mode #sphere-controls { top: 10px; left: 10px; right: auto; }\r\n    .pip-mode #sphere-controls button {\r\n      padding: 5px 14px;\r\n      border: 1px solid rgba(143, 195, 232, 0.35);\r\n      border-radius: 999px;\r\n      background: rgba(255, 255, 255, 0.07);\r\n      color: #a8d4f0;\r\n      font-size: 12px;\r\n      backdrop-filter: blur(6px);\r\n      -webkit-backdrop-filter: blur(6px);\r\n      transition: background 0.15s, color 0.15s;\r\n    }\r\n    .pip-mode #sphere-controls button:hover {\r\n      background: rgba(143, 195, 232, 0.22);\r\n      color: #ffffff;\r\n      border-color: rgba(168, 212, 240, 0.6);\r\n    }\r\n  <\/style>\r\n<\/head>\r\n<body>\r\n  <div id=\"energy-sphere-container\">\r\n    <div id=\"sphere-controls\">\r\n      <button id=\"fullscreen-toggle\">Helsk\u00e4rm \u26f6<\/button>\r\n      <button id=\"save-transcript\">Spara text \ud83d\udcbe<\/button>\r\n      <button id=\"pip-toggle\" hidden>Flytande l\u00e4ge \u29c9<\/button>\r\n    <\/div>\r\n    <div id=\"sphere-status\">Klicka p\u00e5 sf\u00e4ren f\u00f6r att prata med RCH FULLSTACK<\/div>\r\n  <\/div>\r\n\r\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/three.js\/r128\/three.min.js\"><\/script>\r\n\r\n  <script>\r\n    \/\/ =====================================================================\r\n    \/\/ GEMINI LIVE API \u2014 r\u00f6stkonversation via WebSocket\r\n    \/\/ Input:  PCM 16-bit, 16 kHz, mono  (mikrofon, nedsamplad)\r\n    \/\/ Output: PCM 16-bit, 24 kHz, mono  (spelas upp + driver sf\u00e4ren)\r\n    \/\/\r\n    \/\/ VARNING: Nyckeln ligger i klientkoden och \u00e4r synlig f\u00f6r alla som\r\n    \/\/ \u00f6ppnar sidan. OK f\u00f6r test \u2014 f\u00f6r publik sajt: proxy\/ephemeral tokens.\r\n    \/\/ =====================================================================\r\n    const GEMINI_API_KEY = 'AIzaSyBMg6WCZZjk9L9Be-YszZCQ_FTiv5Y8vz8';\r\n    const GEMINI_MODEL = 'models\/gemini-2.5-flash-native-audio-latest';\r\n    const WS_URL =\r\n      'wss:\/\/generativelanguage.googleapis.com\/ws\/' +\r\n      'google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent' +\r\n      '?key=' + GEMINI_API_KEY;\r\n\r\n    const SYSTEM_INSTRUCTION =\r\n      'Du \u00e4r RCH FULLSTACK:s r\u00f6stassistent med en manlig r\u00f6st. ' +\r\n      'Om n\u00e5gon fr\u00e5gar vem du \u00e4r eller vad du heter: du \u00e4r RCH FULLSTACK:s ' +\r\n      'assistent. N\u00e4mn aldrig Gemini eller Google. ' +\r\n      'Svara alltid p\u00e5 samma spr\u00e5k som anv\u00e4ndaren talar, oavsett vilket spr\u00e5k det \u00e4r. ' +\r\n      'Om anv\u00e4ndaren byter spr\u00e5k, byt med. ' +\r\n      'VIKTIGT: Beh\u00e5ll exakt samma manliga r\u00f6st, tonl\u00e4ge och r\u00f6stkarakt\u00e4r ' +\r\n      'genom hela samtalet och p\u00e5 alla spr\u00e5k. Byt aldrig r\u00f6st, dialekt eller ' +\r\n      'accent om det inte \u00e4r absolut n\u00f6dv\u00e4ndigt. ' +\r\n      'Svara kort och naturligt.';\r\n\r\n    \/\/ ---------------------------------------------------------------------\r\n    \/\/ GeminiVoiceSession \u2014 kapslar in WS + mic + uppspelning + analyser\r\n    \/\/ ---------------------------------------------------------------------\r\n    function createGeminiVoiceSession(callbacks) {\r\n      \/\/ callbacks: { onStateChange(state), getAnalyserData() exposed via session }\r\n      let ws = null;\r\n      let captureCtx = null;      \/\/ mic, systemets samplingsfrekvens\r\n      let playbackCtx = null;     \/\/ 24 kHz f\u00f6r Geminis ljud\r\n      let analyser = null;\r\n      let analyserData = null;\r\n      let micStream = null;\r\n      let micSource = null;\r\n      let micProcessor = null;\r\n      let nextStartTime = 0;\r\n      let activeSources = [];\r\n      let state = 'idle'; \/\/ idle | connecting | listening | error\r\n\r\n      \/\/ Sessionsgeneration: varje start() f\u00e5r ett nytt nummer och varje\r\n      \/\/ stop() ogiltigf\u00f6rklarar det. Utan detta kan ett snabbt\r\n      \/\/ start\u2192stop\u2192(startens await blir klar) l\u00e4cka en levande session\r\n      \/\/ med \u00f6ppen WebSocket och mikrofon som ingen kan st\u00e4nga av.\r\n      let sessionGen = 0;\r\n      \/\/ Skicka inte mikrofonljud f\u00f6rr\u00e4n servern bekr\u00e4ftat setup\r\n      let setupDone = false;\r\n      \/\/ N\u00e4r aktuell session blev klar (f\u00f6r samtalstimern i UI:t)\r\n      let sessionStartMs = 0;\r\n\r\n      \/\/ Transkript: allt som sagts, ackumuleras \u00f6ver sessioner tills\r\n      \/\/ anv\u00e4ndaren laddar ner eller rensar. Fragment fr\u00e5n servern sys\r\n      \/\/ ihop till sammanh\u00e4ngande repliker per talare.\r\n      let transcript = []; \/\/ { who: 'user'|'gemini', text, time }\r\n      let lastSpeaker = null;\r\n\r\n      function appendTranscript(who, fragment) {\r\n        if (!fragment) return;\r\n        const last = transcript[transcript.length - 1];\r\n        if (last && lastSpeaker === who) {\r\n          last.text += fragment; \/\/ forts\u00e4ttning p\u00e5 samma replik\r\n        } else {\r\n          transcript.push({\r\n            who,\r\n            text: fragment,\r\n            time: new Date().toLocaleTimeString('sv-SE', {\r\n              hour: '2-digit', minute: '2-digit'\r\n            })\r\n          });\r\n          lastSpeaker = who;\r\n        }\r\n      }\r\n\r\n      function setState(s) {\r\n        state = s;\r\n        callbacks.onStateChange(s);\r\n      }\r\n\r\n      function floatTo16BitPCM(float32) {\r\n        const int16 = new Int16Array(float32.length);\r\n        for (let i = 0; i < float32.length; i++) {\r\n          const s = Math.max(-1, Math.min(1, float32[i]));\r\n          int16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;\r\n        }\r\n        return int16;\r\n      }\r\n\r\n      \/\/ Nedsampling till 16 kHz med medelv\u00e4rdesbildning per utsample.\r\n      \/\/ (Ren nearest-neighbor ger aliasing-artefakter som f\u00f6rs\u00e4mrar\r\n      \/\/ Geminis taligenk\u00e4nning.)\r\n      function downsample(float32, fromRate, toRate) {\r\n        if (fromRate === toRate) return float32;\r\n        const ratio = fromRate \/ toRate;\r\n        const newLength = Math.floor(float32.length \/ ratio);\r\n        const result = new Float32Array(newLength);\r\n        for (let i = 0; i < newLength; i++) {\r\n          const startIdx = Math.floor(i * ratio);\r\n          const endIdx = Math.min(Math.floor((i + 1) * ratio), float32.length);\r\n          let sum = 0, n = 0;\r\n          for (let j = startIdx; j < endIdx; j++) { sum += float32[j]; n++; }\r\n          result[i] = n > 0 ? sum \/ n : 0;\r\n        }\r\n        return result;\r\n      }\r\n\r\n      function int16ToBase64(int16) {\r\n        const bytes = new Uint8Array(int16.buffer);\r\n        let binary = '';\r\n        const CHUNK = 0x8000;\r\n        for (let i = 0; i < bytes.length; i += CHUNK) {\r\n          binary += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));\r\n        }\r\n        return btoa(binary);\r\n      }\r\n\r\n      function base64ToInt16(b64) {\r\n        const binary = atob(b64);\r\n        \/\/ Int16Array kr\u00e4ver j\u00e4mn bytel\u00e4ngd \u2014 trunkera annars (RangeError)\r\n        const len = binary.length - (binary.length % 2);\r\n        const bytes = new Uint8Array(len);\r\n        for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);\r\n        return new Int16Array(bytes.buffer);\r\n      }\r\n\r\n      async function startMic(myGen) {\r\n        \/\/ Skapa lokalt, committa till delade variabler f\u00f6rst EFTER\r\n        \/\/ generationskontrollen. Annars kan en gammal start() som v\u00e4ntat\r\n        \/\/ p\u00e5 tillst\u00e5ndsdialogen skriva \u00f6ver en nyare sessions resurser.\r\n        const stream = await navigator.mediaDevices.getUserMedia({\r\n          audio: {\r\n            channelCount: 1,\r\n            \/\/ Viktigt vid h\u00f6gtalare (inte headset): utan ekoreducering\r\n            \/\/ h\u00f6r Gemini sin egen r\u00f6st och avbryter sig sj\u00e4lv.\r\n            echoCancellation: true,\r\n            noiseSuppression: true,\r\n            autoGainControl: true\r\n          }\r\n        });\r\n\r\n        if (myGen !== sessionGen) {\r\n          \/\/ Sessionen avbr\u00f6ts\/ersattes medan dialogen var \u00f6ppen \u2014\r\n          \/\/ sl\u00e4pp det vi sj\u00e4lva skapade och r\u00f6r inget annat\r\n          stream.getTracks().forEach(t => t.stop());\r\n          return false;\r\n        }\r\n\r\n        micStream = stream;\r\n        captureCtx = new (window.AudioContext || window.webkitAudioContext)();\r\n        micSource = captureCtx.createMediaStreamSource(micStream);\r\n        \/\/ ScriptProcessorNode \u00e4r deprecated men fungerar \u00f6verallt.\r\n        \/\/ AudioWorklet \u00e4r r\u00e4tt v\u00e4g f\u00f6r produktion.\r\n        micProcessor = captureCtx.createScriptProcessor(4096, 1, 1);\r\n\r\n        micProcessor.onaudioprocess = (e) => {\r\n          \/\/ Skicka inget f\u00f6rr\u00e4n servern bekr\u00e4ftat setup \u2014 annars riskerar\r\n          \/\/ ljudpaket att n\u00e5 servern innan sessionen \u00e4r konfigurerad\r\n          if (!setupDone || !ws || ws.readyState !== WebSocket.OPEN) return;\r\n          if (!captureCtx) return;\r\n          const input = e.inputBuffer.getChannelData(0);\r\n          const down = downsample(input, captureCtx.sampleRate, 16000);\r\n          const pcm16 = floatTo16BitPCM(down);\r\n          ws.send(JSON.stringify({\r\n            realtimeInput: {\r\n              mediaChunks: [{\r\n                mimeType: 'audio\/pcm;rate=16000',\r\n                data: int16ToBase64(pcm16)\r\n              }]\r\n            }\r\n          }));\r\n        };\r\n\r\n        micSource.connect(micProcessor);\r\n        \/\/ ScriptProcessor kr\u00e4ver anslutning till destination f\u00f6r att k\u00f6ras;\r\n        \/\/ gain 0 s\u00e5 mic-ljudet inte ekas ut i h\u00f6gtalarna.\r\n        const silent = captureCtx.createGain();\r\n        silent.gain.value = 0;\r\n        micProcessor.connect(silent);\r\n        silent.connect(captureCtx.destination);\r\n        return true;\r\n      }\r\n\r\n      function initPlayback() {\r\n        playbackCtx = new (window.AudioContext || window.webkitAudioContext)({\r\n          sampleRate: 24000\r\n        });\r\n        analyser = playbackCtx.createAnalyser();\r\n        analyser.fftSize = 256;\r\n        analyserData = new Uint8Array(analyser.frequencyBinCount);\r\n        analyser.connect(playbackCtx.destination);\r\n        nextStartTime = 0;\r\n      }\r\n\r\n      function playChunk(base64Data) {\r\n        \/\/ Sena servermeddelanden kan anl\u00e4nda efter stop() \u2014 ignorera dem\r\n        if (!playbackCtx || !analyser) return;\r\n\r\n        const int16 = base64ToInt16(base64Data);\r\n        if (int16.length === 0) return;\r\n        const float32 = new Float32Array(int16.length);\r\n        for (let i = 0; i < int16.length; i++) {\r\n          float32[i] = int16[i] \/ (int16[i] < 0 ? 0x8000 : 0x7FFF);\r\n        }\r\n        const buffer = playbackCtx.createBuffer(1, float32.length, 24000);\r\n        buffer.getChannelData(0).set(float32);\r\n\r\n        const source = playbackCtx.createBufferSource();\r\n        source.buffer = buffer;\r\n        source.connect(analyser);\r\n\r\n        const startAt = Math.max(playbackCtx.currentTime, nextStartTime);\r\n        source.start(startAt);\r\n        nextStartTime = startAt + buffer.duration;\r\n\r\n        activeSources.push(source);\r\n        source.onended = () => {\r\n          activeSources = activeSources.filter(s => s !== source);\r\n        };\r\n      }\r\n\r\n      \/\/ Gemini avbr\u00f6ts av anv\u00e4ndaren (barge-in) \u2192 t\u00f6m uppspelningsk\u00f6n\r\n      function stopPlayback() {\r\n        activeSources.forEach(s => { try { s.stop(); } catch (e) {} });\r\n        activeSources = [];\r\n        nextStartTime = 0;\r\n      }\r\n\r\n      function handleServerMessage(msg) {\r\n        if (msg.setupComplete) {\r\n          setupDone = true;\r\n          sessionStartMs = Date.now();\r\n          setState('listening');\r\n          return;\r\n        }\r\n        \/\/ Servern f\u00f6rvarnar strax innan anslutningen st\u00e4ngs\r\n        \/\/ (~10 min-gr\u00e4nsen per WebSocket-anslutning)\r\n        if (msg.goAway) {\r\n          if (callbacks.onGoAway) callbacks.onGoAway(msg.goAway.timeLeft);\r\n          return;\r\n        }\r\n        const sc = msg.serverContent;\r\n        if (!sc) return;\r\n\r\n        \/\/ Transkriptioner: vad anv\u00e4ndaren sa och vad Gemini svarade\r\n        if (sc.inputTranscription && sc.inputTranscription.text) {\r\n          appendTranscript('user', sc.inputTranscription.text);\r\n        }\r\n        if (sc.outputTranscription && sc.outputTranscription.text) {\r\n          appendTranscript('gemini', sc.outputTranscription.text);\r\n        }\r\n        if (sc.turnComplete) {\r\n          lastSpeaker = null; \/\/ n\u00e4sta fragment blir en ny replik\r\n        }\r\n\r\n        if (sc.interrupted) {\r\n          stopPlayback();\r\n          return;\r\n        }\r\n        if (sc.modelTurn && sc.modelTurn.parts) {\r\n          for (const part of sc.modelTurn.parts) {\r\n            if (part.inlineData && part.inlineData.mimeType &&\r\n                part.inlineData.mimeType.startsWith('audio\/pcm')) {\r\n              playChunk(part.inlineData.data);\r\n            }\r\n          }\r\n        }\r\n      }\r\n\r\n      \/\/ Sl\u00e4pper alla ljudresurser. Anropas b\u00e5de fr\u00e5n stop() och fr\u00e5n\r\n      \/\/ felv\u00e4gar i start() \u2014 utan detta l\u00e4cker AudioContext-instanser\r\n      \/\/ vid t.ex. nekad mikrofon, och webbl\u00e4saren har en h\u00e5rd gr\u00e4ns\r\n      \/\/ (~6 st) varefter allt ljud slutar fungera.\r\n      function releaseAudio() {\r\n        if (micProcessor) {\r\n          micProcessor.onaudioprocess = null;\r\n          try { micProcessor.disconnect(); } catch (e) {}\r\n          micProcessor = null;\r\n        }\r\n        if (micSource) {\r\n          try { micSource.disconnect(); } catch (e) {}\r\n          micSource = null;\r\n        }\r\n        if (micStream) {\r\n          micStream.getTracks().forEach(t => t.stop());\r\n          micStream = null;\r\n        }\r\n        if (captureCtx) {\r\n          try { captureCtx.close(); } catch (e) {}\r\n          captureCtx = null;\r\n        }\r\n        stopPlayback();\r\n        if (playbackCtx) {\r\n          try { playbackCtx.close(); } catch (e) {}\r\n          playbackCtx = null;\r\n        }\r\n        analyser = null;\r\n        analyserData = null;\r\n      }\r\n\r\n      async function start() {\r\n        \/\/ Ignorera dubbelklick \u2014 en session \u00e4r redan p\u00e5 v\u00e4g upp\/ig\u00e5ng\r\n        if (state === 'connecting' || state === 'listening') return;\r\n\r\n        const myGen = ++sessionGen;\r\n        setupDone = false;\r\n        setState('connecting');\r\n\r\n        try {\r\n          initPlayback();\r\n          if (playbackCtx.state === 'suspended') await playbackCtx.resume();\r\n          \/\/ stop() eller en nyare start() kan ha tagit \u00f6ver medan vi\r\n          \/\/ v\u00e4ntade. VIKTIGT: bara returnera \u2014 anropa INTE releaseAudio()\r\n          \/\/ h\u00e4r, f\u00f6r de delade variablerna kan nu tillh\u00f6ra den nyare\r\n          \/\/ sessionen och f\u00e5r inte sl\u00e4ppas av oss.\r\n          if (myGen !== sessionGen) return;\r\n\r\n          const micOk = await startMic(myGen);\r\n          if (!micOk || myGen !== sessionGen) return;\r\n        } catch (err) {\r\n          console.error('Mikrofon\/ljud-init misslyckades:', err);\r\n          \/\/ Sl\u00e4pp resurser bara om vi fortfarande \u00e4ger dem\r\n          if (myGen === sessionGen) {\r\n            releaseAudio();\r\n            setState('error');\r\n          }\r\n          return;\r\n        }\r\n\r\n        ws = new WebSocket(WS_URL);\r\n        const myWs = ws;\r\n\r\n        myWs.onopen = () => {\r\n          if (myGen !== sessionGen) {\r\n            try { myWs.close(1000); } catch (e) {}\r\n            return;\r\n          }\r\n          myWs.send(JSON.stringify({\r\n            setup: {\r\n              model: GEMINI_MODEL,\r\n              generationConfig: {\r\n                responseModalities: ['AUDIO'],\r\n                speechConfig: {\r\n                  \/\/ Charon = djup, stabil manlig r\u00f6st.\r\n                  \/\/ Alternativ om den inte passar: 'Orus' (best\u00e4md manlig)\r\n                  \/\/ eller 'Fenrir' (energisk manlig). R\u00f6sten l\u00e5ses per\r\n                  \/\/ session och kan inte bytas mitt i ett samtal.\r\n                  voiceConfig: { prebuiltVoiceConfig: { voiceName: 'Charon' } }\r\n                }\r\n              },\r\n              systemInstruction: {\r\n                parts: [{ text: SYSTEM_INSTRUCTION }]\r\n              },\r\n              \/\/ Servern transkriberar b\u00e5de anv\u00e4ndarens tal och Geminis\r\n              \/\/ svar och skickar texten l\u00f6pande i serverContent\r\n              inputAudioTranscription: {},\r\n              outputAudioTranscription: {}\r\n            }\r\n          }));\r\n        };\r\n\r\n        \/\/ Blob.text() \u00e4r asynkron \u2014 utan serialisering kan tv\u00e5 snabbt\r\n        \/\/ inkomna meddelanden processas i FEL ordning (den andra blir\r\n        \/\/ klar f\u00f6rst), vilket ger hackigt\/omkastat ljud och trasiga\r\n        \/\/ transkript. Promise-kedjan garanterar FIFO-ordning.\r\n        let msgChain = Promise.resolve();\r\n        myWs.onmessage = (event) => {\r\n          if (myGen !== sessionGen) return; \/\/ meddelande fr\u00e5n gammal session\r\n          msgChain = msgChain.then(async () => {\r\n            let text;\r\n            if (event.data instanceof Blob) {\r\n              text = await event.data.text();\r\n              if (myGen !== sessionGen) return; \/\/ stop() hann f\u00f6re\r\n            } else {\r\n              text = event.data;\r\n            }\r\n            try {\r\n              handleServerMessage(JSON.parse(text));\r\n            } catch (e) {\r\n              console.error('Kunde inte tolka servermeddelande:', e);\r\n            }\r\n          }).catch((e) => {\r\n            console.error('Meddelandehantering misslyckades:', e);\r\n          });\r\n        };\r\n\r\n        myWs.onerror = (err) => {\r\n          console.error('WebSocket-fel:', err);\r\n          if (myGen === sessionGen) setState('error');\r\n        };\r\n\r\n        myWs.onclose = (event) => {\r\n          if (myGen !== sessionGen) return; \/\/ gammal session \u2014 redan st\u00e4dad\r\n          \/\/ 1008 = policy (t.ex. ogiltig nyckel eller modell ej tillg\u00e4nglig)\r\n          if (event.code !== 1000) {\r\n            console.warn('WebSocket st\u00e4ngd:', event.code, event.reason);\r\n            setState('error');\r\n          }\r\n          \/\/ Beh\u00e5ll felstatus synlig \u2014 skriv inte \u00f6ver med 'idle'\r\n          stop(state === 'error' ? 'error' : 'idle');\r\n        };\r\n      }\r\n\r\n      function stop(finalState = 'idle') {\r\n        sessionGen++;      \/\/ avbryter en eventuellt p\u00e5g\u00e5ende start()\r\n        setupDone = false;\r\n        sessionStartMs = 0;\r\n        \/\/ N\u00e4sta session ska b\u00f6rja p\u00e5 en ny replik i transkriptet \u2014\r\n        \/\/ annars sys tv\u00e5 sessioners repliker ihop till en och\r\n        \/\/ tidsst\u00e4mpeln blir missvisande\r\n        lastSpeaker = null;\r\n        if (ws) {\r\n          const oldWs = ws;\r\n          ws = null;\r\n          oldWs.onclose = null; \/\/ f\u00f6rhindra att onclose anropar stop igen\r\n          try { oldWs.close(1000); } catch (e) {}\r\n        }\r\n        releaseAudio();\r\n        setState(finalState);\r\n      }\r\n\r\n      function getAudioData() {\r\n        if (analyser) {\r\n          analyser.getByteFrequencyData(analyserData);\r\n\r\n          const bassRange = analyserData.slice(0, 4);\r\n          const midRange = analyserData.slice(4, 16);\r\n          const highRange = analyserData.slice(16, 32);\r\n\r\n          const avg = arr => arr.reduce((s, v) => s + v, 0) \/ arr.length \/ 255;\r\n\r\n          return {\r\n            bass: avg(bassRange),\r\n            mid: avg(midRange),\r\n            high: avg(highRange),\r\n            overall: avg(analyserData)\r\n          };\r\n        }\r\n        return { bass: 0, mid: 0, high: 0, overall: 0 };\r\n      }\r\n\r\n      return {\r\n        start,\r\n        stop,\r\n        getAudioData,\r\n        getState: () => state,\r\n        isActive: () => state === 'listening' || state === 'connecting',\r\n        getTranscript: () => transcript,\r\n        clearTranscript: () => { transcript = []; lastSpeaker = null; },\r\n        getElapsedSeconds: () =>\r\n          state === 'listening' && sessionStartMs\r\n            ? Math.floor((Date.now() - sessionStartMs) \/ 1000)\r\n            : 0\r\n      };\r\n    }\r\n\r\n    \/\/ ---------------------------------------------------------------------\r\n    \/\/ Cirkeltextur f\u00f6r partiklar\r\n    \/\/ ---------------------------------------------------------------------\r\n    function createCircleTexture() {\r\n      const canvas = document.createElement('canvas');\r\n      canvas.width = 64;\r\n      canvas.height = 64;\r\n      const context = canvas.getContext('2d');\r\n      context.beginPath();\r\n      context.arc(32, 32, 30, 0, Math.PI * 2);\r\n      context.closePath();\r\n      context.fillStyle = 'white';\r\n      context.fill();\r\n      return new THREE.CanvasTexture(canvas);\r\n    }\r\n\r\n    \/\/ ---------------------------------------------------------------------\r\n    \/\/ Energisf\u00e4ren\r\n    \/\/ ---------------------------------------------------------------------\r\n    function createEnergySphere(containerId) {\r\n      const container = document.getElementById(containerId);\r\n      const statusEl = document.getElementById('sphere-status');\r\n      const width = container.clientWidth;\r\n      const height = container.clientHeight;\r\n\r\n      const scene = new THREE.Scene();\r\n      const camera = new THREE.PerspectiveCamera(75, width \/ height, 0.1, 1000);\r\n      camera.position.z = 5;\r\n\r\n      \/\/ let (inte const): renderern kan beh\u00f6va byggas om ifall\r\n      \/\/ WebGL-kontexten f\u00f6rloras n\u00e4r canvasen flyttas mellan dokument\r\n      let renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\r\n      renderer.setSize(width, height);\r\n      renderer.setPixelRatio(window.devicePixelRatio);\r\n      renderer.setClearColor(0x000000, 0);\r\n      container.appendChild(renderer.domElement);\r\n\r\n      function rebuildRendererIfContextLost() {\r\n        let lost = true;\r\n        try {\r\n          const gl = renderer.getContext();\r\n          lost = !gl || gl.isContextLost();\r\n        } catch (e) { lost = true; }\r\n        if (!lost) return;\r\n\r\n        try {\r\n          if (renderer.domElement.parentNode) {\r\n            renderer.domElement.parentNode.removeChild(renderer.domElement);\r\n          }\r\n          renderer.dispose();\r\n        } catch (e) {}\r\n\r\n        renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\r\n        renderer.setPixelRatio(window.devicePixelRatio);\r\n        renderer.setClearColor(0x000000, 0);\r\n        container.appendChild(renderer.domElement);\r\n      }\r\n\r\n      const pointTexture = createCircleTexture();\r\n\r\n      const ambientLight = new THREE.AmbientLight(0x111111);\r\n      scene.add(ambientLight);\r\n\r\n      const mainColor = new THREE.Color(0x0E5287);\r\n      const colorPalette = [\r\n        mainColor,\r\n        new THREE.Color(0x1E6A9F),\r\n        new THREE.Color(0x4A8BC2)\r\n      ];\r\n\r\n      let ripples = [];\r\n\r\n      \/\/ Gemini-session\r\n      const statusText = {\r\n        idle: 'Klicka p\u00e5 sf\u00e4ren f\u00f6r att prata med RCH FULLSTACK',\r\n        connecting: 'Ansluter...',\r\n        listening: 'Lyssnar \u2014 prata! (klicka igen f\u00f6r att avsluta)',\r\n        error: 'Fel: mikrofon eller anslutning misslyckades. Klicka f\u00f6r att f\u00f6rs\u00f6ka igen.'\r\n      };\r\n\r\n      \/\/ Flagga ist\u00e4llet f\u00f6r att matcha p\u00e5 statustextens inneh\u00e5ll \u2014\r\n      \/\/ str\u00e4ngmatchning g\u00e5r s\u00f6nder tyst om texten n\u00e5gonsin \u00e4ndras\r\n      let goAwayActive = false;\r\n\r\n      const gemini = createGeminiVoiceSession({\r\n        onStateChange: (s) => {\r\n          goAwayActive = false; \/\/ varje tillst\u00e5ndsbyte rensar varningen\r\n          if (statusEl) statusEl.textContent = statusText[s] || '';\r\n        },\r\n        onGoAway: (timeLeft) => {\r\n          \/\/ Servern st\u00e4nger snart (~10 min-gr\u00e4nsen per anslutning).\r\n          \/\/ Transkriptet \u00f6verlever \u2014 anv\u00e4ndaren klickar bara igen.\r\n          goAwayActive = true;\r\n          if (statusEl) {\r\n            statusEl.textContent =\r\n              'Anslutningen n\u00e5r sin tidsgr\u00e4ns och st\u00e4ngs strax \u2014 ' +\r\n              'klicka p\u00e5 sf\u00e4ren f\u00f6r att forts\u00e4tta samtalet';\r\n          }\r\n        }\r\n      });\r\n\r\n      \/\/ Samtalstimer: visar hur l\u00e4nge sessionen p\u00e5g\u00e5tt, uppdateras\r\n      \/\/ varje sekund. Max ~10 min per anslutning (serverns gr\u00e4ns).\r\n      function formatElapsed(totalSec) {\r\n        const m = Math.floor(totalSec \/ 60);\r\n        const s = totalSec % 60;\r\n        return String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');\r\n      }\r\n\r\n      const timerInterval = setInterval(() => {\r\n        if (!statusEl) return;\r\n        if (gemini.getState() !== 'listening') return;\r\n        if (goAwayActive) return; \/\/ skriv inte \u00f6ver varningen\r\n        const sec = gemini.getElapsedSeconds();\r\n        statusEl.textContent =\r\n          'Lyssnar \u2014 prata! (' + formatElapsed(sec) +\r\n          ') \u00b7 klicka igen f\u00f6r att avsluta';\r\n      }, 1000);\r\n\r\n      \/\/ K\u00e4rnsf\u00e4r\r\n      const sphereGeometry = new THREE.SphereGeometry(1.8, 64, 64);\r\n      const sphereMaterial = new THREE.MeshPhongMaterial({\r\n        color: 0x0E5287,\r\n        emissive: 0x051E33,\r\n        transparent: true,\r\n        opacity: 0.4\r\n      });\r\n      const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);\r\n      scene.add(sphere);\r\n\r\n      \/\/ Ytpartiklar\r\n      const surfaceParticleCount = 6000;\r\n      const surfaceGeometry = new THREE.BufferGeometry();\r\n      const surfacePositions = new Float32Array(surfaceParticleCount * 3);\r\n      const surfaceColors = new Float32Array(surfaceParticleCount * 3);\r\n      const originalPositions = new Float32Array(surfaceParticleCount * 3);\r\n\r\n      const SURFACE_RADIUS = 1.95;\r\n\r\n      for (let i = 0; i < surfaceParticleCount; i++) {\r\n        const phi = Math.acos(-1 + (2 * i) \/ surfaceParticleCount);\r\n        const theta = Math.sqrt(surfaceParticleCount * Math.PI) * phi;\r\n\r\n        const x = SURFACE_RADIUS * Math.sin(phi) * Math.cos(theta);\r\n        const y = SURFACE_RADIUS * Math.sin(phi) * Math.sin(theta);\r\n        const z = SURFACE_RADIUS * Math.cos(phi);\r\n\r\n        surfacePositions[i * 3] = x;\r\n        surfacePositions[i * 3 + 1] = y;\r\n        surfacePositions[i * 3 + 2] = z;\r\n\r\n        originalPositions[i * 3] = x;\r\n        originalPositions[i * 3 + 1] = y;\r\n        originalPositions[i * 3 + 2] = z;\r\n\r\n        let colorIndex;\r\n        if (y > 0.5) colorIndex = 2;\r\n        else if (x < 0) colorIndex = 1;\r\n        else colorIndex = 0;\r\n\r\n        const color = colorPalette[colorIndex];\r\n        surfaceColors[i * 3] = color.r;\r\n        surfaceColors[i * 3 + 1] = color.g;\r\n        surfaceColors[i * 3 + 2] = color.b;\r\n      }\r\n\r\n      surfaceGeometry.setAttribute('position', new THREE.BufferAttribute(surfacePositions, 3));\r\n      surfaceGeometry.setAttribute('color', new THREE.BufferAttribute(surfaceColors, 3));\r\n\r\n      const surfaceMaterial = new THREE.PointsMaterial({\r\n        size: 0.1,\r\n        map: pointTexture,\r\n        blending: THREE.AdditiveBlending,\r\n        depthWrite: false,\r\n        transparent: true,\r\n        vertexColors: true\r\n      });\r\n\r\n      const surfaceParticles = new THREE.Points(surfaceGeometry, surfaceMaterial);\r\n      scene.add(surfaceParticles);\r\n\r\n      \/\/ Gl\u00f6dlager\r\n      const glowLayers = [];\r\n      colorPalette.forEach((color, index) => {\r\n        const glowGeometry = new THREE.SphereGeometry(2.05 + index * 0.05, 32, 32);\r\n        const glowMaterial = new THREE.MeshBasicMaterial({\r\n          color: color,\r\n          transparent: true,\r\n          opacity: 0.08,\r\n          side: THREE.BackSide\r\n        });\r\n        const glowMesh = new THREE.Mesh(glowGeometry, glowMaterial);\r\n        scene.add(glowMesh);\r\n        glowLayers.push(glowMesh);\r\n      });\r\n\r\n      \/\/ Inre partiklar\r\n      const innerParticleCount = 800;\r\n      const innerGeometry = new THREE.BufferGeometry();\r\n      const innerPositions = new Float32Array(innerParticleCount * 3);\r\n      const innerColors = new Float32Array(innerParticleCount * 3);\r\n\r\n      for (let i = 0; i < innerParticleCount; i++) {\r\n        const radius = Math.random() * 1.6;\r\n        const theta = Math.random() * Math.PI * 2;\r\n        const phi = Math.random() * Math.PI;\r\n\r\n        innerPositions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);\r\n        innerPositions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);\r\n        innerPositions[i * 3 + 2] = radius * Math.cos(phi);\r\n\r\n        const color = colorPalette[Math.floor(Math.random() * colorPalette.length)];\r\n        innerColors[i * 3] = color.r;\r\n        innerColors[i * 3 + 1] = color.g;\r\n        innerColors[i * 3 + 2] = color.b;\r\n      }\r\n\r\n      innerGeometry.setAttribute('position', new THREE.BufferAttribute(innerPositions, 3));\r\n      innerGeometry.setAttribute('color', new THREE.BufferAttribute(innerColors, 3));\r\n\r\n      const innerMaterial = new THREE.PointsMaterial({\r\n        size: 0.08,\r\n        map: pointTexture,\r\n        blending: THREE.AdditiveBlending,\r\n        transparent: true,\r\n        vertexColors: true\r\n      });\r\n\r\n      const innerParticles = new THREE.Points(innerGeometry, innerMaterial);\r\n      scene.add(innerParticles);\r\n\r\n      \/\/ Klick: toggla Gemini-session + skapa ripple\r\n      const raycaster = new THREE.Raycaster();\r\n      const mouse = new THREE.Vector2();\r\n\r\n      function onMouseClick(event) {\r\n        const rect = container.getBoundingClientRect();\r\n        mouse.x = ((event.clientX - rect.left) \/ rect.width) * 2 - 1;\r\n        mouse.y = -((event.clientY - rect.top) \/ rect.height) * 2 + 1;\r\n\r\n        raycaster.setFromCamera(mouse, camera);\r\n        const intersects = raycaster.intersectObject(sphere);\r\n\r\n        if (intersects.length > 0) {\r\n          if (gemini.isActive()) {\r\n            gemini.stop();\r\n          } else {\r\n            gemini.start();\r\n          }\r\n\r\n          const clickPoint = intersects[0].point.clone()\r\n            .normalize()\r\n            .multiplyScalar(SURFACE_RADIUS);\r\n\r\n          ripples.push({\r\n            center: clickPoint,\r\n            radius: 0,\r\n            intensity: 1.0,\r\n            startTime: Date.now()\r\n          });\r\n        }\r\n      }\r\n\r\n      container.addEventListener('click', onMouseClick);\r\n\r\n      function updateWaterRipples() {\r\n        const currentTime = Date.now();\r\n        const positions = surfaceGeometry.attributes.position.array;\r\n\r\n        positions.set(originalPositions);\r\n\r\n        ripples = ripples.filter(ripple => {\r\n          const elapsed = (currentTime - ripple.startTime) \/ 1000.0;\r\n          const maxTime = 3.0;\r\n\r\n          if (elapsed > maxTime) return false;\r\n\r\n          ripple.radius = elapsed * 2.0;\r\n          ripple.intensity = Math.max(0, 1.0 - elapsed \/ maxTime);\r\n\r\n          const cx = ripple.center.x;\r\n          const cy = ripple.center.y;\r\n          const cz = ripple.center.z;\r\n\r\n          for (let i = 0; i < surfaceParticleCount; i++) {\r\n            const px = originalPositions[i * 3];\r\n            const py = originalPositions[i * 3 + 1];\r\n            const pz = originalPositions[i * 3 + 2];\r\n\r\n            const dx = px - cx, dy = py - cy, dz = pz - cz;\r\n            const distanceFromCenter = Math.sqrt(dx * dx + dy * dy + dz * dz);\r\n\r\n            if (distanceFromCenter < ripple.radius + 0.5 && distanceFromCenter > ripple.radius - 0.5) {\r\n              const waveHeight = ripple.intensity * 0.2 * Math.sin((distanceFromCenter - ripple.radius) * 10);\r\n              const invLen = 1 \/ SURFACE_RADIUS;\r\n              positions[i * 3] += px * invLen * waveHeight;\r\n              positions[i * 3 + 1] += py * invLen * waveHeight;\r\n              positions[i * 3 + 2] += pz * invLen * waveHeight;\r\n            }\r\n          }\r\n\r\n          return true;\r\n        });\r\n\r\n        surfaceGeometry.attributes.position.needsUpdate = true;\r\n      }\r\n\r\n      \/\/ Animation\r\n      let time = 0;\r\n      let animationFrameId = null;\r\n      let cameraAngle = 0;\r\n      let cameraYAngle = 0;\r\n      let innerRotY = 0;\r\n      let innerRotX = 0;\r\n      let lastFrameTime = performance.now();\r\n\r\n      \/\/ F\u00f6nstret som driver requestAnimationFrame. Viktigt f\u00f6r PiP:\r\n      \/\/ n\u00e4r huvudfliken \u00e4r dold stryps dess rAF, s\u00e5 animationsloopen\r\n      \/\/ m\u00e5ste k\u00f6ras i PiP-f\u00f6nstret n\u00e4r sf\u00e4ren bor d\u00e4r.\r\n      let hostWindow = window;\r\n\r\n      function resizeToContainer() {\r\n        const w = container.clientWidth;\r\n        const h = container.clientHeight;\r\n        if (w === 0 || h === 0) return;\r\n        camera.aspect = w \/ h;\r\n        camera.updateProjectionMatrix();\r\n        renderer.setSize(w, h);\r\n      }\r\n\r\n      \/\/ ResizeObserver f\u00f6ljer containern oavsett vilket dokument den bor i\r\n      const resizeObserver = new ResizeObserver(resizeToContainer);\r\n      resizeObserver.observe(container);\r\n\r\n      \/\/ --------------------- Helsk\u00e4rmsl\u00e4ge ---------------------\r\n      \/\/ Hybrid: CSS-l\u00e4get (position:fixed \u00f6ver allt) fungerar \u00f6verallt,\r\n      \/\/ inklusive iPhone d\u00e4r Fullscreen API inte st\u00f6djs f\u00f6r element.\r\n      \/\/ D\u00e4r native fullscreen finns anv\u00e4nds det ovanp\u00e5 \u2014 det d\u00f6ljer\r\n      \/\/ adressf\u00e4lt och systemf\u00e4lt p\u00e5 Android\/desktop.\r\n      const fsBtn = document.getElementById('fullscreen-toggle');\r\n      let inFullscreen = false;\r\n\r\n      function nativeFsElement() {\r\n        return document.fullscreenElement || document.webkitFullscreenElement || null;\r\n      }\r\n\r\n      \/\/ position:fixed positioneras relativt n\u00e4rmaste f\u00f6r\u00e4lder med\r\n      \/\/ transform\/filter\/perspective \u2014 vanligt i WordPress-teman\r\n      \/\/ (animerade sektioner). D\u00e5 t\u00e4cker \"helsk\u00e4rmen\" bara sektionen.\r\n      \/\/ L\u00f6sning: flytta containern till <body> under helsk\u00e4rmsl\u00e4get\r\n      \/\/ och tillbaka till exakt samma plats (via platsh\u00e5llare) efter\u00e5t.\r\n      let fsPlaceholder = null;\r\n\r\n      function enterFullscreen() {\r\n        inFullscreen = true;\r\n\r\n        if (container.parentElement !== document.body) {\r\n          fsPlaceholder = document.createComment('sphere-fs-placeholder');\r\n          container.parentNode.insertBefore(fsPlaceholder, container);\r\n          document.body.appendChild(container);\r\n        }\r\n\r\n        container.classList.add('fs-active');\r\n        if (fsBtn) fsBtn.textContent = 'St\u00e4ng \u2715';\r\n\r\n        const req = container.requestFullscreen || container.webkitRequestFullscreen;\r\n        if (req) {\r\n          try {\r\n            const p = req.call(container);\r\n            if (p && p.catch) p.catch(() => {}); \/\/ CSS-l\u00e4get r\u00e4cker som fallback\r\n          } catch (e) {}\r\n        }\r\n        resizeToContainer();\r\n      }\r\n\r\n      function exitFullscreen() {\r\n        inFullscreen = false;\r\n        container.classList.remove('fs-active');\r\n        if (fsBtn) fsBtn.textContent = 'Helsk\u00e4rm \u26f6';\r\n\r\n        \/\/ Flytta tillbaka containern dit den stod\r\n        if (fsPlaceholder && fsPlaceholder.parentNode) {\r\n          fsPlaceholder.parentNode.insertBefore(container, fsPlaceholder);\r\n          fsPlaceholder.remove();\r\n        }\r\n        fsPlaceholder = null;\r\n\r\n        if (nativeFsElement()) {\r\n          const exit = document.exitFullscreen || document.webkitExitFullscreen;\r\n          try {\r\n            const p = exit.call(document);\r\n            if (p && p.catch) p.catch(() => {});\r\n          } catch (e) {}\r\n        }\r\n        resizeToContainer();\r\n      }\r\n\r\n      function onFsBtnClick(e) {\r\n        e.stopPropagation(); \/\/ klick p\u00e5 knappen ska inte tr\u00e4ffa sf\u00e4ren\r\n        if (inFullscreen) exitFullscreen();\r\n        else enterFullscreen();\r\n      }\r\n      if (fsBtn) fsBtn.addEventListener('click', onFsBtnClick);\r\n\r\n      \/\/ Synka n\u00e4r anv\u00e4ndaren l\u00e4mnar native fullscreen via Esc,\r\n      \/\/ tillbaka-gest eller systemknapp \u2014 d\u00e5 ska CSS-l\u00e4get ocks\u00e5 st\u00e4ngas\r\n      function onNativeFsChange() {\r\n        if (!nativeFsElement() && inFullscreen) exitFullscreen();\r\n      }\r\n      document.addEventListener('fullscreenchange', onNativeFsChange);\r\n      document.addEventListener('webkitfullscreenchange', onNativeFsChange);\r\n      \/\/ ----------------------------------------------------------\r\n\r\n      \/\/ --------------------- Spara transkript ---------------------\r\n      const saveBtn = document.getElementById('save-transcript');\r\n\r\n      function downloadTranscript() {\r\n        const entries = gemini.getTranscript();\r\n        if (entries.length === 0) {\r\n          if (statusEl) {\r\n            const prev = statusEl.textContent;\r\n            const tempMsg = 'Inget samtal att spara \u00e4nnu';\r\n            statusEl.textContent = tempMsg;\r\n            \/\/ \u00c5terst\u00e4ll bara om texten inte hunnit \u00e4ndras av n\u00e5got\r\n            \/\/ annat (t.ex. att anv\u00e4ndaren startat en session) \u2014 annars\r\n            \/\/ skriver vi \u00f6ver en f\u00e4rskare status med en gammal\r\n            setTimeout(() => {\r\n              if (statusEl.textContent === tempMsg) statusEl.textContent = prev;\r\n            }, 2500);\r\n          }\r\n          return;\r\n        }\r\n\r\n        const now = new Date();\r\n        const header =\r\n          'Samtal med RCH FULLSTACK\\n' +\r\n          now.toLocaleString('sv-SE') + '\\n' +\r\n          '='.repeat(40) + '\\n\\n';\r\n\r\n        const body = entries.map(e => {\r\n          const who = e.who === 'user' ? 'Du' : 'RCH FULLSTACK';\r\n          return '[' + e.time + '] ' + who + ':\\n' + e.text.trim();\r\n        }).join('\\n\\n');\r\n\r\n        const blob = new Blob([header + body + '\\n'], {\r\n          type: 'text\/plain;charset=utf-8'\r\n        });\r\n        const url = URL.createObjectURL(blob);\r\n        const a = document.createElement('a');\r\n        a.href = url;\r\n        const stamp = now.getFullYear() + '-' +\r\n          String(now.getMonth() + 1).padStart(2, '0') + '-' +\r\n          String(now.getDate()).padStart(2, '0') + '_' +\r\n          String(now.getHours()).padStart(2, '0') + '.' +\r\n          String(now.getMinutes()).padStart(2, '0');\r\n        a.download = 'samtal-rch-fullstack-' + stamp + '.txt';\r\n        document.body.appendChild(a);\r\n        a.click();\r\n        document.body.removeChild(a);\r\n        \/\/ F\u00f6rdr\u00f6j revoke \u2014 vissa mobilwebbl\u00e4sare hinner annars inte\r\n        \/\/ starta nedladdningen innan URL:en d\u00f6r\r\n        setTimeout(() => URL.revokeObjectURL(url), 10000);\r\n      }\r\n\r\n      function onSaveBtnClick(e) {\r\n        e.stopPropagation(); \/\/ klick p\u00e5 knappen ska inte tr\u00e4ffa sf\u00e4ren\r\n        downloadTranscript();\r\n      }\r\n      if (saveBtn) saveBtn.addEventListener('click', onSaveBtnClick);\r\n      \/\/ --------------------------------------------------------------\r\n\r\n      \/\/ ---------------- Document Picture-in-Picture ----------------\r\n      const pipBtn = document.getElementById('pip-toggle');\r\n      let pipWindow = null;\r\n      let inPip = false;\r\n      let pipPollTimer = null;\r\n      const originalParent = container.parentNode;\r\n\r\n      \/\/ Flyttar tillbaka sf\u00e4ren till sidan och startar om animationsloopen.\r\n      \/\/ Idempotent: s\u00e4ker att anropa fr\u00e5n b\u00e5de pagehide och poll-fallbacken.\r\n      function restoreFromPip() {\r\n        if (!inPip) return;\r\n        inPip = false;\r\n\r\n        if (pipPollTimer) {\r\n          clearInterval(pipPollTimer);\r\n          pipPollTimer = null;\r\n        }\r\n\r\n        \/\/ Flytta tillbaka containern om den fortfarande bor i PiP-dokumentet.\r\n        \/\/ Vi h\u00e5ller en JS-referens till elementet, s\u00e5 det \u00f6verlever \u00e4ven om\r\n        \/\/ PiP-dokumentet redan hunnit f\u00f6rst\u00f6ras.\r\n        if (container.ownerDocument !== document) {\r\n          originalParent.append(container);\r\n        }\r\n\r\n        pipWindow = null;\r\n        hostWindow = window;\r\n        pipBtn.textContent = 'Flytande l\u00e4ge \u29c9';\r\n\r\n        \/\/ WebGL-kontexten kan g\u00e5 f\u00f6rlorad n\u00e4r canvasen flyttas mellan\r\n        \/\/ dokument (s\u00e4rskilt om PiP-dokumentet f\u00f6rst\u00f6rdes f\u00f6rst).\r\n        rebuildRendererIfContextLost();\r\n        resizeToContainer();\r\n\r\n        \/\/ KRITISKT: den v\u00e4ntande rAF-callbacken schemalades i PiP-f\u00f6nstret\r\n        \/\/ och dog med det. Utan omstart h\u00e4r fryser\/f\u00f6rsvinner sf\u00e4ren.\r\n        startLoop();\r\n      }\r\n\r\n      async function onPipBtnClick(e) {\r\n        e.stopPropagation(); \/\/ klick p\u00e5 knappen ska inte tr\u00e4ffa sf\u00e4ren\r\n        if (pipWindow) {\r\n          pipWindow.close(); \/\/ triggar pagehide \u2192 restoreFromPip\r\n          return;\r\n        }\r\n        try {\r\n            \/\/ Helsk\u00e4rm och PiP g\u00e5r inte att kombinera \u2014 l\u00e4mna helsk\u00e4rm f\u00f6rst\r\n            if (inFullscreen) exitFullscreen();\r\n\r\n            pipWindow = await documentPictureInPicture.requestWindow({\r\n              width: 340,\r\n              height: 360\r\n            });\r\n            inPip = true;\r\n\r\n            \/\/ Kopiera sidans stilar till PiP-dokumentet\r\n            [...document.styleSheets].forEach((sheet) => {\r\n              try {\r\n                const css = [...sheet.cssRules].map(r => r.cssText).join('');\r\n                const style = pipWindow.document.createElement('style');\r\n                style.textContent = css;\r\n                pipWindow.document.head.appendChild(style);\r\n              } catch (err) {\r\n                \/\/ Cross-origin-stylesheets kan inte l\u00e4sas \u2014 l\u00e4nka ist\u00e4llet\r\n                if (sheet.href) {\r\n                  const link = pipWindow.document.createElement('link');\r\n                  link.rel = 'stylesheet';\r\n                  link.href = sheet.href;\r\n                  pipWindow.document.head.appendChild(link);\r\n                }\r\n              }\r\n            });\r\n\r\n            pipWindow.document.documentElement.style.overflow = 'hidden';\r\n            pipWindow.document.documentElement.style.height = '100%';\r\n            pipWindow.document.body.classList.add('pip-mode');\r\n            pipWindow.document.body.append(container);\r\n            pipBtn.textContent = 'Tillbaka till sidan';\r\n\r\n            \/\/ K\u00f6r animationsloopen i PiP-f\u00f6nstret (annars stryps den\r\n            \/\/ n\u00e4r huvudfliken \u00e4r dold)\r\n            hostWindow = pipWindow;\r\n            rebuildRendererIfContextLost();\r\n            resizeToContainer();\r\n            startLoop();\r\n\r\n            \/\/ Prim\u00e4r v\u00e4g tillbaka: pagehide n\u00e4r PiP-f\u00f6nstret st\u00e4ngs\r\n            pipWindow.addEventListener('pagehide', restoreFromPip);\r\n\r\n            \/\/ Fallback: pagehide \u00e4r inte p\u00e5litligt i alla webbl\u00e4sare\/l\u00e4gen,\r\n            \/\/ s\u00e5 polla \u00e4ven f\u00f6nstrets closed-status\r\n            pipPollTimer = setInterval(() => {\r\n              if (!pipWindow || pipWindow.closed) restoreFromPip();\r\n            }, 400);\r\n          } catch (err) {\r\n            console.error('Kunde inte \u00f6ppna PiP-f\u00f6nster:', err);\r\n            pipWindow = null;\r\n            inPip = false;\r\n          }\r\n      }\r\n\r\n      if ('documentPictureInPicture' in window && pipBtn) {\r\n        pipBtn.hidden = false;\r\n        pipBtn.addEventListener('click', onPipBtnClick);\r\n      }\r\n      \/\/ --------------------------------------------------------------\r\n\r\n      \/\/ Token-baserad animationsloop: varje startLoop() ogiltigf\u00f6rklarar\r\n      \/\/ tidigare loopar, s\u00e5 vi aldrig f\u00e5r dubbla loopar eller d\u00f6da kedjor\r\n      \/\/ n\u00e4r rAF-v\u00e4rden byts mellan huvudf\u00f6nster och PiP-f\u00f6nster.\r\n      let loopToken = 0;\r\n\r\n      function startLoop() {\r\n        loopToken++;\r\n        const myToken = loopToken;\r\n        lastFrameTime = performance.now();\r\n\r\n        function frame() {\r\n          if (myToken !== loopToken) return; \/\/ en nyare loop har tagit \u00f6ver\r\n          try {\r\n            animationFrameId = hostWindow.requestAnimationFrame(frame);\r\n          } catch (e) {\r\n            return; \/\/ hostWindow \u00e4r st\u00e4ngt; en omstart tar \u00f6ver\r\n          }\r\n          animate();\r\n        }\r\n        frame();\r\n      }\r\n\r\n      function animate() {\r\n\r\n        const now = performance.now();\r\n        const dt = Math.min((now - lastFrameTime) \/ 1000, 0.1);\r\n        lastFrameTime = now;\r\n\r\n        \/\/ dt-baserad \u2014 med fast +0.01\/frame gick pulsen dubbelt s\u00e5\r\n        \/\/ fort p\u00e5 120 Hz-sk\u00e4rmar (vanligt p\u00e5 moderna mobiler).\r\n        \/\/ 0.6\/s motsvarar den gamla hastigheten vid 60 fps.\r\n        time += dt * 0.6;\r\n\r\n        \/\/ Ljuddata fr\u00e5n Geminis r\u00f6st driver sf\u00e4ren\r\n        const audioData = gemini.getAudioData();\r\n        const speaking = gemini.isActive() && audioData.overall > 0.05;\r\n\r\n        updateWaterRipples();\r\n\r\n        let pulseScale = 1.0 + Math.sin(time * 2) * 0.05;\r\n        let rotationSpeed = 0.1;\r\n        let cameraMovement = 0.1;\r\n\r\n        if (speaking) {\r\n          pulseScale = 1.0 + audioData.overall * 0.3 + Math.sin(time * 2) * 0.05;\r\n          rotationSpeed = 0.1 + audioData.mid * 0.5;\r\n          cameraMovement = 0.1 + audioData.bass * 0.3;\r\n\r\n          surfaceMaterial.size = 0.1 + audioData.high * 0.15;\r\n\r\n          glowLayers.forEach((layer, index) => {\r\n            const frequency = index === 0 ? audioData.bass :\r\n                             index === 1 ? audioData.mid : audioData.high;\r\n            layer.material.opacity = 0.08 + frequency * 0.2;\r\n          });\r\n\r\n          const colorIntensity = 1.0 + audioData.overall * 0.5;\r\n          sphere.material.emissive.setRGB(\r\n            0.05 * colorIntensity,\r\n            0.1 * colorIntensity,\r\n            0.2 * colorIntensity\r\n          );\r\n        } else if (gemini.getState() === 'listening') {\r\n          \/\/ Lyssnar-l\u00e4ge: lugn, tydligare gl\u00f6d s\u00e5 man ser att sessionen \u00e4r ig\u00e5ng\r\n          surfaceMaterial.size = 0.1;\r\n          glowLayers.forEach(layer => {\r\n            layer.material.opacity = 0.12 + Math.sin(time * 3) * 0.04;\r\n          });\r\n          sphere.material.emissive.setRGB(0.06, 0.13, 0.24);\r\n        } else {\r\n          surfaceMaterial.size = 0.1;\r\n          glowLayers.forEach(layer => {\r\n            layer.material.opacity = 0.08;\r\n          });\r\n          sphere.material.emissive.setRGB(0.05, 0.1, 0.2);\r\n        }\r\n\r\n        sphere.scale.set(pulseScale, pulseScale, pulseScale);\r\n\r\n        innerRotY += rotationSpeed * dt;\r\n        innerRotX += rotationSpeed * 0.5 * dt;\r\n        innerParticles.rotation.y = innerRotY;\r\n        innerParticles.rotation.x = innerRotX;\r\n\r\n        cameraAngle += cameraMovement * dt;\r\n        cameraYAngle += cameraMovement * 1.5 * dt;\r\n        camera.position.x = Math.sin(cameraAngle) * 5;\r\n        camera.position.z = Math.cos(cameraAngle) * 5;\r\n        camera.position.y = Math.sin(cameraYAngle) * 1;\r\n        camera.lookAt(scene.position);\r\n\r\n        \/\/ Kontexten kan f\u00f6rloras \u00e4ven under drift (GPU-reset, flikbyte\r\n        \/\/ med PiP) \u2014 inte bara vid dokumentflytt. Bygg om och forts\u00e4tt.\r\n        try {\r\n          renderer.render(scene, camera);\r\n        } catch (e) {\r\n          rebuildRendererIfContextLost();\r\n          resizeToContainer();\r\n        }\r\n      }\r\n\r\n      startLoop();\r\n\r\n      return function cleanup() {\r\n        loopToken++; \/\/ ogiltigf\u00f6rklarar den aktiva loopen\r\n        try {\r\n          if (animationFrameId) hostWindow.cancelAnimationFrame(animationFrameId);\r\n        } catch (e) {}\r\n        if (pipPollTimer) clearInterval(pipPollTimer);\r\n        clearInterval(timerInterval);\r\n\r\n        resizeObserver.disconnect();\r\n        \/\/ inPip = false F\u00d6RE close(): annars triggar close \u2192 pagehide \u2192\r\n        \/\/ restoreFromPip \u2192 startLoop(), som \u00e5terupplivar loopen p\u00e5 en\r\n        \/\/ scen som just h\u00e5ller p\u00e5 att rivas\r\n        inPip = false;\r\n        if (pipWindow) {\r\n          try { pipWindow.close(); } catch (e) {}\r\n          pipWindow = null;\r\n        }\r\n        container.removeEventListener('click', onMouseClick);\r\n        \/\/ Knapplyssnare m\u00e5ste bort \u2014 vid SPA-\/sid\u00f6verg\u00e5ngar d\u00e4r\r\n        \/\/ createEnergySphere k\u00f6rs igen p\u00e5 samma DOM dubbleras de annars\r\n        \/\/ (dubbla nedladdningar, dubbla PiP-f\u00f6rs\u00f6k)\r\n        if (fsBtn) fsBtn.removeEventListener('click', onFsBtnClick);\r\n        if (saveBtn) saveBtn.removeEventListener('click', onSaveBtnClick);\r\n        if (pipBtn) pipBtn.removeEventListener('click', onPipBtnClick);\r\n        document.removeEventListener('fullscreenchange', onNativeFsChange);\r\n        document.removeEventListener('webkitfullscreenchange', onNativeFsChange);\r\n        if (inFullscreen) exitFullscreen();\r\n\r\n        gemini.stop();\r\n\r\n        if (container.contains(renderer.domElement)) {\r\n          container.removeChild(renderer.domElement);\r\n        }\r\n\r\n        pointTexture.dispose();\r\n        sphereGeometry.dispose();\r\n        sphereMaterial.dispose();\r\n        surfaceGeometry.dispose();\r\n        surfaceMaterial.dispose();\r\n        innerGeometry.dispose();\r\n        innerMaterial.dispose();\r\n\r\n        glowLayers.forEach(layer => {\r\n          layer.geometry.dispose();\r\n          layer.material.dispose();\r\n          scene.remove(layer);\r\n        });\r\n\r\n        scene.remove(sphere);\r\n        scene.remove(surfaceParticles);\r\n        scene.remove(innerParticles);\r\n\r\n        renderer.dispose();\r\n      };\r\n    }\r\n\r\n    function init() {\r\n      try {\r\n        const container = document.getElementById('energy-sphere-container');\r\n        if (container) {\r\n          const cleanup = createEnergySphere('energy-sphere-container');\r\n          window.addEventListener('pagehide', cleanup);\r\n        }\r\n      } catch (error) {\r\n        console.error(\"Initialization error:\", error);\r\n      }\r\n    }\r\n\r\n    if (document.readyState === 'loading') {\r\n      document.addEventListener('DOMContentLoaded', init);\r\n    } else {\r\n      init();\r\n    }\r\n  <\/script>\r\n<\/body>\r\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-297d9f33 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"297d9f33\" data-element_type=\"section\" data-e-type=\"section\" id=\"kontakt\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t\t<div class=\"elementor-background-overlay\"><\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-top-column elementor-element elementor-element-6f09ceab\" data-id=\"6f09ceab\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-37dbd601 elementor-widget elementor-widget-heading\" data-id=\"37dbd601\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">Ta kontakt!<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3956f2b1 elementor-widget elementor-widget-heading\" data-id=\"3956f2b1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">Ber\u00e4tta om ditt problem  <br>\ns\u00e5 hittar vi l\u00f6sningen.<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-top-column elementor-element elementor-element-5527b29\" data-id=\"5527b29\" data-element_type=\"column\" data-e-type=\"column\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-540bf4f8 elementor-widget elementor-widget-heading\" data-id=\"540bf4f8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h4 class=\"elementor-heading-title elementor-size-default\">Skicka g\u00e4rna ett meddelande<\/h4>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2acc3fa5 elementor-widget elementor-widget-text-editor\" data-id=\"2acc3fa5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\r\n<style type=\"text\/css\">\r\n\r\n\r\n\r\nbody #wpforms-6 {\r\n\t\tborder-color:#ffffff !important;\r\n\t\t\t}\r\n\r\n\t\r\n\r\n\r\n\r\n\r\n\tbody #wpforms-6 .wpforms-submit-container .wpforms-submit, body #wpforms-6 .wpforms-field-pagebreak button.wpforms-page-button {\r\n\t\tbackground-color:#889407 !important;\t\tborder-width: 0px;\t}\r\n\r\n\tbody #wpforms-6 .wpforms-submit-container .wpforms-submit:hover, body #wpforms-6 .wpforms-field-pagebreak button.wpforms-page-button:hover {\r\n\tbackground-color:#0e5287;\t\t}\r\n\r\n\tbody #wpforms-6 .wpforms-submit-container,\r\n\tbody #wpforms-6 .wpforms-field-pagebreak .wpforms-pagebreak-left{\r\n\t\t}\r\n\r\n\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field input[type=text],\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field input[type=email],\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field input[type=tel],\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field input[type=url],\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field input[type=password],\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field input[type=number]\r\n\t{\r\n\t\tborder-width:2px !important;border-color:#889407 !important;border-style:solid !important;\t\t\t}\r\n\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field .wpforms-field-row\r\n\t{\r\n\t\t\t}\r\n\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field-layout.wpforms-field input[type=text],\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field-layout.wpforms-field input[type=email],\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field-layout.wpforms-field input[type=tel],\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field-layout.wpforms-field input[type=url],\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field-layout.wpforms-field input[type=password],\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field-layout.wpforms-field input[type=number]{\r\n\t\twidth: 100%;\r\n\t}\r\n\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field .wpforms-field-row input[type=text]{\r\n\t\twidth: 100%;\r\n\t}\r\n\r\n\r\n\r\n\r\n\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field textarea {\r\n\t\tborder-width:2px !important;border-color:#889407 !important;border-style:ridge !important;\t\t\t\t}\r\n\r\n \r\n\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field select {\r\n\r\n\t\tborder-width:2px !important;border-color:#889407 !important;border-style:ridge !important;\r\n\t\t\t}\r\n\r\n\t\/* dropdown styles on modern background *\/\r\n\tbody #wpforms-6 .wpforms-form .wpforms-field-select-style-modern .choices__inner{\r\n\t\tborder-width:2px !important;border-color:#889407 !important;border-style:ridge !important;\t\t\r\n\t}\r\n\r\n\t\/* background color on modern dropdown *\/\r\n\t\r\n\r\n\r\n\r\n\/* Styling for Tablets *\/\r\n@media only screen and (max-width: 800px) and (min-width:481px) {\r\n\t\r\n\r\n\r\n\r\n}\r\n\r\n@media only screen and (max-width: 480px){\r\n\t\r\n\r\n\r\n\r\n}\r\n\/*Option to add custom CSS *\/\r\n\r\n\r\n\r\n\t\t<\/style>\r\n\t\t<div class=\"wpforms-container \" id=\"wpforms-6\"><form id=\"wpforms-form-6\" class=\"wpforms-validate wpforms-form\" data-formid=\"6\" method=\"post\" enctype=\"multipart\/form-data\" action=\"\/index.php\/wp-json\/wp\/v2\/pages\/384\" data-token=\"39a63b678e502d218f1b27b892d67e4d\" data-token-time=\"1787725381\"><noscript class=\"wpforms-error-noscript\">Aktivera JavaScript i din webbl\u00e4sare f\u00f6r att fylla i formul\u00e4ret.<\/noscript><div class=\"wpforms-field-container\"><div id=\"wpforms-6-field_0-container\" class=\"wpforms-field wpforms-field-name\" data-field-id=\"0\"><label class=\"wpforms-field-label wpforms-label-hide\" for=\"wpforms-6-field_0\">Namn <span class=\"wpforms-required-label\">*<\/span><\/label><input type=\"text\" id=\"wpforms-6-field_0\" class=\"wpforms-field-large wpforms-field-required\" name=\"wpforms[fields][0]\" placeholder=\"Namn *\" required><\/div><div id=\"wpforms-6-field_3-container\" class=\"wpforms-field wpforms-field-text\" data-field-id=\"3\"><label class=\"wpforms-field-label wpforms-label-hide\" for=\"wpforms-6-field_3\">Telefon<\/label><input type=\"text\" id=\"wpforms-6-field_3\" class=\"wpforms-field-large\" name=\"wpforms[fields][3]\" placeholder=\"Telefon\" ><\/div><div id=\"wpforms-6-field_1-container\" class=\"wpforms-field wpforms-field-email\" data-field-id=\"1\"><label class=\"wpforms-field-label wpforms-label-hide\" for=\"wpforms-6-field_1\">E-post <span class=\"wpforms-required-label\">*<\/span><\/label><input type=\"email\" id=\"wpforms-6-field_1\" class=\"wpforms-field-large wpforms-field-required\" name=\"wpforms[fields][1]\" placeholder=\"E-post *\" spellcheck=\"false\" required><\/div><div id=\"wpforms-6-field_2-container\" class=\"wpforms-field wpforms-field-textarea\" data-field-id=\"2\"><label class=\"wpforms-field-label wpforms-label-hide\" for=\"wpforms-6-field_2\">Meddelande <span class=\"wpforms-required-label\">*<\/span><\/label><textarea id=\"wpforms-6-field_2\" class=\"wpforms-field-medium wpforms-field-required\" name=\"wpforms[fields][2]\" placeholder=\"Meddelande\" required><\/textarea><\/div><\/div><!-- .wpforms-field-container --><div class=\"wpforms-field wpforms-field-hp\"><label for=\"wpforms-6-field-hp\" class=\"wpforms-field-label\">Phone<\/label><input type=\"text\" name=\"wpforms[hp]\" id=\"wpforms-6-field-hp\" class=\"wpforms-field-medium\"><\/div><div class=\"wpforms-recaptcha-container wpforms-is-recaptcha wpforms-is-recaptcha-type-v2\" ><div class=\"g-recaptcha\" data-sitekey=\"6Lc9OYknAAAAAHSlpriD_s10ejOgCScuoTP5oNg7\"><\/div><input type=\"text\" name=\"g-recaptcha-hidden\" class=\"wpforms-recaptcha-hidden\" style=\"position:absolute!important;clip:rect(0,0,0,0)!important;height:1px!important;width:1px!important;border:0!important;overflow:hidden!important;padding:0!important;margin:0!important;\" data-rule-recaptcha=\"1\"><\/div><div class=\"wpforms-submit-container\" ><input type=\"hidden\" name=\"wpforms[id]\" value=\"6\"><input type=\"hidden\" name=\"page_title\" value=\"\"><input type=\"hidden\" name=\"page_url\" value=\"https:\/\/www.rchfullstack.com\/index.php\/wp-json\/wp\/v2\/pages\/384\"><input type=\"hidden\" name=\"url_referer\" value=\"\"><button type=\"submit\" name=\"wpforms[submit]\" id=\"wpforms-submit-6\" class=\"wpforms-submit\" data-alt-text=\"Skickar...\" data-submit-text=\"Ta kontakt\" aria-live=\"assertive\" value=\"wpforms-submit\">Ta kontakt<\/button><\/div><\/form><\/div>  <!-- .wpforms-container -->\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t<div class=\"elementor-element elementor-element-b58c57a e-flex e-con-boxed e-con e-parent\" data-id=\"b58c57a\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-22f9016 elementor-widget elementor-widget-heading\" data-id=\"22f9016\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Hitta hit<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-2fe555d7 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"2fe555d7\" data-element_type=\"section\" data-e-type=\"section\" id=\"contact\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-46221d80\" data-id=\"46221d80\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-2c7424cc elementor-widget elementor-widget-google_maps\" data-id=\"2c7424cc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"google_maps.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-custom-embed\">\n\t\t\t<iframe loading=\"lazy\"\n\t\t\t\t\tsrc=\"https:\/\/maps.google.com\/maps?q=Gothenburg%2C%20Sweden&#038;t=m&#038;z=16&#038;output=embed&#038;iwloc=near\"\n\t\t\t\t\ttitle=\"Gothenburg, Sweden\"\n\t\t\t\t\taria-label=\"Gothenburg, Sweden\"\n\t\t\t><\/iframe>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t<div class=\"elementor-element elementor-element-36d10c6 e-flex e-con-boxed e-con e-parent\" data-id=\"36d10c6\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1ef88d5 elementor-widget elementor-widget-shortcode\" data-id=\"1ef88d5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"shortcode.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-shortcode\"><div class='mwai-chatbot-container' data-params='{&quot;aiName&quot;:&quot;RCH FULLSTACK: &quot;,&quot;userName&quot;:&quot;G\\u00e4st: &quot;,&quot;guestName&quot;:&quot;G\\u00e4st:&quot;,&quot;aiAvatar&quot;:false,&quot;userAvatar&quot;:false,&quot;aiAvatarUrl&quot;:&quot;chat-robot-1.svg&quot;,&quot;textSend&quot;:&quot;Skicka&quot;,&quot;textClear&quot;:&quot;Rensa&quot;,&quot;imageUpload&quot;:false,&quot;fileUpload&quot;:false,&quot;multiUpload&quot;:false,&quot;maxUploads&quot;:1,&quot;fileUploads&quot;:0,&quot;mode&quot;:&quot;chat&quot;,&quot;textInputPlaceholder&quot;:&quot;Skriv ett meddelande...&quot;,&quot;textInputMaxLength&quot;:512,&quot;textCompliance&quot;:&quot;&quot;,&quot;startSentence&quot;:&quot;Hej! Vad kan jag hj\\u00e4lpa dig med? &quot;,&quot;localMemory&quot;:true,&quot;themeId&quot;:&quot;messages&quot;,&quot;window&quot;:true,&quot;icon&quot;:&quot;chat-robot-1.svg&quot;,&quot;iconText&quot;:&quot;&quot;,&quot;iconTextDelay&quot;:7,&quot;iconAlt&quot;:&quot;AI Chatbot Avatar&quot;,&quot;iconPosition&quot;:&quot;bottom-right&quot;,&quot;centerOpen&quot;:false,&quot;width&quot;:&quot;&quot;,&quot;openDelay&quot;:&quot;&quot;,&quot;iconBubble&quot;:false,&quot;windowAnimation&quot;:&quot;zoom&quot;,&quot;fullscreen&quot;:true,&quot;copyButton&quot;:true,&quot;pdfButton&quot;:false,&quot;headerSubtitle&quot;:&quot;Discuss with&quot;,&quot;containerType&quot;:&quot;standard&quot;,&quot;headerType&quot;:&quot;standard&quot;,&quot;messagesType&quot;:&quot;standard&quot;,&quot;inputType&quot;:&quot;standard&quot;,&quot;footerType&quot;:&quot;standard&quot;}' data-system='{&quot;botId&quot;:&quot;default&quot;,&quot;customId&quot;:null,&quot;userData&quot;:null,&quot;sessionId&quot;:null,&quot;restNonce&quot;:null,&quot;contextId&quot;:null,&quot;pluginUrl&quot;:&quot;https:\\\/\\\/www.rchfullstack.com\\\/wp-content\\\/plugins\\\/ai-engine&quot;,&quot;restUrl&quot;:&quot;https:\\\/\\\/www.rchfullstack.com\\\/index.php\\\/wp-json&quot;,&quot;stream&quot;:false,&quot;debugMode&quot;:false,&quot;eventLogs&quot;:false,&quot;speech_recognition&quot;:true,&quot;speech_synthesis&quot;:false,&quot;typewriter&quot;:false,&quot;crossSite&quot;:false,&quot;actions&quot;:[],&quot;blocks&quot;:[],&quot;shortcuts&quot;:[]}' data-theme='{&quot;type&quot;:&quot;internal&quot;,&quot;name&quot;:&quot;Messages&quot;,&quot;themeId&quot;:&quot;messages&quot;,&quot;settings&quot;:{&quot;backgroundHeaderColor&quot;:&quot;#889407&quot;,&quot;width&quot;:&quot;460px&quot;,&quot;maxHeight&quot;:&quot;40vh&quot;,&quot;backgroundUserColor&quot;:&quot;#889407&quot;},&quot;style&quot;:&quot;&quot;,&quot;cssUrl&quot;:&quot;https:\\\/\\\/www.rchfullstack.com\\\/wp-content\\\/plugins\\\/ai-engine\\\/themes\\\/messages.css&quot;}'><\/div><\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>V\u00c4LKOMMEN TILLRCH FULLSTACK! Jag hj\u00e4lper dig l\u00f6sa problemet \u2013 enkelt, direkt och med r\u00e4tt AI-verktyg Ta kontakt Prata med RCH FULLSTACK Om mig RCH Fullstack hj\u00e4lper dig att bygga skr\u00e4ddarsydda l\u00f6sningar och automatisera arbetsfl\u00f6den med r\u00e4tt AI-verktyg. \u00a0 AI f\u00f6r\u00e4ndrar hur f\u00f6retag arbetar \u2013 snabbare \u00e4n de flesta hinner anpassa sig. Jag hj\u00e4lper dig ligga [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_theme","meta":{"site-sidebar-layout":"no-sidebar","site-content-layout":"page-builder","ast-site-content-layout":"full-width-container","site-content-style":"unboxed","site-sidebar-style":"unboxed","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"disabled","ast-breadcrumbs-content":"disabled","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"default","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"folder":[],"class_list":["post-384","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.rchfullstack.com\/index.php\/wp-json\/wp\/v2\/pages\/384","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rchfullstack.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.rchfullstack.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.rchfullstack.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rchfullstack.com\/index.php\/wp-json\/wp\/v2\/comments?post=384"}],"version-history":[{"count":1137,"href":"https:\/\/www.rchfullstack.com\/index.php\/wp-json\/wp\/v2\/pages\/384\/revisions"}],"predecessor-version":[{"id":2058,"href":"https:\/\/www.rchfullstack.com\/index.php\/wp-json\/wp\/v2\/pages\/384\/revisions\/2058"}],"wp:attachment":[{"href":"https:\/\/www.rchfullstack.com\/index.php\/wp-json\/wp\/v2\/media?parent=384"}],"wp:term":[{"taxonomy":"folder","embeddable":true,"href":"https:\/\/www.rchfullstack.com\/index.php\/wp-json\/wp\/v2\/folder?post=384"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}