{"id":862,"date":"2023-06-07T09:27:59","date_gmt":"2023-06-07T07:27:59","guid":{"rendered":"https:\/\/moisolar.com\/?page_id=862"},"modified":"2026-01-14T15:04:38","modified_gmt":"2026-01-14T13:04:38","slug":"fixture-calculator","status":"publish","type":"page","link":"https:\/\/moisolar.com\/en\/fixture-calculator\/","title":{"rendered":"Fixture Calculator"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"862\" class=\"elementor elementor-862\" data-elementor-post-type=\"page\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-20e7271 elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no\" data-id=\"20e7271\" data-element_type=\"section\" 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-100 elementor-top-column elementor-element elementor-element-f6b6fab\" data-id=\"f6b6fab\" data-element_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-ba45c04 elementor-widget-mobile__width-initial elementor-widget elementor-widget-heading\" data-id=\"ba45c04\" data-element_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\">Solar Panel Fixture Calculator<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-861e802 elementor-widget-mobile__width-initial elementor-widget elementor-widget-text-editor\" data-id=\"861e802\" data-element_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<div class=\"group w-full text-gray-800 dark:text-gray-100 border-b border-black\/10 dark:border-gray-900\/50 bg-gray-50 dark:bg-[#444654]\"><div class=\"flex p-4 gap-4 text-base md:gap-6 md:max-w-2xl lg:max-w-[38rem] xl:max-w-3xl md:py-6 lg:px-0 m-auto\"><div class=\"relative flex w-[calc(100%-50px)] flex-col gap-1 md:gap-3 lg:w-[calc(100%-115px)]\"><div class=\"flex flex-grow flex-col gap-3\"><div class=\"min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap break-words\"><div class=\"markdown prose w-full break-words dark:prose-invert light\"><p>Welcome to our enhanced Solar Panel Fixture Calculator, now leveraging SGS test results for unparalleled accuracy. This intuitive tool simplifies your solar installation planning by instantly estimating the required roof fixtures based on the city information, roof angle, panel size, roof type, and number of solar panels you input. Designed for all project scales, it aids in efficient resource management and smoother installations. Begin optimizing your solar panel installation process today with our scientifically backed calculator.<\/p><\/div><\/div><\/div><\/div><\/div><\/div>\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-top-section elementor-element elementor-element-95303a4 elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no\" data-id=\"95303a4\" data-element_type=\"section\" 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-5925f62\" data-id=\"5925f62\" data-element_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-2bf872e elementor-widget elementor-widget-spacer\" data-id=\"2bf872e\" data-element_type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\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\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-04b9f02 elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no\" data-id=\"04b9f02\" data-element_type=\"section\" 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-a7190c2\" data-id=\"a7190c2\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\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-top-section elementor-element elementor-element-cf8bb49 elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no\" data-id=\"cf8bb49\" data-element_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-top-column elementor-element elementor-element-84143a6\" data-id=\"84143a6\" data-element_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-0b89350 elementor-widget elementor-widget-html\" data-id=\"0b89350\" data-element_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>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/>\n  <title>Nordic Slope Fixture Calculator<\/title>\n  <style>\n\n:root {\n  --page-bg: #0f172a;\n  --page-bg-secondary: #13213f;\n  --surface: rgba(255, 255, 255, 0.86);\n  --surface-muted: rgba(255, 255, 255, 0.65);\n  --accent: #1b8ef2;\n  --accent-dark: #1363c4;\n  --accent-soft: rgba(27, 142, 242, 0.12);\n  --success: #23a46d;\n  --border-muted: rgba(15, 23, 42, 0.08);\n  --border-strong: rgba(15, 23, 42, 0.18);\n  --text-color: #13213f;\n  --text-muted: #5b657d;\n  --text-invert: #f8fafc;\n  --input-bg: rgba(255, 255, 255, 0.92);\n  --shadow-soft: 0 24px 50px rgba(15, 23, 42, 0.18);\n  --shadow-card: 0 16px 40px rgba(15, 23, 42, 0.12);\n}\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  margin: 0;\n  font-family: \"Inter\", \"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif;\n  color: var(--text-color);\n}\n\n.calculator-shell {\n  --layout-max-width: 1100px;\n  min-height: 100vh;\n  background: radial-gradient(circle at top left, #f3f6ff 0%, #e4ecff 40%, #d6e2ff 100%);\n  display: flex;\n  justify-content: center;\n  padding: 3rem 1.5rem;\n  width: 100%;\n}\n\na {\n  color: inherit;\n}\n\nimg {\n  max-width: 100%;\n  height: auto;\n  display: block;\n}\n\nmain.layout {\n  position: relative;\n  width: 100%;\n  max-width: var(--layout-max-width);\n  display: flex;\n  flex-direction: column;\n  gap: 2.25rem;\n  padding: 3rem 3rem 4rem;\n  border-radius: 28px;\n  background: linear-gradient(145deg, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.85));\n  box-shadow: var(--shadow-soft);\n  backdrop-filter: blur(12px);\n}\n\n.layout::before {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  border-radius: inherit;\n  padding: 1px;\n  background: linear-gradient(135deg, rgba(27, 142, 242, 0.45), rgba(35, 164, 109, 0.35));\n  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);\n  -webkit-mask-composite: xor;\n          mask-composite: exclude;\n}\n\n.intro {\n  position: relative;\n  text-align: center;\n  padding-bottom: 0.5rem;\n}\n\n.intro h1 {\n  margin: 0 0 0.75rem;\n  font-size: 2.4rem;\n  font-weight: 700;\n  letter-spacing: -0.01em;\n}\n\n.intro p {\n  margin: 0;\n  color: var(--text-muted);\n  font-size: 1.05rem;\n  line-height: 1.65;\n}\n\nform#calculatorForm {\n  display: grid;\n  gap: 1.75rem;\n}\n\n.section-card {\n  position: relative;\n  background: var(--surface);\n  border-radius: 16px;\n  padding: 2rem;\n  border: 1px solid var(--border-muted);\n  box-shadow: var(--shadow-card);\n  overflow: hidden;\n}\n\n.section-card::before {\n  content: \"\";\n  position: absolute;\n  inset: -40% 55% auto -20%;\n  height: 320px;\n  background: radial-gradient(circle at center, rgba(27, 142, 242, 0.25), transparent 70%);\n  opacity: 0.4;\n  pointer-events: none;\n}\n\n.section-card h2,\n.section-card h3 {\n  margin: 0 0 1rem;\n  font-size: 1.35rem;\n  letter-spacing: -0.01em;\n}\n\n.field-group + .field-group {\n  margin-top: 1.35rem;\n}\n\n.field-label {\n  display: block;\n  font-size: 0.95rem;\n  font-weight: 600;\n  margin-bottom: 0.4rem;\n  letter-spacing: 0.02em;\n}\n\n.input,\n.input-select {\n  width: 100%;\n  padding: 0.75rem 0.9rem;\n  font-size: 1rem;\n  border: 1px solid var(--border-strong);\n  border-radius: 10px;\n  background: var(--input-bg);\n  color: inherit;\n  transition: border 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;\n}\n\n.input:hover,\n.input-select:hover {\n  border-color: rgba(27, 142, 242, 0.6);\n}\n\n.input:focus,\n.input-select:focus {\n  border-color: var(--accent);\n  box-shadow: 0 0 0 4px var(--accent-soft);\n  outline: none;\n  transform: translateY(-1px);\n}\n\n.help-text {\n  margin: 0.5rem 0 0;\n  color: var(--text-muted);\n  font-size: 0.95rem;\n  line-height: 1.65;\n}\n\n.media-frame {\n  margin: 1.25rem auto 1rem;\n  border-radius: 14px;\n  overflow: hidden;\n  box-shadow: 0 18px 35px rgba(15, 23, 42, 0.12);\n  max-width: 420px;\n}\n\n.media-frame img {\n  max-height: 240px;\n  object-fit: cover;\n}\n\n#roofTypeContainer {\n  display: grid;\n  gap: 1.2rem;\n  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n}\n\n.roof-type-option {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  gap: 0.95rem;\n  align-items: stretch;\n  padding: 1rem;\n  border: 1px solid var(--border-muted);\n  border-radius: 14px;\n  background: var(--surface-muted);\n  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;\n  cursor: pointer;\n  overflow: hidden;\n}\n\n.roof-type-option img {\n  border-radius: 10px;\n  height: 140px;\n  object-fit: cover;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);\n}\n\n.roof-type-option input[type=\"radio\"] {\n  position: absolute;\n  inset: 0;\n  opacity: 0;\n  cursor: pointer;\n}\n\n.roof-type-option span {\n  font-weight: 600;\n  font-size: 0.95rem;\n  letter-spacing: 0.01em;\n}\n\n.roof-type-option:is(:hover, :focus-within) {\n  border-color: rgba(27, 142, 242, 0.6);\n  box-shadow: 0 14px 35px rgba(27, 142, 242, 0.16);\n  transform: translateY(-3px);\n}\n\n.roof-type-option:has(input[type=\"radio\"]:checked) {\n  border-color: var(--accent);\n  box-shadow: 0 18px 40px rgba(27, 142, 242, 0.28);\n  background: #f4f9ff;\n}\n\n#panelTypeContainer {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1.5rem;\n}\n\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  border: 0;\n}\n\n.panel-config {\n  overflow: visible;\n}\n\n.profile-spacing-block {\n  margin-top: 1.75rem;\n}\n\n.profile-spacing-visual {\n  margin: 1rem auto 1.25rem;\n  max-width: 360px;\n  border-radius: 16px;\n  overflow: hidden;\n  box-shadow: 0 18px 32px rgba(27, 142, 242, 0.18);\n  display: none;\n}\n\n.profile-spacing-visual img {\n  width: 100%;\n  height: auto;\n  display: block;\n}\n\n.panel-config-header {\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  margin-bottom: 1.5rem;\n}\n\n.profile-span-options {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1rem;\n  margin-bottom: 1rem;\n}\n\n.profile-mode-card {\n  position: relative;\n  flex: 1 1 240px;\n  padding: 1rem 1.1rem;\n  border-radius: 14px;\n  border: 1px solid rgba(99, 102, 241, 0.25);\n  background: linear-gradient(140deg, rgba(233, 240, 255, 0.92), rgba(215, 226, 255, 0.9));\n  box-shadow: 0 12px 30px rgba(132, 150, 192, 0.25);\n  cursor: pointer;\n  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;\n  display: grid;\n  gap: 0.4rem;\n}\n\n.profile-mode-card input {\n  position: absolute;\n  inset: 0;\n  opacity: 0;\n  cursor: pointer;\n}\n\n.profile-mode-card strong {\n  font-size: 1rem;\n  letter-spacing: 0.02em;\n}\n\n.profile-mode-card span {\n  font-size: 0.9rem;\n  color: rgba(22, 47, 79, 0.7);\n}\n\n.profile-mode-card:is(:hover, :focus-within) {\n  transform: translateY(-2px);\n  border-color: rgba(99, 102, 241, 0.55);\n  box-shadow: 0 16px 34px rgba(132, 150, 192, 0.35);\n}\n\n.profile-mode-card:has(input:checked) {\n  border-color: rgba(99, 102, 241, 0.8);\n  box-shadow: 0 18px 36px rgba(99, 102, 241, 0.35);\n}\n\n.profile-spacing-block {\n  margin-top: 1.75rem;\n}\n\n.profile-custom-field {\n  display: grid;\n  gap: 0.4rem;\n}\n\n.profile-custom-field.disabled {\n  opacity: 0.55;\n}\n\n.panel-control-bar {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n  background: linear-gradient(145deg, rgba(229, 236, 255, 0.95), rgba(209, 223, 255, 0.9));\n  color: #162f4f;\n  padding: 0.9rem 1.2rem;\n  border-radius: 16px;\n  box-shadow: inset 0 0 0 1px rgba(99, 102, 241, 0.18);\n}\n\n.panel-control-hint {\n  margin: 0;\n  font-size: 0.9rem;\n  color: rgba(22, 47, 79, 0.7);\n}\n\n.panel-stepper {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.55rem;\n  background: rgba(15, 23, 42, 0.08);\n  border-radius: 12px;\n  padding: 0.45rem 0.75rem;\n  box-shadow: inset 0 0 0 1px rgba(99, 102, 241, 0.18);\n}\n\n.panel-stepper-unit {\n  font-size: 0.8rem;\n  letter-spacing: 0.08em;\n  text-transform: uppercase;\n  color: rgba(22, 47, 79, 0.6);\n}\n\n.panel-stepper-btn {\n  width: 36px;\n  height: 36px;\n  border-radius: 10px;\n  border: none;\n  background: linear-gradient(145deg, #f97316, #f0522d);\n  color: #fff;\n  font-size: 1.35rem;\n  font-weight: 600;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  cursor: pointer;\n  transition: transform 0.2s ease, box-shadow 0.2s ease;\n}\n\n.panel-stepper-btn:active {\n  transform: translateY(1px);\n}\n\n.panel-stepper-btn:hover {\n  box-shadow: 0 10px 20px rgba(240, 82, 45, 0.35);\n}\n\n.panel-stepper-btn:focus-visible {\n  outline: 2px solid rgba(255, 255, 255, 0.75);\n  outline-offset: 2px;\n}\n\n.panel-stepper-btn span {\n  pointer-events: none;\n}\n\n.panel-stepper-input {\n  width: 56px;\n  border-radius: 8px;\n  border: 1px solid rgba(99, 102, 241, 0.35);\n  background: rgba(255, 255, 255, 0.9);\n  color: #162f4f;\n  font-weight: 600;\n  font-size: 1rem;\n  text-align: center;\n  padding: 0.3rem 0.35rem;\n  box-shadow: inset 0 1px 3px rgba(15, 23, 42, 0.08);\n}\n\n.panel-stepper-input:focus {\n  outline: none;\n  border-color: rgba(99, 102, 241, 0.65);\n  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.18);\n}\n\n.panel-grid {\n  margin-top: 1.5rem;\n  display: grid;\n  gap: 1.2rem;\n}\n\n.panel-row-card {\n  background: linear-gradient(150deg, rgba(233, 240, 255, 0.95), rgba(210, 223, 255, 0.92));\n  border-radius: 18px;\n  padding: 1.35rem 1.6rem 1.45rem;\n  color: #162f4f;\n  box-shadow: 0 20px 45px rgba(132, 150, 192, 0.35);\n  border: 1px solid rgba(99, 102, 241, 0.18);\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n}\n\n.panel-row-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n}\n\n.panel-row-title {\n  font-weight: 600;\n  font-size: 1rem;\n  letter-spacing: 0.04em;\n  text-transform: uppercase;\n}\n\n.panel-row-card .panel-stepper {\n  background: rgba(15, 23, 42, 0.08);\n  border-radius: 10px;\n}\n\n.panel-row-card .panel-stepper-unit {\n  color: rgba(22, 47, 79, 0.55);\n}\n\n\n.panel-preview {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.7rem;\n  background: rgba(236, 242, 255, 0.94);\n  border-radius: 14px;\n  padding: 0.85rem;\n  box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.18);\n}\n\n.panel-preview-cell {\n  position: relative;\n  isolation: isolate;\n  border-radius: 11px;\n  border: 1px solid rgba(100, 116, 139, 0.28);\n  flex: 0 0 122px;\n  aspect-ratio: 1134 \/ 1722;\n  box-shadow: 0 14px 24px rgba(99, 102, 241, 0.16);\n  overflow: hidden;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.panel-preview-cell::after {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  background: url(\"https:\/\/moisolar.com\/wp-content\/uploads\/2025\/10\/panel.jpg\") center\/cover no-repeat;\n  opacity: 0.96;\n  filter: saturate(1.05);\n  transition: transform 0.2s ease;\n}\n\n.panel-preview-more {\n  font-size: 0.85rem;\n  font-weight: 600;\n  background: rgba(249, 115, 22, 0.12);\n  border: 1px solid rgba(249, 115, 22, 0.35);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  color: rgba(22, 47, 79, 0.9);\n}\n\n.panel-preview-cell span {\n  position: relative;\n  z-index: 1;\n  color: rgba(22, 47, 79, 0.85);\n  font-size: 0.95rem;\n  font-weight: 600;\n  letter-spacing: 0.05em;\n}\n\n.panel-preview-cell:hover::after {\n  transform: scale(1.05);\n}\n\n.panel-preview-more::after {\n  filter: grayscale(0.4) brightness(0.9);\n}\n\n.panel-placeholder {\n  padding: 1.5rem;\n  border-radius: 14px;\n  background: rgba(233, 240, 255, 0.6);\n  text-align: center;\n  font-size: 0.95rem;\n  color: var(--text-muted);\n  border: 1px dashed rgba(99, 102, 241, 0.35);\n}\n\n#buttonContainer {\n  display: flex;\n  align-items: center;\n  gap: 1rem;\n  flex-wrap: wrap;\n}\n\nbutton.button {\n  padding: 0.75rem 1.75rem;\n  border-radius: 12px;\n  border: none;\n  font-size: 1rem;\n  font-weight: 600;\n  cursor: pointer;\n  transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;\n  font-family: inherit;\n  box-shadow: 0 14px 28px rgba(15, 23, 42, 0.12);\n}\n\nbutton.button.primary {\n  background: var(--accent);\n  color: var(--text-invert);\n}\n\nbutton.button.primary:hover {\n  background: var(--accent-dark);\n  transform: translateY(-2px);\n  box-shadow: 0 16px 32px rgba(27, 142, 242, 0.32);\n}\n\nbutton.button.secondary {\n  background: var(--success);\n  color: var(--text-invert);\n}\n\nbutton.button.secondary:hover {\n  background: #1c8257;\n  transform: translateY(-2px);\n}\n\nbutton.button:active {\n  transform: translateY(0);\n}\n\nbutton.button:focus-visible {\n  outline: 3px solid rgba(27, 142, 242, 0.35);\n  outline-offset: 3px;\n}\n\n#saveAsPDF,\n#saveAsCSV,\n#suggestUserToSendEmail,\n#resultTable,\n#resultTable2 {\n  display: none;\n}\n\n#resultsSection {\n  display: none;\n  flex-direction: column;\n  gap: 1.5rem;\n  background: var(--surface);\n  border-radius: 16px;\n  padding: 2rem;\n  border: 1px solid var(--border-muted);\n  box-shadow: var(--shadow-card);\n}\n\n#result,\n#result2 {\n  font-size: 1.05rem;\n  font-weight: 600;\n  gap: 0.85rem;\n  padding: 1.5rem 1.5rem 1.75rem;\n  border-radius: 14px;\n  border: 1px solid rgba(27, 142, 242, 0.18);\n  background: linear-gradient(135deg, rgba(27, 142, 242, 0.12), rgba(35, 164, 109, 0.12));\n  box-shadow: 0 14px 40px rgba(15, 23, 42, 0.08);\n}\n\n#result {\n  display: flex;\n  align-items: center;\n  gap: 1.25rem;\n}\n\n#result img {\n  width: 150px;\n  max-width: 45%;\n  border-radius: 16px;\n  box-shadow: 0 16px 32px rgba(27, 142, 242, 0.22);\n}\n\n#result p {\n  margin: 0;\n  letter-spacing: 0.01em;\n}\n\n#result2 {\n  display: grid;\n  grid-template-columns: minmax(0, 1fr);\n  text-align: center;\n}\n\n#result2 .result-visual-container {\n  display: flex;\n  justify-content: center;\n  padding: 0.15rem 0 0.65rem;\n}\n\n#result2 .result-visual {\n  width: min(420px, 90%);\n  height: auto;\n  border-radius: 20px;\n  box-shadow: 0 20px 35px rgba(27, 142, 242, 0.24);\n}\n\n#result2 .result-text {\n  margin: 0;\n  font-size: 1.1rem;\n  letter-spacing: 0.01em;\n}\n\n.warning {\n  color: #5c3d00;\n  background: rgba(255, 225, 179, 0.55);\n  padding: 1rem 1.2rem;\n  border-radius: 12px;\n  border: 1px solid rgba(157, 95, 0, 0.35);\n  font-size: 0.97rem;\n  line-height: 1.6;\n}\n\n.table-wrapper {\n  border-radius: 16px;\n  border: 1px solid var(--border-muted);\n  background: rgba(255, 255, 255, 0.92);\n  box-shadow: 0 12px 32px rgba(15, 23, 42, 0.08);\n  position: relative;\n  overflow: hidden;\n}\n\n.table-wrapper::after {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  pointer-events: none;\n  border-radius: inherit;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);\n}\n\n.table-scroll {\n  border-radius: inherit;\n  overflow-x: auto;\n  overflow-y: hidden;\n  -webkit-overflow-scrolling: touch;\n  scrollbar-width: thin;\n}\n\n.table-scroll::-webkit-scrollbar {\n  height: 10px;\n}\n\n.table-scroll::-webkit-scrollbar-track {\n  background: rgba(148, 163, 184, 0.12);\n  border-radius: 999px;\n}\n\n.table-scroll::-webkit-scrollbar-thumb {\n  background: rgba(27, 142, 242, 0.35);\n  border-radius: 999px;\n}\n\n.table-scroll::-webkit-scrollbar-thumb:hover {\n  background: rgba(27, 142, 242, 0.55);\n}\n\n.table-header-line {\n  display: block;\n  line-height: 1.2;\n}\n\ntable {\n  border-collapse: collapse;\n  width: max-content;\n  min-width: 100%;\n  background: transparent;\n}\n\nthead th {\n  background: rgba(27, 142, 242, 0.12);\n}\n\n#resultTable td,\n#resultTable th,\n#resultTable2 td,\n#resultTable2 th {\n  padding: 0.75rem 1rem;\n  border-bottom: 1px solid rgba(15, 23, 42, 0.07);\n  text-align: left;\n  font-size: 0.95rem;\n}\n\n#resultTable th,\n#resultTable2 th {\n  background: rgba(27, 142, 242, 0.14);\n  font-weight: 600;\n  letter-spacing: 0.03em;\n}\n\n#resultTable tr:nth-child(even),\n#resultTable2 tr:nth-child(even) {\n  background: rgba(15, 23, 42, 0.025);\n}\n\n#resultTable tr:hover,\n#resultTable2 tr:hover {\n  background: rgba(27, 142, 242, 0.08);\n}\n\n#resultTable img,\n#resultTable2 img {\n  max-width: 82px;\n  border-radius: 10px;\n  box-shadow: 0 10px 18px rgba(15, 23, 42, 0.14);\n}\n\n#suggestUserToSendEmail {\n  color: var(--text-muted);\n  font-size: 0.95rem;\n}\n\n#calculatingIndicator {\n  position: fixed;\n  inset: 0;\n  background: rgba(15, 23, 42, 0.45);\n  color: #fff;\n  display: none;\n  align-items: center;\n  justify-content: center;\n  z-index: 1000;\n  backdrop-filter: blur(8px);\n}\n\n#calculatingIndicator::before {\n  content: \"\";\n  width: 78px;\n  height: 78px;\n  border-radius: 50%;\n  border: 5px solid rgba(255, 255, 255, 0.25);\n  border-top-color: #fff;\n  animation: spinner 1.1s linear infinite;\n  margin-right: 1.5rem;\n}\n\n#calculatingText {\n  font-size: 1.35rem;\n  letter-spacing: 0.08em;\n  animation: pulse 1s ease-in-out infinite;\n}\n\n@keyframes pulse {\n  0%,\n  100% {\n    opacity: 0.4;\n  }\n  50% {\n    opacity: 1;\n  }\n}\n\n@keyframes spinner {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n@media (max-width: 1024px) {\n  main.layout {\n    padding: 2.5rem 2rem 3.5rem;\n  }\n}\n\n@media (max-width: 768px) {\n  .calculator-shell {\n    padding: 2rem 1rem;\n  }\n\n  main.layout {\n    padding: 2.25rem 1.5rem 3rem;\n  }\n\n  #buttonContainer {\n    flex-direction: column;\n    align-items: stretch;\n  }\n\n  button.button {\n    width: 100%;\n    text-align: center;\n  }\n\n  #result,\n  #result2 {\n    flex-direction: column;\n    text-align: center;\n  }\n\n  .panel-control-bar {\n    flex-direction: column;\n    gap: 0.75rem;\n    align-items: stretch;\n  }\n\n  .panel-row-header {\n    flex-direction: column;\n    align-items: stretch;\n    gap: 0.75rem;\n  }\n\n  .panel-row-card .panel-stepper {\n    justify-content: space-between;\n  }\n}\n\n  <\/style>\n<\/head>\n<body>\n  <div class=\"calculator-shell\">\n    <main class=\"layout\">\n      <header class=\"intro\"><\/header>\n\n    <form id=\"calculatorForm\" novalidate>\n      <section id=\"snow-load-info\" class=\"section-card\">\n        <h2>Set City Information<\/h2>\n        <p class=\"help-text\">Selecting the correct city is important as the general snow load varies by location. This value is used to calculate the specific load on your solar panels.<\/p>\n        <figure class=\"media-frame\">\n          <img decoding=\"async\" src=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/snow-load-for-cities.png\" alt=\"Snow load heat map for Finland\" loading=\"lazy\" \/>\n        <\/figure>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"city\">Select Your City<\/label>\n          <select id=\"city\" class=\"input-select\">\n<option value=\"Akaa\">Akaa - 2.3 kN\/m\u00b2<\/option>\n    <option value=\"Alaja\u0308rvi\">Alaja\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Alavieska\">Alavieska - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Alavus\">Alavus - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Asikkala\">Asikkala - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Askola\">Askola - 2.7 kN\/m\u00b2<\/option>\n    <option value=\"Aura\">Aura - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Bra\u0308ndo\u0308\">Bra\u0308ndo\u0308 - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Eckero\u0308\">Eckero\u0308 - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Enonkoski\">Enonkoski - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Enontekio\u0308\">Enontekio\u0308 - 3.5 kN\/m\u00b2<\/option>\n    <option value=\"Espoo\">Espoo - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Eura\">Eura - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Eurajoki\">Eurajoki - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Evija\u0308rvi\">Evija\u0308rvi - 2.2 kN\/m\u00b2<\/option>\n    <option value=\"Finstro\u0308m\">Finstro\u0308m - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Forssa\">Forssa - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Fo\u0308glo\u0308\">Fo\u0308glo\u0308 - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Geta\">Geta - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Haapaja\u0308rvi\">Haapaja\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Haapavesi\">Haapavesi - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Hailuoto\">Hailuoto - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Halsua\">Halsua - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Hamina\">Hamina - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Hammarland\">Hammarland - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Hankasalmi\">Hankasalmi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Hanko\">Hanko - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Harjavalta\">Harjavalta - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Hartola\">Hartola - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Hattula\">Hattula - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Hausja\u0308rvi\">Hausja\u0308rvi - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Heinola\">Heinola - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Heina\u0308vesi\">Heina\u0308vesi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Helsinki\">Helsinki - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Hirvensalmi\">Hirvensalmi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Hollola\">Hollola - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Honkajoki\">Honkajoki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Huittinen\">Huittinen - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Humppila\">Humppila - 2.25 kN\/m\u00b2<\/option>\n    <option value=\"Hyrynsalmi\">Hyrynsalmi - 3.5 kN\/m\u00b2<\/option>\n    <option value=\"Hyvinka\u0308a\u0308 \">Hyvinka\u0308a\u0308  - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Ha\u0308meenkyro\u0308 \">Ha\u0308meenkyro\u0308  - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ha\u0308meenlinna \">Ha\u0308meenlinna  - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ii  \">Ii   - 2.8 kN\/m\u00b2<\/option>\n    <option value=\"Iisalmi \">Iisalmi  - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Iitti \">Iitti  - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ikaalinen \">Ikaalinen  - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ilmajoki \">Ilmajoki  - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ilomantsi \">Ilomantsi  - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Imatra\">Imatra - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Inari \">Inari  - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Inkoo\">Inkoo - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Isojoki\">Isojoki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Isokyro\u0308\">Isokyro\u0308 - 2.3 kN\/m\u00b2<\/option>\n    <option value=\"Janakkala\">Janakkala - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Joensuu\">Joensuu - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Jokioinen\">Jokioinen - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Jomala\">Jomala - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Joroinen\">Joroinen - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Joutsa\">Joutsa - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Juuka\">Juuka - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Juupajoki\">Juupajoki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Juva\">Juva - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Jyva\u0308skyla\u0308\">Jyva\u0308skyla\u0308 - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ja\u0308mija\u0308rvi\">Ja\u0308mija\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ja\u0308msa\u0308\">Ja\u0308msa\u0308 - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ja\u0308rvenpa\u0308a\u0308\">Ja\u0308rvenpa\u0308a\u0308 - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Kaarina\">Kaarina - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kaavi\">Kaavi - 2.65 kN\/m\u00b2<\/option>\n    <option value=\"Kajaani\">Kajaani - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Kalajoki\">Kalajoki - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Kangasala\">Kangasala - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kangasniemi\">Kangasniemi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kankaanpa\u0308a\u0308\">Kankaanpa\u0308a\u0308 - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kannonkoski\">Kannonkoski - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kannus\">Kannus - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Karijoki\">Karijoki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Karkkila\">Karkkila - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Karstula\">Karstula - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Karvia\">Karvia - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kaskinen\">Kaskinen - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Kauhajoki\">Kauhajoki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kauhava\">Kauhava - 2.3 kN\/m\u00b2<\/option>\n    <option value=\"Kauniainen\">Kauniainen - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Kaustinen\">Kaustinen - 2.2 kN\/m\u00b2<\/option>\n    <option value=\"Keitele \">Keitele  - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kemi\">Kemi - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Kemija\u0308rvi\">Kemija\u0308rvi - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Keminmaa\">Keminmaa - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Kemio\u0308nsaari\">Kemio\u0308nsaari - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kempele\">Kempele - 2.25 kN\/m\u00b2<\/option>\n    <option value=\"Kerava\">Kerava - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Keuruu\">Keuruu - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kihnio\u0308\">Kihnio\u0308 - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kinnula\">Kinnula - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kirkkonummi\">Kirkkonummi - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Kitee\">Kitee - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Kittila\u0308\">Kittila\u0308 - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Kiuruvesi\">Kiuruvesi - 2.55 kN\/m\u00b2<\/option>\n    <option value=\"Kivija\u0308rvi\">Kivija\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kokema\u0308ki\">Kokema\u0308ki - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Kokkola\">Kokkola - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Kolari\">Kolari - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Konnevesi\">Konnevesi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kontiolahti\">Kontiolahti - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Korsna\u0308s\">Korsna\u0308s - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Koski Tl\">Koski Tl - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Kotka\">Kotka - 2.65 kN\/m\u00b2<\/option>\n    <option value=\"Kouvola\">Kouvola - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kristiinankaupunki\">Kristiinankaupunki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kruunupyy\">Kruunupyy - 2.2 kN\/m\u00b2<\/option>\n    <option value=\"Kuhmo\">Kuhmo - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Kuhmoinen\">Kuhmoinen - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kumlinge\">Kumlinge - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Kuopio\">Kuopio - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kuortane\">Kuortane - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kurikka\">Kurikka - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Kustavi\">Kustavi - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Kuusamo\">Kuusamo - 3.5 kN\/m\u00b2<\/option>\n    <option value=\"Kyyja\u0308rvi\">Kyyja\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ka\u0308rko\u0308la\u0308\">Ka\u0308rko\u0308la\u0308 - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Ka\u0308rsa\u0308ma\u0308ki\">Ka\u0308rsa\u0308ma\u0308ki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ko\u0308kar\">Ko\u0308kar - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Lahti\">Lahti - 2.65 kN\/m\u00b2<\/option>\n    <option value=\"Laihia\">Laihia - 2.3 kN\/m\u00b2<\/option>\n    <option value=\"Laitila\">Laitila - 2.2 kN\/m\u00b2<\/option>\n    <option value=\"Lapinja\u0308rvi\">Lapinja\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Lapinlahti\">Lapinlahti - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Lappaja\u0308rvi\">Lappaja\u0308rvi - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Lappeenranta\">Lappeenranta - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Lapua\">Lapua - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Laukaa \">Laukaa  - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Lemi\">Lemi - 2.7 kN\/m\u00b2<\/option>\n    <option value=\"Lemland\">Lemland - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Lempa\u0308a\u0308la\u0308\">Lempa\u0308a\u0308la\u0308 - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Leppa\u0308virta\">Leppa\u0308virta - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Lestija\u0308rvi\">Lestija\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Lieksa\">Lieksa - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Lieto\">Lieto - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Liminka\">Liminka - 2.45 kN\/m\u00b2<\/option>\n    <option value=\"Liperi\">Liperi - 2.55 kN\/m\u00b2<\/option>\n    <option value=\"Lohja\">Lohja - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Loimaa\">Loimaa - 2.65 kN\/m\u00b2<\/option>\n    <option value=\"Loppi\">Loppi - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Loviisa\">Loviisa - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Luhanka\">Luhanka - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Lumijoki\">Lumijoki - 2.25 kN\/m\u00b2<\/option>\n    <option value=\"Lumparland\">Lumparland - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Luoto\">Luoto - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Luuma\u0308ki\">Luuma\u0308ki - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Maalahti\">Maalahti - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Maarianhamina\">Maarianhamina - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Marttila\">Marttila - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Masku\">Masku - 2.3 kN\/m\u00b2<\/option>\n    <option value=\"Merija\u0308rvi\">Merija\u0308rvi - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Merikarvia\">Merikarvia - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Miehikka\u0308la\u0308\">Miehikka\u0308la\u0308 - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Mikkeli\">Mikkeli - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Muhos\">Muhos - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Multia\">Multia - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Muonio\">Muonio - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Mustasaari\">Mustasaari - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Muurame\">Muurame - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Myna\u0308ma\u0308ki\">Myna\u0308ma\u0308ki - 2.3 kN\/m\u00b2<\/option>\n    <option value=\"Myrskyla\u0308\">Myrskyla\u0308 - 2.65 kN\/m\u00b2<\/option>\n    <option value=\"Ma\u0308ntsa\u0308la\u0308\">Ma\u0308ntsa\u0308la\u0308 - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Ma\u0308ntta\u0308\u2010Vilppula\">Ma\u0308ntta\u0308\u2010Vilppula - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ma\u0308ntyharju\">Ma\u0308ntyharju - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Naantali\">Naantali - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Nakkila\">Nakkila - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Nivala\">Nivala - 2.35 kN\/m\u00b2<\/option>\n    <option value=\"Nokia\">Nokia - 2.45 kN\/m\u00b2<\/option>\n    <option value=\"Nousiainen\">Nousiainen - 2.3 kN\/m\u00b2<\/option>\n    <option value=\"Nurmes\">Nurmes - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Nurmija\u0308rvi\">Nurmija\u0308rvi - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Na\u0308rpio\u0308\">Na\u0308rpio\u0308 - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Orimattila \">Orimattila  - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Oripa\u0308a\u0308 \">Oripa\u0308a\u0308  - 2.2 kN\/m\u00b2<\/option>\n    <option value=\"Orivesi\">Orivesi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Oulainen\">Oulainen - 2.15 kN\/m\u00b2<\/option>\n    <option value=\"Oulu\">Oulu - 2.45 kN\/m\u00b2<\/option>\n    <option value=\"Outokumpu\">Outokumpu - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Padasjoki\">Padasjoki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Paimio\">Paimio - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Paltamo\">Paltamo - 3.3 kN\/m\u00b2<\/option>\n    <option value=\"Parainen\">Parainen - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Parikkala\">Parikkala - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Parkano\">Parkano - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Pederso\u0308ren kunta\">Pederso\u0308ren kunta - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Pelkosenniemi\">Pelkosenniemi - 2.9 kN\/m\u00b2<\/option>\n    <option value=\"Pello\">Pello - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Perho\">Perho - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Pertunmaa\">Pertunmaa - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Peta\u0308ja\u0308vesi\">Peta\u0308ja\u0308vesi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Pieksa\u0308ma\u0308ki\">Pieksa\u0308ma\u0308ki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Pielavesi\">Pielavesi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Pietarsaari\">Pietarsaari - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Pihtipudas\">Pihtipudas - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Pirkkala\">Pirkkala - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Polvija\u0308rvi\">Polvija\u0308rvi - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Pomarkku\">Pomarkku - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Pori\">Pori - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Pornainen\">Pornainen - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Porvoo\">Porvoo - 2.65 kN\/m\u00b2<\/option>\n    <option value=\"Posio\">Posio - 3.5 kN\/m\u00b2<\/option>\n    <option value=\"Pudasja\u0308rvi\">Pudasja\u0308rvi - 3.5 kN\/m\u00b2<\/option>\n    <option value=\"Pukkila\">Pukkila - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Punkalaidun\">Punkalaidun - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Puolanka\">Puolanka - 3.5 kN\/m\u00b2<\/option>\n    <option value=\"Puumala\">Puumala - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Pyhta\u0308a\u0308\">Pyhta\u0308a\u0308 - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Pyha\u0308joki\">Pyha\u0308joki - 2.05 kN\/m\u00b2<\/option>\n    <option value=\"Pyha\u0308ja\u0308rvi\">Pyha\u0308ja\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Pyha\u0308nta\u0308\">Pyha\u0308nta\u0308 - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Pyha\u0308ranta\">Pyha\u0308ranta - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Pa\u0308lka\u0308ne\">Pa\u0308lka\u0308ne - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Po\u0308ytya\u0308\">Po\u0308ytya\u0308 - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Raahe\">Raahe - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Raasepori\">Raasepori - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Raisio\">Raisio - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Rantasalmi\">Rantasalmi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ranua\">Ranua - 3.2 kN\/m\u00b2<\/option>\n    <option value=\"Rauma \">Rauma  - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Rautalampi\">Rautalampi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Rautavaara\">Rautavaara - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Rautja\u0308rvi\">Rautja\u0308rvi - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Reisja\u0308rvi\">Reisja\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Riihima\u0308ki\">Riihima\u0308ki - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Ristija\u0308rvi\">Ristija\u0308rvi - 3.5 kN\/m\u00b2<\/option>\n    <option value=\"Rovaniemi\">Rovaniemi - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Ruokolahti\">Ruokolahti - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Ruovesi\">Ruovesi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Rusko\">Rusko - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ra\u0308a\u0308kkyla\u0308\">Ra\u0308a\u0308kkyla\u0308 - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Saarija\u0308rvi\">Saarija\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Salla\">Salla - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Salo\">Salo - 2.7 kN\/m\u00b2<\/option>\n    <option value=\"Saltvik\">Saltvik - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Sastamala\">Sastamala - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Sauvo\">Sauvo - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"Savitaipale\">Savitaipale - 2.65 kN\/m\u00b2<\/option>\n    <option value=\"Savonlinna\">Savonlinna - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Savukoski\">Savukoski - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Seina\u0308joki\">Seina\u0308joki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Sievi\">Sievi - 2.35 kN\/m\u00b2<\/option>\n    <option value=\"Siikainen\">Siikainen - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Siikajoki\">Siikajoki - 2.25 kN\/m\u00b2<\/option>\n    <option value=\"Siikalatva\">Siikalatva - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Siilinja\u0308rvi\">Siilinja\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Simo\">Simo - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Sipoo\">Sipoo - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Siuntio\">Siuntio - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Sodankyla\u0308\">Sodankyla\u0308 - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Soini\">Soini - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Somero\">Somero - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Sonkaja\u0308rvi\">Sonkaja\u0308rvi - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Sotkamo\">Sotkamo - 3.4 kN\/m\u00b2<\/option>\n    <option value=\"Sottunga\">Sottunga - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Sulkava\">Sulkava - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Sund\">Sund - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Suomussalmi\">Suomussalmi - 3.5 kN\/m\u00b2<\/option>\n    <option value=\"Suonenjoki\">Suonenjoki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Sysma\u0308\">Sysma\u0308 - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Sa\u0308kyla\u0308\">Sa\u0308kyla\u0308 - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Taipalsaari\">Taipalsaari - 2.7 kN\/m\u00b2<\/option>\n    <option value=\"Taivalkoski\">Taivalkoski - 3.5 kN\/m\u00b2<\/option>\n    <option value=\"Taivassalo\">Taivassalo - 2.2 kN\/m\u00b2<\/option>\n    <option value=\"Tammela\">Tammela - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Tampere\">Tampere - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Tervo \">Tervo  - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Tervola\">Tervola - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Teuva\">Teuva - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Tohmaja\u0308rvi\">Tohmaja\u0308rvi - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Toholampi\">Toholampi - 2.25 kN\/m\u00b2<\/option>\n    <option value=\"Toivakka\">Toivakka - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Tornio\">Tornio - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Turku\">Turku - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Tuusniemi\">Tuusniemi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Tuusula\">Tuusula - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Tyrna\u0308va\u0308\">Tyrna\u0308va\u0308 - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Ulvila\">Ulvila - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Urjala\">Urjala - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Utaja\u0308rvi\">Utaja\u0308rvi - 2.85 kN\/m\u00b2<\/option>\n    <option value=\"Utsjoki\">Utsjoki - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Uurainen\">Uurainen - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Uusikaarlepyy\">Uusikaarlepyy - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Uusikaupunki\">Uusikaupunki - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Vaala\">Vaala - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Vaasa\">Vaasa - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Valkeakoski\">Valkeakoski - 2.4 kN\/m\u00b2<\/option>\n    <option value=\"Valtimo\">Valtimo - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Vantaa\">Vantaa - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Varkaus\">Varkaus - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Vehmaa\">Vehmaa - 2.2 kN\/m\u00b2<\/option>\n    <option value=\"Vesanto\">Vesanto - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Vesilahti\">Vesilahti - 2.3 kN\/m\u00b2<\/option>\n    <option value=\"Veteli\">Veteli - 2.3 kN\/m\u00b2<\/option>\n    <option value=\"Vierema\u0308\">Vierema\u0308 - 2.85 kN\/m\u00b2<\/option>\n    <option value=\"Vihti\">Vihti - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Viitasaari\">Viitasaari - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Vimpeli\">Vimpeli - 2.45 kN\/m\u00b2<\/option>\n    <option value=\"Virolahti\">Virolahti - 2.75 kN\/m\u00b2<\/option>\n    <option value=\"Virrat\">Virrat - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Va\u030ardo\u0308\">Va\u030ardo\u0308 - 2.0 kN\/m\u00b2<\/option>\n    <option value=\"Vo\u0308yri\">Vo\u0308yri - 2.1 kN\/m\u00b2<\/option>\n    <option value=\"Ylitornio\">Ylitornio - 3.0 kN\/m\u00b2<\/option>\n    <option value=\"Ylivieska\">Ylivieska - 2.15 kN\/m\u00b2<\/option>\n    <option value=\"Ylo\u0308ja\u0308rvi\">Ylo\u0308ja\u0308rvi - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"Ypa\u0308ja\u0308\">Ypa\u0308ja\u0308 - 2.6 kN\/m\u00b2<\/option>\n    <option value=\"A\u0308hta\u0308ri\">A\u0308hta\u0308ri - 2.5 kN\/m\u00b2<\/option>\n    <option value=\"A\u0308a\u0308nekoski \">A\u0308a\u0308nekoski  - 2.5 kN\/m\u00b2<\/option>\n\n          <\/select>\n        <\/div>\n      <\/section>\n\n      <section id=\"roof-angle-info\" class=\"section-card\">\n        <h2>Set Roof Angle<\/h2>\n        <p class=\"help-text\">Enter the roof angle directly or provide the roof length and height to calculate the angle. The roof steepness influences how much snow accumulates.<\/p>\n        <figure class=\"media-frame\">\n          <img decoding=\"async\" src=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/roof-angle-example-2.jpg\" alt=\"Diagram illustrating roof angle measurements\" loading=\"lazy\" \/>\n        <\/figure>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"A\">Angle in degrees (A)<\/label>\n          <input type=\"number\" id=\"A\" class=\"input\" value=\"29\" inputmode=\"decimal\" min=\"0\" max=\"89\" step=\"0.1\" \/>\n        <\/div>\n        <p class=\"help-text\"><strong>Or<\/strong> provide the roof length and height to derive the angle automatically.<\/p>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"roofLength\">Length in meters (L)<\/label>\n          <input type=\"number\" id=\"roofLength\" class=\"input\" placeholder=\"Enter length in meters\" inputmode=\"decimal\" min=\"0\" step=\"0.01\" \/>\n        <\/div>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"roofHeight\">Height in meters (H)<\/label>\n          <input type=\"number\" id=\"roofHeight\" class=\"input\" placeholder=\"Enter height in meters\" inputmode=\"decimal\" min=\"0\" step=\"0.01\" \/>\n        <\/div>\n      <\/section>\n\n      <section id=\"solar-panel-dimensions\" class=\"section-card\">\n        <h2>Set Panel Dimensions<\/h2>\n        <p class=\"help-text\">Input the width and length of your solar panel in millimetres. These dimensions are required to calculate the snow load each panel can bear.<\/p>\n        <figure class=\"media-frame\">\n          <img decoding=\"async\" src=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/panel-example.jpg\" alt=\"Example solar panel dimensions\" loading=\"lazy\" \/>\n        <\/figure>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"PW\">Width (W)<\/label>\n          <input type=\"number\" id=\"PW\" class=\"input\" value=\"1134\" inputmode=\"numeric\" min=\"0\" \/>\n        <\/div>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"PL\">Length (L)<\/label>\n          <input type=\"number\" id=\"PL\" class=\"input\" value=\"1722\" inputmode=\"numeric\" min=\"0\" \/>\n        <\/div>\n      <\/section>\n\n      <section class=\"section-card\">\n        <h2>Set Roof Type<\/h2>\n        <div id=\"roofTypeContainer\">\n          <label class=\"roof-type-option\" for=\"roofType1\">\n            <img decoding=\"async\" src=\"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/06\/roof_rb01.jpg\" alt=\"Lock seamed metal roof\" loading=\"lazy\" \/>\n            <input type=\"radio\" id=\"roofType1\" name=\"roofType\" value=\"1\" data-image=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/1.png\" checked \/>\n            <span>Lock seamed metal roof<\/span>\n          <\/label>\n          <label class=\"roof-type-option\" for=\"roofType2\">\n            <img decoding=\"async\" src=\"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/06\/roof_rb02.jpg\" alt=\"Felt roof and metal profile roof\" loading=\"lazy\" \/>\n            <input type=\"radio\" id=\"roofType2\" name=\"roofType\" value=\"2\" data-image=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/2.png\" \/>\n            <span>Felt roof and metal profile roof<\/span>\n          <\/label>\n          <label class=\"roof-type-option\" for=\"roofType3\">\n            <img decoding=\"async\" src=\"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/06\/roof_rb03.jpg\" alt=\"Tile-shaped metal profile roof\" loading=\"lazy\" \/>\n            <input type=\"radio\" id=\"roofType3\" name=\"roofType\" value=\"3\" data-image=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/3.png\" \/>\n            <span>Tile-shaped metal profile roof<\/span>\n          <\/label>\n          <label class=\"roof-type-option\" for=\"roofType4\">\n            <img decoding=\"async\" src=\"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/06\/roof_rb04.jpg\" alt=\"Wave-shaped metal profile roof\" loading=\"lazy\" \/>\n            <input type=\"radio\" id=\"roofType4\" name=\"roofType\" value=\"4\" data-image=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/4.png\" \/>\n            <span>Wave-shaped metal profile roof<\/span>\n          <\/label>\n          <label class=\"roof-type-option\" for=\"roofType5\">\n            <img decoding=\"async\" src=\"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/06\/roof_rb05.jpg\" alt=\"Curved tile roof\" loading=\"lazy\" \/>\n            <input type=\"radio\" id=\"roofType5\" name=\"roofType\" value=\"5\" data-image=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/5.png\" \/>\n            <span>Tile roof (curved)<\/span>\n          <\/label>\n          <label class=\"roof-type-option\" for=\"roofType6\">\n            <img decoding=\"async\" src=\"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/06\/roof_rb05.jpg\" alt=\"Straight tile roof\" loading=\"lazy\" \/>\n            <input type=\"radio\" id=\"roofType6\" name=\"roofType\" value=\"6\" data-image=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/6.png\" \/>\n            <span>Tile roof (straight)<\/span>\n          <\/label>\n        <\/div>\n\n        <div class=\"profile-spacing-block\">\n          <h3>Set Profile Spacing<\/h3>\n          <p class=\"help-text\">Choose whether to follow the calculator\u2019s recommended spacing between roof brackets or enter your own value. Larger spacing reduces bracket count but increases the load each bracket must carry.<\/p>\n          <div class=\"profile-spacing-visual\" id=\"profileSpacingVisual\" aria-hidden=\"true\">\n            <img decoding=\"async\" src=\"\" alt=\"Profile spacing illustration\" id=\"profileSpacingImage\" loading=\"lazy\" \/>\n          <\/div>\n          <div class=\"profile-span-options\">\n            <label class=\"profile-mode-card\">\n              <input type=\"radio\" name=\"profileSpanMode\" value=\"auto\" checked>\n              <strong>Use Recommended Value<\/strong>\n              <span>The calculator determines spacing based on snow load and roof type.<\/span>\n            <\/label>\n            <label class=\"profile-mode-card\">\n              <input type=\"radio\" name=\"profileSpanMode\" value=\"custom\">\n              <strong>Set Custom Spacing<\/strong>\n              <span>Enter your preferred distance between roof brackets in millimetres.<\/span>\n            <\/label>\n          <\/div>\n          <div class=\"profile-custom-field disabled\" id=\"profileSpanCustomField\" aria-hidden=\"true\">\n            <label class=\"field-label\" for=\"profileSpanCustom\">Custom spacing (mm)<\/label>\n            <input type=\"number\" id=\"profileSpanCustom\" class=\"input\" value=\"1200\" min=\"0\" max=\"5000\" step=\"10\" placeholder=\"1500\" disabled>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <section class=\"section-card panel-config\">\n        <div class=\"panel-config-header\">\n          <h2>Set Solar Panel Amount<\/h2>\n          <p class=\"help-text\">Adjust the rows and simulate how many panels will sit on each row.<\/p>\n        <\/div>\n        <div class=\"panel-control-bar\">\n          <div class=\"panel-stepper\" id=\"panelRowsStepper\" role=\"group\" aria-label=\"Adjust number of panel rows\">\n            <button type=\"button\" class=\"panel-stepper-btn\" data-action=\"decrement\" aria-label=\"Remove row\"><span aria-hidden=\"true\">-<\/span><\/button>\n            <input type=\"number\" class=\"panel-stepper-input\" id=\"panelRowsInput\" value=\"0\" min=\"0\" max=\"30\" step=\"1\" aria-label=\"Number of panel rows\">\n            <span class=\"panel-stepper-unit\">rows<\/span>\n            <button type=\"button\" class=\"panel-stepper-btn\" data-action=\"increment\" aria-label=\"Add row\"><span aria-hidden=\"true\">+<\/span><\/button>\n          <\/div>\n          <p class=\"panel-control-hint\"><\/p>\n        <\/div>\n        <div class=\"sr-only\">\n          <label for=\"numRows\">How many rows of solar panels do you plan to install?<\/label>\n          <select id=\"numRows\" name=\"numRows\" class=\"input-select\">\n          <option value=\"0\">0<\/option>\n          <option value=\"1\">1<\/option>\n          <option value=\"2\">2<\/option>\n          <option value=\"3\">3<\/option>\n          <option value=\"4\">4<\/option>\n          <option value=\"5\">5<\/option>\n          <option value=\"6\">6<\/option>\n          <option value=\"7\">7<\/option>\n          <option value=\"8\">8<\/option>\n          <option value=\"9\">9<\/option>\n          <option value=\"10\">10<\/option>\n          <option value=\"11\">11<\/option>\n          <option value=\"12\">12<\/option>\n          <option value=\"13\">13<\/option>\n          <option value=\"14\">14<\/option>\n          <option value=\"15\">15<\/option>\n          <option value=\"16\">16<\/option>\n          <option value=\"17\">17<\/option>\n          <option value=\"18\">18<\/option>\n          <option value=\"19\">19<\/option>\n          <option value=\"20\">20<\/option>\n          <option value=\"21\">21<\/option>\n          <option value=\"22\">22<\/option>\n          <option value=\"23\">23<\/option>\n          <option value=\"24\">24<\/option>\n          <option value=\"25\">25<\/option>\n          <option value=\"26\">26<\/option>\n          <option value=\"27\">27<\/option>\n          <option value=\"28\">28<\/option>\n          <option value=\"29\">29<\/option>\n          <option value=\"30\">30<\/option>\n          <\/select>\n        <\/div>\n        <div id=\"rowInputs\" class=\"panel-grid\">\n          <div class=\"panel-placeholder\">Use the controls above to add panel rows.<\/div>\n        <\/div>\n      <\/section>\n\n      <section class=\"section-card\">\n        <h2>Customer Information<\/h2>\n        <p class=\"help-text\">Provide contact details to generate an initial quotation. To receive a tailored offer, download the quotation table and email it to <a href=\"mailto:info@moisolar.com\">info@moisolar.com<\/a>. We specialise in B2B services.<\/p>\n        <p class=\"help-text\"><strong>Note on Data Privacy:<\/strong> By filling in the information below and starting the calculation, you agree to our <a href=\"https:\/\/moisolar.com\/privacy-policy\/\">Privacy Policy<\/a>.<\/p>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"customerName\">Customer name<\/label>\n          <input type=\"text\" id=\"customerName\" class=\"input\" placeholder=\"Enter customer name\" autocomplete=\"name\" \/>\n        <\/div>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"companyName\">Company name<\/label>\n          <input type=\"text\" id=\"companyName\" class=\"input\" placeholder=\"Enter company name\" autocomplete=\"organization\" \/>\n        <\/div>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"email\">Email<\/label>\n          <input type=\"email\" id=\"email\" class=\"input\" placeholder=\"Enter email\" autocomplete=\"email\" \/>\n        <\/div>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"phone\">Phone number<\/label>\n          <input type=\"tel\" id=\"phone\" class=\"input\" placeholder=\"Enter phone number\" autocomplete=\"tel\" \/>\n        <\/div>\n        <div class=\"field-group\">\n          <label class=\"field-label\" for=\"discount\">Discount (%)<\/label>\n          <input type=\"number\" id=\"discount\" name=\"discount\" class=\"input\" value=\"0\" min=\"0\" max=\"100\" step=\"1\" \/>\n          <p class=\"help-text\">Enter a number between 0\u2013100 for an estimate or contact us for a tailored quote.<\/p>\n        <\/div>\n        <div id=\"buttonContainer\">\n          <button id=\"calculateButton\" type=\"submit\" class=\"button primary\">Calculate<\/button>\n          <button id=\"saveAsPDF\" type=\"button\" class=\"button secondary\">Save result as PDF<\/button>\n        <\/div>\n      <\/section>\n    <\/form>\n\n    <section id=\"resultsSection\" class=\"section-card\">\n      <div id=\"result\"><\/div>\n      <div id=\"result2\"><\/div>\n      <div id=\"warning0\" class=\"warning\" role=\"alert\"><\/div>\n      <div id=\"warningSpan\" class=\"warning\" role=\"alert\"><\/div>\n      <div class=\"table-wrapper\">\n        <div class=\"table-scroll\">\n          <table id=\"resultTable\"><\/table>\n        <\/div>\n      <\/div>\n      <div id=\"suggestUserToSendEmail\" class=\"help-text\"><\/div>\n      <button id=\"saveAsCSV\" type=\"button\" class=\"button secondary\">Save quotation<\/button>\n      <div class=\"table-wrapper\">\n        <div class=\"table-scroll\">\n          <table id=\"resultTable2\"><\/table>\n        <\/div>\n      <\/div>\n      <div id=\"warning1\" class=\"warning\" role=\"alert\"><\/div>\n      <div id=\"warning2\" class=\"warning\" role=\"alert\"><\/div>\n    <\/section>\n    <\/main>\n  <\/div>\n\n  <div id=\"calculatingIndicator\">\n    <span id=\"calculatingText\">Lasketaan...<\/span>\n  <\/div>\n\n  <script src=\"https:\/\/ajax.googleapis.com\/ajax\/libs\/jquery\/3.5.1\/jquery.min.js\"><\/script>\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jspdf\/2.4.0\/jspdf.umd.min.js\"><\/script>\n  <script>\n\/\/ \/\/ Handle change event for the row count dropdown\n\/\/ $(\"#numRows\").change(function () {\n\/\/   var rowCount = $(this).val();\n\/\/   \/\/ Clear the current panel amount inputs and create new ones based on the selected row count\n\/\/   $(\"#panelAmounts\").html(\"\");\n\/\/   for (var i = 0; i < rowCount; i++) {\n\/\/     $(\"#panelAmounts\").append(\n\/\/       \"Row \" + (i + 1) + ': <input type=\"number\" id=\"panelAmount' + i + '\"><br>'\n\/\/     );\n\/\/   }\n\/\/ });\n\n'use strict';\n\nconst rowCountElement = document.getElementById(\"numRows\");\nconst rowInputsContainer = document.getElementById(\"rowInputs\");\nconst panelRowsStepper = document.getElementById(\"panelRowsStepper\");\nconst panelRowsInput = document.getElementById(\"panelRowsInput\");\nconst saveCsvButton = document.getElementById(\"saveAsCSV\");\nconst profileSpanModeRadios = document.querySelectorAll('input[name=\"profileSpanMode\"]');\nconst profileSpanCustomInput = document.getElementById(\"profileSpanCustom\");\nconst profileSpanCustomField = document.getElementById(\"profileSpanCustomField\");\n\/\/ Start safely grabbing elements (some may not exist yet in all contexts)\nconst profileSpacingBlock = document.querySelector('.profile-spacing-block');\nconst profileSpacingVisual = document.getElementById(\"profileSpacingVisual\");\nconst profileSpacingImage = document.getElementById(\"profileSpacingImage\");\nlet customSpanWarning = \"\";\n\nconst roofTypeRadios = document.querySelectorAll('input[name=\"roofType\"]');\n\nconst currencyFormatter = new Intl.NumberFormat(\"fi-FI\", {\n  minimumFractionDigits: 2,\n  maximumFractionDigits: 2,\n});\n\nconst formatCurrency = (value) => currencyFormatter.format(value);\n\nconst applyDiscount = (price, discountPercent) =>\n  price * (1 - discountPercent \/ 100);\n\nlet recommendedProfileSpan = 1500;\nconst PROFILE_SPAN_MIN = 0;\nconst PROFILE_SPAN_MAX = 5000;\n\nfunction setWarning(elementId, content) {\n  const element = document.getElementById(elementId);\n  if (!element) {\n    return;\n  }\n  if (elementId === \"warningSpan\") {\n    customSpanWarning = content || \"\";\n  }\n\n  if (!content) {\n    element.textContent = \"\";\n    element.style.display = \"none\";\n  } else {\n    element.innerHTML = content;\n    element.style.display = \"block\";\n  }\n}\n\nfunction syncProfileSpanControls() {\n  const mode = document.querySelector('input[name=\"profileSpanMode\"]:checked')?.value || 'auto';\n  if (profileSpanCustomInput) {\n    const isCustom = mode === 'custom';\n    profileSpanCustomInput.disabled = !isCustom;\n    if (profileSpanCustomField) {\n      profileSpanCustomField.classList.toggle('disabled', !isCustom);\n      profileSpanCustomField.style.display = isCustom ? 'grid' : 'none';\n      profileSpanCustomField.setAttribute('aria-hidden', isCustom ? 'false' : 'true');\n    }\n    if (!isCustom) {\n      profileSpanCustomInput.setAttribute('aria-disabled', 'true');\n      profileSpanCustomInput.value = String(recommendedProfileSpan);\n    } else {\n      profileSpanCustomInput.removeAttribute('aria-disabled');\n      if (!profileSpanCustomInput.value) {\n        profileSpanCustomInput.value = recommendedProfileSpan;\n      }\n    }\n    profileSpanCustomInput.placeholder = recommendedProfileSpan;\n  }\n}\n\nfunction getSelectedRoofType() {\n  const selected = document.querySelector('input[name=\"roofType\"]:checked');\n  return selected ? selected.value : null;\n}\n\nfunction updateProfileSpacingVisual(selectedRoofType) {\n  if (!profileSpacingBlock) {\n    return;\n  }\n\n  const roofTypeValue = selectedRoofType || getSelectedRoofType();\n  const selectedRadio = roofTypeValue\n    ? document.querySelector(`input[name=\"roofType\"][value=\"${roofTypeValue}\"]`)\n    : null;\n  const imageUrl = selectedRadio?.dataset?.image ||\n    (roofTypeValue ? `https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/${roofTypeValue}.png` : \"\");\n\n  profileSpacingBlock.style.display = 'block';\n\n  if (!roofTypeValue || !imageUrl) {\n    if (profileSpacingVisual) {\n      profileSpacingVisual.style.display = 'none';\n      profileSpacingVisual.setAttribute('aria-hidden', 'true');\n    }\n    return;\n  }\n\n  if (profileSpacingImage && profileSpacingVisual) {\n    profileSpacingImage.src = imageUrl;\n    profileSpacingImage.alt = `Profile spacing illustration for roof type ${roofTypeValue}`;\n    profileSpacingVisual.style.display = 'block';\n    profileSpacingVisual.setAttribute('aria-hidden', 'false');\n  }\n}\n\nfunction syncRowCountDisplay() {\n  if (panelRowsInput) {\n    panelRowsInput.value = rowCountElement.value;\n  }\n}\n\nfunction renderPanelPreview(inputId, total) {\n  const preview = document.getElementById(`${inputId}-preview`);\n  const numberInput = document.getElementById(`${inputId}-input`);\n  const hiddenInput = document.getElementById(inputId);\n  if (!preview || !numberInput || !hiddenInput) {\n    return;\n  }\n\n  const count = Math.max(1, Math.min(100, Number(total) || 1));\n  numberInput.value = String(count);\n  hiddenInput.value = String(count);\n  const maxPreviewTiles = 48;\n  const fragment = document.createDocumentFragment();\n\n  const tilesToRender = Math.min(count, maxPreviewTiles);\n\n  for (let panelIndex = 1; panelIndex <= tilesToRender; panelIndex += 1) {\n    const cell = document.createElement('div');\n    cell.className = 'panel-preview-cell';\n    cell.innerHTML = `<span>${panelIndex}<\/span>`;\n    fragment.appendChild(cell);\n  }\n\n  if (count > maxPreviewTiles) {\n    const moreCell = document.createElement('div');\n    moreCell.className = 'panel-preview-cell panel-preview-more';\n    moreCell.innerHTML = `<span>+${count - maxPreviewTiles}<\/span>`;\n    fragment.appendChild(moreCell);\n  }\n\n  preview.innerHTML = '';\n  preview.appendChild(fragment);\n}\n\nfunction updatePanelRowValue(inputId, delta) {\n  const numberInput = document.getElementById(`${inputId}-input`);\n  const hiddenInput = document.getElementById(inputId);\n  if (!numberInput || !hiddenInput) {\n    return;\n  }\n  const current = Number(numberInput.value || 0);\n  let next = current + delta;\n  next = Math.max(1, Math.min(100, next));\n  if (next === current) {\n    return;\n  }\n  numberInput.value = String(next);\n  hiddenInput.value = String(next);\n  renderPanelPreview(inputId, next);\n}\n\nfunction adjustRowCount(delta) {\n  const current = Number(rowCountElement.value || 0);\n  let next = current + delta;\n  next = Math.max(0, Math.min(30, next));\n  if (next === current) {\n    return;\n  }\n  rowCountElement.value = String(next);\n  updateRowInputs();\n}\n\nfunction updateRowInputs() {\n  const rowCount = Number(rowCountElement.value || 0);\n  syncRowCountDisplay();\n  const existingValues = [];\n  rowInputsContainer\n    .querySelectorAll('input[type=\"hidden\"][id^=\"row\"]')\n    .forEach((input) => {\n      existingValues.push(Number(input.value) || 1);\n    });\n\n  rowInputsContainer.innerHTML = '';\n\n  if (!rowCount) {\n    rowInputsContainer.innerHTML = '<div class=\"panel-placeholder\">Use the controls above to add panel rows.<\/div>';\n    return;\n  }\n\n  for (let index = 0; index < rowCount; index += 1) {\n    const inputId = `row${index}`;\n    const initialValue = existingValues[index] ?? 1;\n    const card = document.createElement('div');\n    card.className = 'panel-row-card';\n    card.innerHTML = `\n      <div class=\"panel-row-header\">\n        <span class=\"panel-row-title\">Row ${index + 1}<\/span>\n        <div class=\"panel-stepper\" data-input=\"${inputId}\">\n          <button type=\"button\" class=\"panel-stepper-btn\" data-action=\"decrement\" aria-label=\"Remove panel from row ${index + 1}\">-<\/button>\n          <input type=\"number\" class=\"panel-stepper-input\" id=\"${inputId}-input\" data-input=\"${inputId}\" value=\"${initialValue}\" min=\"1\" max=\"100\" step=\"1\" aria-label=\"Panels in row ${index + 1}\">\n          <span class=\"panel-stepper-unit\">pcs<\/span>\n          <button type=\"button\" class=\"panel-stepper-btn\" data-action=\"increment\" aria-label=\"Add panel to row ${index + 1}\">+<\/button>\n        <\/div>\n      <\/div>\n      <input type=\"hidden\" id=\"${inputId}\" name=\"${inputId}\" value=\"${initialValue}\">\n      <div class=\"panel-preview\" id=\"${inputId}-preview\"><\/div>\n    `;\n\n    rowInputsContainer.appendChild(card);\n    renderPanelPreview(inputId, initialValue);\n  }\n}\n\nrowInputsContainer.addEventListener('click', (event) => {\n  const button = event.target.closest('.panel-stepper-btn');\n  if (!button) {\n    return;\n  }\n  const wrapper = button.closest('.panel-stepper');\n  if (!wrapper) {\n    return;\n  }\n  const inputId = wrapper.getAttribute('data-input');\n  if (!inputId) {\n    return;\n  }\n  const delta = button.dataset.action === 'increment' ? 1 : -1;\n  updatePanelRowValue(inputId, delta);\n});\n\nrowInputsContainer.addEventListener('input', (event) => {\n  const numberInput = event.target.closest('.panel-stepper-input');\n  if (!numberInput) {\n    return;\n  }\n  const inputId = numberInput.dataset.input;\n  if (!inputId) {\n    return;\n  }\n  let value = Number(numberInput.value);\n  if (!Number.isFinite(value)) {\n    value = 1;\n  }\n  value = Math.round(Math.max(1, Math.min(100, value)));\n  numberInput.value = value;\n  renderPanelPreview(inputId, value);\n});\n\nif (panelRowsStepper) {\n  panelRowsStepper.addEventListener('click', (event) => {\n    const button = event.target.closest('.panel-stepper-btn');\n    if (!button) {\n      return;\n    }\n    const delta = button.dataset.action === 'increment' ? 1 : -1;\n    adjustRowCount(delta);\n  });\n}\n\nif (panelRowsInput) {\n  panelRowsInput.addEventListener('input', () => {\n    let value = Number(panelRowsInput.value);\n    if (!Number.isFinite(value)) {\n      value = 0;\n    }\n    value = Math.round(Math.max(0, Math.min(30, value)));\n    panelRowsInput.value = value;\n    rowCountElement.value = String(value);\n    updateRowInputs();\n  });\n}\n\nprofileSpanModeRadios.forEach((radio) => {\n  radio.addEventListener('change', () => {\n    syncProfileSpanControls();\n    setWarning('warningSpan', '');\n  });\n});\n\nroofTypeRadios.forEach((radio) => {\n  radio.addEventListener('change', () => {\n    updateProfileSpacingVisual(radio.value);\n    syncProfileSpanControls();\n  });\n});\n\nif (profileSpanCustomInput) {\n  profileSpanCustomInput.addEventListener('input', () => {\n    if (profileSpanCustomInput.disabled) {\n      return;\n    }\n    let value = Number(profileSpanCustomInput.value);\n    if (!Number.isFinite(value)) {\n      value = recommendedProfileSpan;\n    }\n    value = Math.round(Math.max(PROFILE_SPAN_MIN, Math.min(PROFILE_SPAN_MAX, value)));\n    profileSpanCustomInput.value = value;\n  });\n}\n\nrowCountElement.addEventListener('change', updateRowInputs);\nsyncRowCountDisplay();\nupdateRowInputs();\nsyncProfileSpanControls();\nupdateProfileSpacingVisual(getSelectedRoofType());\n\nif (saveCsvButton) {\n  saveCsvButton.addEventListener(\"click\", downloadTableAsCSV);\n}\n\n\/\/ Function to validate user input\nfunction validateInput() {\n  const angleInput = $(\"#A\").val();\n  const angle = angleInput === \"\" ? null : Number(angleInput);\n  const angleInvalid =\n    angle === null || Number.isNaN(angle) || angle <= 0 || angle >= 90;\n\n  const lengthInput = $(\"#roofLength\").val();\n  const heightInput = $(\"#roofHeight\").val();\n  const length = lengthInput === \"\" ? null : Number(lengthInput);\n  const height = heightInput === \"\" ? null : Number(heightInput);\n  const lengthInvalid =\n    length === null || Number.isNaN(length) || length <= 0;\n  const heightInvalid =\n    height === null || Number.isNaN(height) || height <= 0;\n\n  if (angleInvalid && (lengthInvalid || heightInvalid)) {\n    alert(\n      \"Sy\u00f6t\u00e4 joko kelvollinen kattokulma tai sek\u00e4 katon pituus ett\u00e4 korkeus.\"\n    );\n    return false;\n  }\n\n  const panelWidthInput = $(\"#PW\").val();\n  const panelLengthInput = $(\"#PL\").val();\n  const panelWidth = Number(panelWidthInput);\n  const panelLength = Number(panelLengthInput);\n\n  if (panelWidthInput === \"\" || Number.isNaN(panelWidth) || panelWidth <= 0) {\n    alert(\"Sy\u00f6t\u00e4 kelvollinen aurinkopaneelin leveys.\");\n    return false;\n  }\n  if (panelLengthInput === \"\" || Number.isNaN(panelLength) || panelLength <= 0) {\n    alert(\"Sy\u00f6t\u00e4 kelvollinen aurinkopaneelin pituus.\");\n    return false;\n  }\n\n  if (!$('input[name=\"roofType\"]:checked').val()) {\n    alert(\"Ole hyv\u00e4 ja valitse kattotyyppi.\");\n    return false;\n  }\n\n  const numRows = Number($(\"#numRows\").val());\n  if (!Number.isFinite(numRows) || numRows <= 0) {\n    alert(\n      \"Ole hyv\u00e4 ja m\u00e4\u00e4rit\u00e4, kuinka monta rivi\u00e4 aurinkopaneeleja aiot asentaa.\"\n    );\n    return false;\n  }\n\n  for (let i = 0; i < numRows; i += 1) {\n    const rowValue = Number($(`#row${i}`).val());\n    if (!Number.isFinite(rowValue) || rowValue <= 0) {\n      alert(`Ole hyv\u00e4 ja m\u00e4\u00e4rit\u00e4 aurinkopaneelien m\u00e4\u00e4r\u00e4 rivill\u00e4 ${i + 1}`);\n      return false;\n    }\n  }\n\n  const name = $(\"#customerName\").val().trim();\n  const company = $(\"#companyName\").val().trim();\n  const email = $(\"#email\").val().trim();\n  const phone = $(\"#phone\").val().trim();\n  const discountInput = $(\"#discount\").val();\n\n  if (!name || !company || !email || !phone || discountInput === \"\") {\n    alert(\"Anna voimassa olevat asiakastiedot.\");\n    return false;\n  }\n\n  return true;\n}\n\nfunction getNextEvenNumber(number) {\n  \/\/ Round the number up to the nearest integer\n  const roundedNumber = Math.ceil(number);\n\n  if (roundedNumber % 2 === 0) {\n    \/\/ If the rounded number is already even, increment it by 2 to get the next even number\n    return roundedNumber + 2;\n  } else {\n    \/\/ If the rounded number is odd, increment it by 1 to make it even, then add another 1 to get the next even number\n    return roundedNumber + 1;\n  }\n}\n\nconst productTotals = {\n  PR11: 0,\n  PR12: 0,\n  CL11: 0,\n  CL02: 0,\n  RB11: 0,\n  RB12: 0,\n  RB13: 0,\n  RB04: 0,\n  RB15: 0,\n  RB06: 0,\n  RB17: 0,\n  BN01: 0,\n  RS01: 0,\n  RS02: 0,\n  SS05: 0,\n  SS06: 0,\n};\n\nlet fixtures = {};\nlet totalPanelAmount = 0;\n\nconst productData = [\n  {\n    code: \"PR11\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/PR11.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/pr01\/\",\n    unitText: \"kpl\",\n    unitNumber: 1,\n    unit: \"kpl\",\n    price: 54.95,\n  },\n  {\n    code: \"PR12\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/PR12.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/pr02\/\",\n    unitText: \"pussia (10 kpl\/pussi)\",\n    unitNumber: 10,\n    unit: \"pussia\",\n    price: 30,\n  },\n  {\n    code: \"CL11\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/CL11.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/cl01\/\",\n    unitText: \"pussia (10 kpl\/pussi)\",\n    unitNumber: 10,\n    unit: \"pussia\",\n    price: 26.15,\n  },\n  {\n    code: \"CL02\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/CL02.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/cl02\/\",\n    unitText: \"pussia (8 kpl\/pussi)\",\n    unitNumber: 8,\n    unit: \"pussia\",\n    price: 22.27,\n  },\n  {\n    code: \"RB11\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/RB11.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/rb01\/\",\n    unitText: \"kpl\",\n    unitNumber: 1,\n    unit: \"kpl\",\n    price: 9.45,\n  },\n  {\n    code: \"RB12\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/RB12.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/rb02\/\",\n    unitText: \"kpl\",\n    unitNumber: 1,\n    unit: \"kpl\",\n    price: 4.95,\n  },\n  {\n    code: \"RB13\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/RB13.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/rb03\/\",\n    unitText: \"kpl\",\n    unitNumber: 1,\n    unit: \"kpl\",\n    price: 5.85,\n  },\n  {\n    code: \"RB04\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/RB04.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/rb04\/\",\n    unitText: \"kpl\",\n    unitNumber: 1,\n    unit: \"kpl\",\n    price: 7.73,\n  },\n  {\n    code: \"RB15\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/RB15.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/rb05\/\",\n    unitText: \"kpl\",\n    unitNumber: 1,\n    unit: \"kpl\",\n    price: 9.85,\n  },\n  {\n    code: \"RB06\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/RB06.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/rb06\/\",\n    unitText: \"kpl\",\n    unitNumber: 1,\n    unit: \"kpl\",\n    price: 2.79,\n  },\n  {\n    code: \"RB17\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/RB17.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/rb17\/\",\n    unitText: \"kpl\",\n    unitNumber: 1,\n    unit: \"kpl\",\n    price: 9.21,\n  },\n  {\n    code: \"BN01\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/08\/BN01.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/bn01\/\",\n    unitText: \"pussia (50 kpl\/pussi)\",\n    unitNumber: 50,\n    unit: \"pussia\",\n    price: 47,\n  },\n  {\n    code: \"RS01\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/08\/RS01.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/rs01\/\",\n    unitText: \"pussia (100 kpl\/pussi)\",\n    unitNumber: 100,\n    unit: \"pussia\",\n    price: 30,\n  },\n  {\n    code: \"RS02\",\n    total: 0,\n    imageUrl: \"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/08\/RS02.jpg\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/rs02\/\",\n    unitText: \"pussia (50 kpl\/pussi)\",\n    unitNumber: 50,\n    unit: \"pussia\",\n    price: 30,\n  },\n  {\n    code: \"SS05\",\n    total: 0,\n    imageUrl: \"\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/ss05\/\",\n    unitText: \"kpl\",\n    unitNumber: 1,\n    unit: \"kpl\",\n    price: 0.448,\n  },\n  {\n    code: \"SS06\",\n    total: 0,\n    imageUrl: \"\",\n    url: \"https:\/\/moisolar.com\/fi\/tuote\/ss06\/\",\n    unitText: \"pussia (100 kpl\/pussi)\",\n    unitNumber: 100,\n    unit: \"pussia\",\n    price: 31.2,\n  },\n];\n\nfunction scrollToResults() {\n  const element = document.getElementById(\"result\");\n  const elementPosition = element.getBoundingClientRect().top + window.scrollY;\n  window.scrollTo({\n    top: elementPosition,\n    behavior: \"smooth\",\n  });\n}\n\n\/\/ Object mapping cities to their snow load values\nconst snowLoadValues = {\n  Akaa: 2.3,\n  Alaja\u0308rvi: 2.5,\n  Alavieska: 2.0,\n  Alavus: 2.5,\n  Asikkala: 2.5,\n  Askola: 2.7,\n  Aura: 2.5,\n  Bra\u0308ndo\u0308: 2.0,\n  Eckero\u0308: 2.0,\n  Enonkoski: 2.5,\n  Enontekio\u0308: 3.5,\n  Espoo: 2.75,\n  Eura: 2.1,\n  Eurajoki: 2.0,\n  Evija\u0308rvi: 2.2,\n  Finstro\u0308m: 2.0,\n  Forssa: 2.6,\n  Fo\u0308glo\u0308: 2.0,\n  Geta: 2.0,\n  Haapaja\u0308rvi: 2.5,\n  Haapavesi: 2.4,\n  Hailuoto: 2.4,\n  Halsua: 2.4,\n  Hamina: 2.75,\n  Hammarland: 2.0,\n  Hankasalmi: 2.5,\n  Hanko: 2.5,\n  Harjavalta: 2.0,\n  Hartola: 2.5,\n  Hattula: 2.5,\n  Hausja\u0308rvi: 2.75,\n  Heinola: 2.5,\n  Heina\u0308vesi: 2.5,\n  Helsinki: 2.75,\n  Hirvensalmi: 2.5,\n  Hollola: 2.75,\n  Honkajoki: 2.5,\n  Huittinen: 2.0,\n  Humppila: 2.25,\n  Hyrynsalmi: 3.5,\n  \"Hyvinka\u0308a\u0308 \": 2.75,\n  \"Ha\u0308meenkyro\u0308 \": 2.5,\n  \"Ha\u0308meenlinna \": 2.5,\n  \"Ii  \": 2.8,\n  \"Iisalmi \": 2.6,\n  \"Iitti \": 2.5,\n  \"Ikaalinen \": 2.5,\n  \"Ilmajoki \": 2.5,\n  \"Ilomantsi \": 2.75,\n  Imatra: 2.75,\n  \"Inari \": 3.0,\n  Inkoo: 2.75,\n  Isojoki: 2.5,\n  Isokyro\u0308: 2.3,\n  Janakkala: 2.75,\n  Joensuu: 2.6,\n  Jokioinen: 2.6,\n  Jomala: 2.0,\n  Joroinen: 2.5,\n  Joutsa: 2.5,\n  Juuka: 2.75,\n  Juupajoki: 2.5,\n  Juva: 2.5,\n  Jyva\u0308skyla\u0308: 2.5,\n  Ja\u0308mija\u0308rvi: 2.5,\n  Ja\u0308msa\u0308: 2.5,\n  Ja\u0308rvenpa\u0308a\u0308: 2.75,\n  Kaarina: 2.5,\n  Kaavi: 2.65,\n  Kajaani: 2.75,\n  Kalajoki: 2.0,\n  Kangasala: 2.5,\n  Kangasniemi: 2.5,\n  Kankaanpa\u0308a\u0308: 2.5,\n  Kannonkoski: 2.5,\n  Kannus: 2.1,\n  Karijoki: 2.5,\n  Karkkila: 2.75,\n  Karstula: 2.5,\n  Karvia: 2.5,\n  Kaskinen: 2.0,\n  Kauhajoki: 2.5,\n  Kauhava: 2.3,\n  Kauniainen: 2.75,\n  Kaustinen: 2.2,\n  \"Keitele \": 2.5,\n  Kemi: 3.0,\n  Kemija\u0308rvi: 3.0,\n  Keminmaa: 3.0,\n  Kemio\u0308nsaari: 2.5,\n  Kempele: 2.25,\n  Kerava: 2.75,\n  Keuruu: 2.5,\n  Kihnio\u0308: 2.5,\n  Kinnula: 2.5,\n  Kirkkonummi: 2.75,\n  Kitee: 2.75,\n  Kittila\u0308: 3.0,\n  Kiuruvesi: 2.55,\n  Kivija\u0308rvi: 2.5,\n  Kokema\u0308ki: 2.1,\n  Kokkola: 2.0,\n  Kolari: 3.0,\n  Konnevesi: 2.5,\n  Kontiolahti: 2.75,\n  Korsna\u0308s: 2.0,\n  \"Koski Tl\": 2.6,\n  Kotka: 2.65,\n  Kouvola: 2.5,\n  Kristiinankaupunki: 2.5,\n  Kruunupyy: 2.2,\n  Kuhmo: 3.0,\n  Kuhmoinen: 2.5,\n  Kumlinge: 2.0,\n  Kuopio: 2.5,\n  Kuortane: 2.5,\n  Kurikka: 2.5,\n  Kustavi: 2.1,\n  Kuusamo: 3.5,\n  Kyyja\u0308rvi: 2.5,\n  Ka\u0308rko\u0308la\u0308: 2.75,\n  Ka\u0308rsa\u0308ma\u0308ki: 2.5,\n  Ko\u0308kar: 2.0,\n  Lahti: 2.65,\n  Laihia: 2.3,\n  Laitila: 2.2,\n  Lapinja\u0308rvi: 2.5,\n  Lapinlahti: 2.6,\n  Lappaja\u0308rvi: 2.4,\n  Lappeenranta: 2.75,\n  Lapua: 2.5,\n  \"Laukaa \": 2.5,\n  Lemi: 2.7,\n  Lemland: 2.0,\n  Lempa\u0308a\u0308la\u0308: 2.4,\n  Leppa\u0308virta: 2.5,\n  Lestija\u0308rvi: 2.5,\n  Lieksa: 2.75,\n  Lieto: 2.6,\n  Liminka: 2.45,\n  Liperi: 2.55,\n  Lohja: 2.75,\n  Loimaa: 2.65,\n  Loppi: 2.75,\n  Loviisa: 2.5,\n  Luhanka: 2.5,\n  Lumijoki: 2.25,\n  Lumparland: 2.0,\n  Luoto: 2.0,\n  Luuma\u0308ki: 2.75,\n  Maalahti: 2.0,\n  Maarianhamina: 2.0,\n  Marttila: 2.6,\n  Masku: 2.3,\n  Merija\u0308rvi: 2.0,\n  Merikarvia: 2.4,\n  Miehikka\u0308la\u0308: 2.75,\n  Mikkeli: 2.5,\n  Muhos: 2.5,\n  Multia: 2.5,\n  Muonio: 3.0,\n  Mustasaari: 2.0,\n  Muurame: 2.5,\n  Myna\u0308ma\u0308ki: 2.3,\n  Myrskyla\u0308: 2.65,\n  Ma\u0308ntsa\u0308la\u0308: 2.75,\n  \"Ma\u0308ntta\u0308\u2010Vilppula\": 2.5,\n  Ma\u0308ntyharju: 2.5,\n  Naantali: 2.5,\n  Nakkila: 2.0,\n  Nivala: 2.35,\n  Nokia: 2.45,\n  Nousiainen: 2.3,\n  Nurmes: 3.0,\n  Nurmija\u0308rvi: 2.75,\n  Na\u0308rpio\u0308: 2.1,\n  \"Orimattila \": 2.75,\n  \"Oripa\u0308a\u0308 \": 2.2,\n  Orivesi: 2.5,\n  Oulainen: 2.15,\n  Oulu: 2.45,\n  Outokumpu: 2.6,\n  Padasjoki: 2.5,\n  Paimio: 2.6,\n  Paltamo: 3.3,\n  Parainen: 2.5,\n  Parikkala: 2.75,\n  Parkano: 2.5,\n  \"Pederso\u0308ren kunta\": 2.1,\n  Pelkosenniemi: 2.9,\n  Pello: 3.0,\n  Perho: 2.5,\n  Pertunmaa: 2.5,\n  Peta\u0308ja\u0308vesi: 2.5,\n  Pieksa\u0308ma\u0308ki: 2.5,\n  Pielavesi: 2.5,\n  Pietarsaari: 2.0,\n  Pihtipudas: 2.5,\n  Pirkkala: 2.4,\n  Polvija\u0308rvi: 2.75,\n  Pomarkku: 2.4,\n  Pori: 2.0,\n  Pornainen: 2.75,\n  Porvoo: 2.65,\n  Posio: 3.5,\n  Pudasja\u0308rvi: 3.5,\n  Pukkila: 2.75,\n  Punkalaidun: 2.1,\n  Puolanka: 3.5,\n  Puumala: 2.5,\n  Pyhta\u0308a\u0308: 2.5,\n  Pyha\u0308joki: 2.05,\n  Pyha\u0308ja\u0308rvi: 2.5,\n  Pyha\u0308nta\u0308: 2.75,\n  Pyha\u0308ranta: 2.1,\n  Pa\u0308lka\u0308ne: 2.5,\n  Po\u0308ytya\u0308: 2.5,\n  Raahe: 2.1,\n  Raasepori: 2.75,\n  Raisio: 2.5,\n  Rantasalmi: 2.5,\n  Ranua: 3.2,\n  \"Rauma \": 2.0,\n  Rautalampi: 2.5,\n  Rautavaara: 3.0,\n  Rautja\u0308rvi: 2.75,\n  Reisja\u0308rvi: 2.5,\n  Riihima\u0308ki: 2.75,\n  Ristija\u0308rvi: 3.5,\n  Rovaniemi: 3.0,\n  Ruokolahti: 2.75,\n  Ruovesi: 2.5,\n  Rusko: 2.5,\n  Ra\u0308a\u0308kkyla\u0308: 2.6,\n  Saarija\u0308rvi: 2.5,\n  Salla: 3.0,\n  Salo: 2.7,\n  Saltvik: 2.0,\n  Sastamala: 2.5,\n  Sauvo: 2.6,\n  Savitaipale: 2.65,\n  Savonlinna: 2.5,\n  Savukoski: 3.0,\n  Seina\u0308joki: 2.5,\n  Sievi: 2.35,\n  Siikainen: 2.5,\n  Siikajoki: 2.25,\n  Siikalatva: 2.5,\n  Siilinja\u0308rvi: 2.5,\n  Simo: 3.0,\n  Sipoo: 2.75,\n  Siuntio: 2.75,\n  Sodankyla\u0308: 3.0,\n  Soini: 2.5,\n  Somero: 2.75,\n  Sonkaja\u0308rvi: 3.0,\n  Sotkamo: 3.4,\n  Sottunga: 2.0,\n  Sulkava: 2.5,\n  Sund: 2.0,\n  Suomussalmi: 3.5,\n  Suonenjoki: 2.5,\n  Sysma\u0308: 2.5,\n  Sa\u0308kyla\u0308: 2.1,\n  Taipalsaari: 2.7,\n  Taivalkoski: 3.5,\n  Taivassalo: 2.2,\n  Tammela: 2.75,\n  Tampere: 2.5,\n  \"Tervo \": 2.5,\n  Tervola: 3.0,\n  Teuva: 2.5,\n  Tohmaja\u0308rvi: 2.75,\n  Toholampi: 2.25,\n  Toivakka: 2.5,\n  Tornio: 3.0,\n  Turku: 2.5,\n  Tuusniemi: 2.5,\n  Tuusula: 2.75,\n  Tyrna\u0308va\u0308: 2.4,\n  Ulvila: 2.0,\n  Urjala: 2.4,\n  Utaja\u0308rvi: 2.85,\n  Utsjoki: 2.5,\n  Uurainen: 2.5,\n  Uusikaarlepyy: 2.0,\n  Uusikaupunki: 2.1,\n  Vaala: 2.75,\n  Vaasa: 2.0,\n  Valkeakoski: 2.4,\n  Valtimo: 3.0,\n  Vantaa: 2.75,\n  Varkaus: 2.5,\n  Vehmaa: 2.2,\n  Vesanto: 2.5,\n  Vesilahti: 2.3,\n  Veteli: 2.3,\n  Vierema\u0308: 2.85,\n  Vihti: 2.75,\n  Viitasaari: 2.5,\n  Vimpeli: 2.45,\n  Virolahti: 2.75,\n  Virrat: 2.5,\n  Va\u030ardo\u0308: 2.0,\n  Vo\u0308yri: 2.1,\n  Ylitornio: 3.0,\n  Ylivieska: 2.15,\n  Ylo\u0308ja\u0308rvi: 2.5,\n  Ypa\u0308ja\u0308: 2.6,\n  A\u0308hta\u0308ri: 2.5,\n  \"A\u0308a\u0308nekoski \": 2.5,\n};\n\nlet snowLoadPerPanel = 0;\nlet snowLoadperProfile = 0;\nlet profileSpan = 1500;\n\nfunction calculateSnowLoad(roofTypeValue) {\n  const city = document.getElementById(\"city\").value;\n  const designSnowLoad = snowLoadValues[city];\n  if (typeof designSnowLoad !== \"number\") {\n    console.warn(`No snow load value configured for city: ${city}`);\n    return;\n  }\n  updateProfileSpacingVisual(roofTypeValue);\n  const roofLengthValue = document.getElementById(\"roofLength\").value;\n  const roofHeightValue = document.getElementById(\"roofHeight\").value;\n  const panelWidth = Number(document.getElementById(\"PW\").value) \/ 1000;\n  const panelLength = Number(document.getElementById(\"PL\").value) \/ 1000;\n\n  const roofLength = roofLengthValue === \"\" ? null : Number(roofLengthValue);\n  const roofHeight = roofHeightValue === \"\" ? null : Number(roofHeightValue);\n\n  let angle = document.getElementById(\"A\").value;\n  if (angle === \"\" && roofLength && roofHeight) {\n    angle = Math.atan(roofHeight \/ roofLength) * (180 \/ Math.PI);\n  } else {\n    angle = Number(angle);\n  }\n\n  const shapeCoefficient = Math.min(0.8, (0.8 * (60 - angle)) \/ 30);\n  const snowLoadOnRoof = shapeCoefficient * designSnowLoad;\n\n  snowLoadPerPanel = snowLoadOnRoof * panelWidth * panelLength;\n  snowLoadperProfile = snowLoadPerPanel \/ 2;\n\n  let maxProfileSpan = PROFILE_SPAN_MAX;\n  if (roofTypeValue === \"6\") {\n    maxProfileSpan = 900;\n  } else if (roofTypeValue === \"2\") {\n    maxProfileSpan = 1200;\n  } else if (roofTypeValue === \"3\" || roofTypeValue === \"4\") {\n    maxProfileSpan = 1500;\n  }\n\n  if (roofTypeValue === \"1\") {\n    if (snowLoadperProfile < 2.3) {\n      profileSpan = 1500;\n    } else if (snowLoadperProfile < 4) {\n      profileSpan = 1000;\n    } else {\n      profileSpan = 500;\n    }\n  } else if (roofTypeValue === \"5\" || roofTypeValue === \"6\") {\n    if (snowLoadperProfile < 3.1) {\n      profileSpan = 1200;\n    } else if (snowLoadperProfile < 3.5) {\n      profileSpan = 1100;\n    } else if (snowLoadperProfile < 4) {\n      profileSpan = 1000;\n    } else if (snowLoadperProfile < 4.7) {\n      profileSpan = 900;\n    } else {\n      profileSpan = 800;\n    }\n  } else {\n    if (snowLoadperProfile < 2.1) {\n      profileSpan = 1600;\n    } else if (snowLoadperProfile < 2.3) {\n      profileSpan = 1500;\n    } else if (snowLoadperProfile < 2.5) {\n      profileSpan = 1400;\n    } else if (snowLoadperProfile < 2.8) {\n      profileSpan = 1300;\n    } else if (snowLoadperProfile < 3.1) {\n      profileSpan = 1200;\n    } else if (snowLoadperProfile < 3.5) {\n      profileSpan = 1100;\n    } else if (snowLoadperProfile < 4) {\n      profileSpan = 1000;\n    } else if (snowLoadperProfile < 4.7) {\n      profileSpan = 900;\n    } else {\n      profileSpan = 800;\n    }\n  }\n\n  recommendedProfileSpan = Math.min(profileSpan, maxProfileSpan);\n\n  const mode = document.querySelector('input[name=\"profileSpanMode\"]:checked')?.value || 'auto';\n  let selectedProfileSpan = recommendedProfileSpan;\n\n  if (mode === 'custom' && profileSpanCustomInput) {\n    let customValue = Number(profileSpanCustomInput.value);\n    if (!Number.isFinite(customValue) || customValue <= 0) {\n      customValue = recommendedProfileSpan;\n      profileSpanCustomInput.value = recommendedProfileSpan;\n    }\n    customValue = Math.round(Math.max(PROFILE_SPAN_MIN, Math.min(PROFILE_SPAN_MAX, customValue)));\n    profileSpanCustomInput.value = customValue;\n    selectedProfileSpan = customValue;\n  } else if (profileSpanCustomInput) {\n    profileSpanCustomInput.placeholder = recommendedProfileSpan;\n  }\n\n  profileSpan = selectedProfileSpan;\n\n  syncProfileSpanControls();\n\n  if (mode === 'custom' && selectedProfileSpan > recommendedProfileSpan) {\n    setWarning(\n      'warningSpan',\n      `* Antamasi kiinnikev\u00e4li ${selectedProfileSpan} mm ylitt\u00e4\u00e4 suosituksen ${recommendedProfileSpan} mm. Varmista, ett\u00e4 kattorakenne ja kiinnikkeet kest\u00e4v\u00e4t kuormituksen.`\n    );\n  } else {\n    setWarning('warningSpan', '');\n  }\n\n  document.getElementById(\"result\").innerHTML =\n    '<img decoding=\"async\" src=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/snow_panel.png\" alt=\"Snow Load on Solar Panel\">' +\n    '<p style=\"font-size:20px; font-weight:bold; color:#333;\">Lumikuorma aurinkopaneelia kohden: ' +\n    snowLoadPerPanel.toFixed(2) +\n    \" kN.<\/p>\";\n\n  const selectedRadio = document.querySelector(`input[name=\"roofType\"][value=\"${roofTypeValue}\"]`);\n  const spanLabel = mode === 'custom' ? ' (valitsemasi arvo)' : '';\n  const roofTypeImageSrc = selectedRadio?.dataset?.image || `https:\/\/moisolar.com\/wp-content\/uploads\/2024\/02\/${roofTypeValue}.png`;\n\n  document.getElementById(\"result2\").innerHTML =\n    '<div class=\"result-visual-container\">' +\n    `<img decoding=\"async\" src=\"${roofTypeImageSrc}\" alt=\"Profile Span Image\" class=\"result-visual\">` +\n    '<\/div>' +\n    '<p class=\"result-text\">Kahden kattotelineen v\u00e4linen et\u00e4isyys on <strong>' +\n    profileSpan +\n    ` mm.<\/strong>${spanLabel}<\/p>`;\n\n  if (roofTypeValue === \"5\") {\n    setWarning(\n      \"warning0\",\n      \"* Suosittelemme vahvasti, ett\u00e4 k\u00e4yt\u00e4tte enint\u00e4\u00e4n 1200mm kiinnikev\u00e4li\u00e4 tiilikatoilla. Harkitse lis\u00e4ksi ylim\u00e4\u00e4r\u00e4isten kattotelineiden lis\u00e4\u00e4mist\u00e4 alimpaan profiililinjaan. Suosittelemme asentamaan kattokiinnikkeen mahdollisimman l\u00e4helle aurinkopaneelin keskikiinnikkeit\u00e4 (CL11) v\u00e4hent\u00e4\u00e4ksesi kattotiilen vaurioitumisen riski\u00e4. Alin profiililinja voi ylikuormittua kasaantuvan lumen seurauksena. Huonokuntoinen kattotiili voi t\u00e4ll\u00f6in vaurioitua.\"\n    );\n  } else if (roofTypeValue === \"6\") {\n    setWarning(\n      \"warning0\",\n      \"* Suosittelemme vahvasti, ett\u00e4 k\u00e4yt\u00e4tte enint\u00e4\u00e4n 900mm kiinnikev\u00e4li\u00e4 tiilikatoilla. Harkitse lis\u00e4ksi ylim\u00e4\u00e4r\u00e4isten kattotelineiden lis\u00e4\u00e4mist\u00e4 alimpaan profiililinjaan. Suosittelemme asentamaan kattokiinnikkeen mahdollisimman l\u00e4helle aurinkopaneelin keskikiinnikkeit\u00e4 (CL11) v\u00e4hent\u00e4\u00e4ksesi kattotiilen vaurioitumisen riski\u00e4. Alin profiililinja voi ylikuormittua kasaantuvan lumen seurauksena. Huonokuntoinen kattotiili voi t\u00e4ll\u00f6in vaurioitua.\"\n    );\n  } else {\n    setWarning(\"warning0\", \"\");\n  }\n}\nlet productDiscount = 0;\n\nfunction clearAllData() {\n  productTotals.PR11 = 0;\n  productTotals.PR12 = 0;\n  productTotals.CL11 = 0;\n  productTotals.CL02 = 0;\n  productTotals.RB11 = 0;\n  productTotals.RB12 = 0;\n  productTotals.RB13 = 0;\n  productTotals.RB04 = 0;\n  productTotals.RB15 = 0;\n  productTotals.RB06 = 0;\n  productTotals.RB17 = 0;\n  productTotals.BN01 = 0;\n  productTotals.RS01 = 0;\n  productTotals.RS02 = 0;\n  productTotals.SS05 = 0;\n  productTotals.SS06 = 0;\n  fixtures = {};\n  totalPanelAmount = 0;\n  for (let productx of productData) {\n    productx.total = 0;\n  }\n  eachRowData = [];\n  productDiscount = 0;\n  document.getElementById(\"saveAsCSV\").style.display = \"none\";\n  document.getElementById(\"saveAsPDF\").style.display = \"none\";\n  document.getElementById(\"suggestUserToSendEmail\").style.display = \"none\";\n  setWarning(\"warning0\", \"\");\n  setWarning(\"warningSpan\", \"\");\n  setWarning(\"warning1\", \"\");\n  setWarning(\"warning2\", \"\");\n  document.getElementById(\"resultsSection\").style.display = \"none\";\n  syncProfileSpanControls();\n}\n\nlet eachRowData = [];\n\nfunction animateCalculatingText() {\n  const textElement = document.getElementById(\"calculatingText\");\n  let dotCount = 0; \/\/ Initialize dot count for controlling the number of dots\n\n  \/\/ Clear any existing interval to avoid overlapping animations\n  clearInterval(window.calculatingIntervalId);\n\n  window.calculatingIntervalId = setInterval(() => {\n    let dots = \".\".repeat(dotCount % 4); \/\/ Create a string of dots that cycles from 0 to 3\n    textElement.textContent = `Lasketaan${dots}`; \/\/ Update text content with the current number of dots\n    dotCount++; \/\/ Increment dot count for the next cycle\n  }, 500); \/\/ Update text every 500 milliseconds\n}\n\nfunction checkDiscount() {\n  const discount = $(\"#discount\").val();\n  \/\/ Check if discount is not empty and is a number within the specified range\n  if (discount !== \"\" && !isNaN(discount) && discount >= 0 && discount <= 100) {\n    productDiscount = Number(discount);\n  } else {\n    productDiscount = 0;\n  }\n}\n\nfunction renderResultTable() {\n  const resultTable = document.getElementById(\"resultTable\");\n  resultTable.innerHTML = \"\";\n\n  const headerLabels = [\n    \"Tarvittava tuote\",\n    \"Tarvittava m\u00e4\u00e4r\u00e4\",\n    \"Myyntiyksikk\u00f6\",\n    \"Vaadittu yksikk\u00f6\",\n    \"Yksikk\u00f6hinta (\u20ac)\",\n    \"Alennus(%)\",\n    \"Yksikk\u00f6hinta alennuksen j\u00e4lkeen (\u20ac)\",\n    \"Kokonaishinta tuotteelle (\u20ac)\",\n  ];\n\n  const headerRow = document.createElement(\"tr\");\n  headerLabels.forEach((label) => {\n    const th = document.createElement(\"th\");\n    label.split(\" \").forEach((word) => {\n      const line = document.createElement(\"span\");\n      line.className = \"table-header-line\";\n      line.textContent = word;\n      th.appendChild(line);\n    });\n    headerRow.appendChild(th);\n  });\n  resultTable.appendChild(headerRow);\n\n  fixtures = {};\n  let totalPrice = 0;\n\n  productData.forEach((product) => {\n    const quantity = productTotals[product.code] || 0;\n    if (!quantity) {\n      product.total = 0;\n      return;\n    }\n\n    product.total = quantity;\n    fixtures[product.code] = quantity;\n\n    const packagesNeeded = Math.ceil(quantity \/ product.unitNumber);\n    const discountedUnitPrice = applyDiscount(product.price, productDiscount);\n    const lineTotal = packagesNeeded * discountedUnitPrice;\n    const imageHtml = product.imageUrl\n      ? `<a href=\"${product.url}\"><img decoding=\"async\" src=\"${product.imageUrl}\" alt=\"${product.code}\" style=\"display:block; margin-bottom: 5px;\"><\\\/a>`\n      : \"\";\n    const codeLinkHtml = `<a href=\"${product.url}\">${product.code}<\\\/a>`;\n\n    const row = document.createElement(\"tr\");\n    row.innerHTML = `\n      <td>${imageHtml}${codeLinkHtml}<\\\/td>\n      <td>${quantity} kpl<\\\/td>\n      <td>${product.unitText}<\\\/td>\n      <td>${packagesNeeded}<\\\/td>\n      <td>${formatCurrency(product.price)}<\\\/td>\n      <td>${productDiscount}<\\\/td>\n      <td>${formatCurrency(discountedUnitPrice)}<\\\/td>\n      <td>${formatCurrency(lineTotal)}<\\\/td>\n    `;\n    resultTable.appendChild(row);\n\n    totalPrice += lineTotal;\n  });\n\n  const summaryRow = document.createElement(\"tr\");\n  summaryRow.innerHTML = `\n    <td colspan=\"7\">Kokonaishinta (ALV 0%)<\\\/td>\n    <td>${formatCurrency(totalPrice)}<\\\/td>\n  `;\n  resultTable.appendChild(summaryRow);\n\n  return totalPrice;\n}\n\nfunction downloadTableAsCSV() {\n  const csv = [];\n  const rows = document.querySelectorAll(\"#resultTable tr\");\n\n  for (let i = 0; i < rows.length; i++) {\n    const row = [];\n    const cols = rows[i].querySelectorAll(\"td, th\");\n\n    for (let j = 0; j < cols.length; j++) {\n      \/\/ Clean the text and wrap it in quotes to handle commas correctly\n      const text = cols[j].innerText.replace(\/\"\/g, '\"\"').replace(\".\", \",\");\n      row.push('\"' + text + '\"');\n    }\n    csv.push(row.join(\",\"));\n  }\n\n  \/\/ Create a CSV string from the data\n  const csvString = csv.join(\"\\n\");\n  const blob = new Blob([csvString], { type: \"text\/csv;charset=utf-8;\" });\n  const link = document.createElement(\"a\");\n  const url = URL.createObjectURL(blob);\n  const companyName = document.getElementById(\"companyName\").value.trim();\n  const fileName = (companyName || \"quotation\") + \".csv\";\n\n  link.setAttribute(\"href\", url);\n  link.setAttribute(\"download\", fileName);\n  link.style.visibility = \"hidden\";\n\n  document.body.appendChild(link);\n  link.click();\n  document.body.removeChild(link);\n}\n\ndocument\n  .getElementById(\"calculatorForm\")\n  .addEventListener(\"submit\", function (event) {\n    event.preventDefault();\n\n    if (validateInput()) {\n      \/\/Add a claculating... animation\n      \/\/ Show calculating indicator\n      animateCalculatingText();\n      document.getElementById(\"calculatingIndicator\").style.display = \"flex\";\n\n      \/\/ Simulate calculating for 5 to 10 seconds (using 5000ms as an example)\n      setTimeout(function () {\n        \/\/ After calculating, hide the indicator and proceed\n        document.getElementById(\"calculatingIndicator\").style.display = \"none\";\n        clearInterval(window.calculatingIntervalId);\n        \/\/ Here you can add what should happen after the \"calculating\" phase is over\n      }, 4000); \/\/ Adjust time as needed\n\n      \/\/ Reset the total variables\n      clearAllData();\n\n      const roofType = document\n        .querySelector('input[name=\"roofType\"]:checked')\n        .value;\n      const panelWidth = Number(document.getElementById(\"PW\").value);\n      const numRows = Number(document.getElementById(\"numRows\").value);\n      const panelsPerRow = [];\n\n      calculateSnowLoad(roofType);\n      checkDiscount(); \/\/ Check if need to show user price\n\n      for (let i = 0; i < numRows; i += 1) {\n        panelsPerRow.push(Number(document.getElementById(`row${i}`).value));\n      }\n\n      \/\/ Calculation logic based on roof type\n      for (let i = 0; i < numRows; i += 1) {\n        const numPanels = panelsPerRow[i];\n        if (!numPanels || numPanels <= 0) {\n          continue;\n        }\n        totalPanelAmount += numPanels;\n        const requiredProfileInMM =\n          numPanels * panelWidth + 20 * (numPanels - 1) + 105;\n        const requiredProfileInPCS = requiredProfileInMM \/ 3550;\n        const PR01 = Math.ceil(2 * requiredProfileInPCS); \/\/ panel needs up profile and down profile\n        const PR02 = 2 * Math.floor(requiredProfileInPCS);\n        const CL01 = (numPanels - 1) * 2;\n        const CL02 = 4;\n        const RB0X =\n          Math.ceil((requiredProfileInMM - 400) \/ profileSpan) * 2 + 2;\n\n        eachRowData.push([\n          numPanels,\n          2 * requiredProfileInMM,\n          (2 * requiredProfileInPCS).toFixed(2),\n          PR02,\n          RB0X,\n          CL01,\n          CL02,\n        ]);\n\n        \/\/ console.log(eachRowData);\n\n        if (roofType === \"1\") {\n          \/\/ \"Lock seamed metal roof\"\n          const RB01 = RB0X;\n          const BN01 = RB01;\n\n          productTotals.RB11 += RB01;\n          productTotals.BN01 += BN01;\n        }\n        if (roofType === \"2\") {\n          \/\/ \"Felt roof and metal profile roof\"\n          const RB02 = RB0X;\n          const BN01 = RB02;\n          const SS06 = RB02 * 4;\n\n          productTotals.RB12 += RB02;\n          productTotals.BN01 += BN01;\n          productTotals.SS06 += SS06;\n        }\n        if (roofType === \"3\") {\n          \/\/ \"Tile-shaped metal profile roof\"\n          const RB03 = RB0X;\n          const BN01 = RB03;\n          const RS01 = 2 * RB03;\n          const RS02 = RB03;\n          const SS05 = RB03 * 2;\n\n          productTotals.RB13 += RB03;\n          productTotals.BN01 += BN01;\n          productTotals.RS01 += RS01;\n          productTotals.RS02 += RS02;\n          productTotals.SS05 += SS05;\n        }\n        if (roofType === \"4\") {\n          \/\/ \"Wave-shaped metal profile roof\"\n          const RB04 = RB0X;\n          const BN01 = RB04;\n          const SS05 = RB04 * 2;\n\n          productTotals.RB04 += RB04;\n          productTotals.BN01 += BN01;\n          productTotals.SS05 += SS05;\n        }\n        if (roofType === \"5\") {\n          \/\/ \"Tile roof\"\n          const RB05 = RB0X;\n          const BN01 = RB05;\n          const RB06 = RB05;\n          const SS06 = RB05;\n\n          productTotals.RB15 += RB05;\n          productTotals.BN01 += BN01;\n          productTotals.RB06 += RB06;\n          productTotals.SS06 += SS06;\n        }\n        if (roofType === \"6\") {\n          \/\/ \"Tile roof with flat style\"\n          const RB17 = RB0X;\n          const BN01 = RB17;\n          const SS06 = RB17;\n\n          productTotals.RB17 += RB17;\n          productTotals.BN01 += BN01;\n          productTotals.SS06 += SS06;\n        }\n\n        productTotals.PR11 += PR01;\n        productTotals.PR12 += PR02;\n        productTotals.CL11 += CL01;\n        productTotals.CL02 += CL02;\n      }\n\n      \/\/ Output the results in a table\n      document.getElementById(\"resultTable\").style.display = \"table\";\n      document.getElementById(\"resultTable2\").style.display = \"table\";\n      document.getElementById(\"resultsSection\").style.display = \"flex\";\n\n      renderResultTable();\n\n      buildResultTable2();\n\n      setWarning(\n        \"warning1\",\n        \"* Laskelmat ovat vain suosituksia. Huomioithan rakennuspaikkasi erityisolosuhteet tehdess\u00e4si p\u00e4\u00e4t\u00f6ksi\u00e4.\"\n      );\n\n      setWarning(\n        \"warning2\",\n        \"* T\u00e4m\u00e4 laskin ei ota huomioon joitakin poikkeuksellisia tilanteita, jotka on mainittu Eurokoodissa, kuten ylemm\u00e4t katot. Erityisesti rakennuksen korkeuden ylitt\u00e4ess\u00e4 10 metri\u00e4 ja\/tai sijaitessa hyvin tuulisella ja\/tai lumisella alueella, tullee mitoitus suorittaa riitt\u00e4v\u00e4n ammattitaidon omaavan rakennesuunnittelijan toimesta. Lis\u00e4tietoja varten ole hyv\u00e4 ja konsultoi teknist\u00e4 asiantuntijaamme.\"\n      );\n\n      \/\/ After outputting the results in the table\n      scrollToResults(); \/\/ Call the smooth scroll function here\n\n      document.getElementById(\"saveAsPDF\").style.display = \"block\";\n      document.getElementById(\"saveAsCSV\").style.display = \"block\";\n      document.getElementById(\"suggestUserToSendEmail\").style.display = \"block\";\n\n      \/\/Insert into db\n      const formData = {\n        date: new Date().toISOString().split(\"T\")[0],\n        customer_name: document.getElementById(\"customerName\").value,\n        company_name: document.getElementById(\"companyName\").value,\n        email: document.getElementById(\"email\").value,\n        phone: document.getElementById(\"phone\").value,\n        roof_type: roofType,\n        panel_amount: totalPanelAmount,\n        fixtures: fixtures,\n      };\n\n      fetch(\"https:\/\/api.moisolar.com:3005\/insertRecord\", {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application\/json\",\n        },\n        body: JSON.stringify(formData),\n      })\n        .then((response) => response.json())\n        .then((data) => {\n          console.log(\"Success\");\n        })\n        .catch((error) => {\n          console.error(\"Error:\", error);\n          \/\/ alert(\"Error inserting record.\");\n        });\n    }\n  });\n\nfunction buildResultTable2() {\n  const resultTable2 = document.getElementById(\"resultTable2\");\n  resultTable2.innerHTML = \"\"; \/\/ Clear previous table contents\n\n  const hasBN = productTotals.BN01 > 0;\n  const hasRS = productTotals.RS01 > 0;\n  const hasSS05 = productTotals.SS05 > 0;\n  const hasSS06 = productTotals.SS06 > 0;\n\n  const headerCells = [\n    { text: \"Rivinumero\" },\n    { lines: [\"Paneelien\", \"m\u00e4\u00e4r\u00e4 (kpl)\"] },\n    { lines: [\"PR11\", \"(mm)\"] },\n    { lines: [\"PR11\", \"(kpl)\"] },\n    { lines: [\"PR12\", \"(kpl)\"] },\n    { lines: [\"Kattokiinnike\", \"(kpl)\"] },\n    { lines: [\"CL11\", \"(kpl)\"] },\n    { lines: [\"CL02\", \"(kpl)\"] },\n  ];\n\n  if (hasSS06) {\n    headerCells.push({ lines: [\"SS06\", \"(kpl)\"] });\n  }\n  if (hasSS05) {\n    headerCells.push({ lines: [\"SS05\", \"(kpl)\"] });\n  }\n  if (hasBN) {\n    headerCells.push({ lines: [\"BN01\", \"(kpl)\"] });\n  }\n  if (hasRS) {\n    headerCells.push({ lines: [\"RS01\", \"(kpl)\"] });\n    headerCells.push({ lines: [\"RS02\", \"(kpl)\"] });\n  }\n\n  const columnCount = headerCells.length;\n  const titleRow = document.createElement(\"tr\");\n  titleRow.innerHTML = `<td colspan=\"${columnCount}\" style=\"text-align:center;\"><strong>Tiedot rivi kerrallaan<\\\/strong><\\\/td>`;\n  resultTable2.appendChild(titleRow);\n\n  const headerRow = document.createElement(\"tr\");\n  headerCells.forEach((cell) => {\n    const th = document.createElement(\"th\");\n    if (cell.lines && cell.lines.length) {\n      cell.lines.forEach((line) => {\n        const div = document.createElement(\"div\");\n        div.textContent = line;\n        th.appendChild(div);\n      });\n    } else {\n      th.textContent = cell.text;\n    }\n    headerRow.appendChild(th);\n  });\n  resultTable2.appendChild(headerRow);\n\n  eachRowData.forEach(function (rowData, index) {\n    const rbPerRow = rowData[4];\n    const rowCells = [`Rivi ${index + 1}`, ...rowData];\n\n    if (hasSS06) {\n      rowCells.push(rbPerRow * 4);\n    }\n    if (hasSS05) {\n      rowCells.push(rbPerRow * 2);\n    }\n    if (hasBN) {\n      rowCells.push(rbPerRow);\n    }\n    if (hasRS) {\n      rowCells.push(2 * rbPerRow);\n      rowCells.push(rbPerRow);\n    }\n\n    const row = document.createElement(\"tr\");\n    rowCells.forEach(function (cell) {\n      const td = document.createElement(\"td\");\n      td.textContent = cell;\n      row.appendChild(td);\n    });\n    resultTable2.appendChild(row);\n  });\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", function () {\n  document\n    .getElementById(\"saveAsPDF\")\n    .addEventListener(\"click\", async function (event) {\n      event.preventDefault();\n\n      try {\n        const { jsPDF } = window.jspdf;\n        const doc = new jsPDF();\n        \/\/ Constants for the table\n        const tableLeft = 10;\n        const tableRight = 201;\n        const tableMiddle = (tableLeft + tableRight) \/ 2;\n        const tableGap1 = 20;\n        const tp1 = tableLeft + tableGap1;\n        const tableGap2 = 35;\n        const tp2 = tp1 + tableGap2;\n        const tableGap3 = 25;\n        const tp3 = tp2 + tableGap3;\n        const tableGap4 = 25;\n        const tp4 = tp3 + tableGap4;\n        const tableGap5 = 18;\n        const tp5 = tp4 + tableGap5;\n        const tableGap6 = 33;\n        const tp6 = tp5 + tableGap6;\n        const tableGap7 = 18;\n        const tp7 = tp6 + tableGap7;\n        const tableGap8 = 18;\n        const tp8 = tp7 + tableGap8;\n        const rowHeight = 4;\n        const rowHeight2 = 9;\n\n        const right = doc.internal.pageSize.width - 40; \/\/ Adjust the -60 as needed\n\n        \/\/ console.log(tp1, tp2, tp3, tp4, tp5, tp6, tp7, tp8);\n\n        let rbType = \"\";\n        const hasBN = productTotals.BN01 > 0;\n        const hasRS = productTotals.RS01 > 0;\n        const hasSS05 = productTotals.SS05 > 0;\n        const hasSS06 = productTotals.SS06 > 0;\n\n        \/\/ console.log(eachRowData);\n\n        let currentY = 0; \/\/ Adjusted starting position to accommodate the logo and title\n        let spaceBetweenLines = 2;\n\n        \/\/ Load the logo\n        const logoWidth = 30;\n        const logoHeight = (422 \/ 1838) * logoWidth; \/\/ Calculating height to maintain aspect ratio\n        const logo = await fetchImageAsBase64(\n          \"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/08\/moisolarlogo.jpg\"\n        );\n        doc.addImage(logo, \"JPEG\", right, 3, logoWidth, logoHeight);\n\n        doc.setFontSize(11);\n        const customerName = $(\"#customerName\").val();\n        const companyName = $(\"#companyName\").val();\n        doc.text(companyName, 10, 10);\n        doc.setFontSize(10);\n        doc.text(customerName, 10, 15);\n\n        currentY += 23;\n\n        \/\/ Add title\n        const title = \"Laskennan tulos\";\n        doc.setFontSize(14); \/\/ Adjust for a bigger title size\n        doc.text(title, doc.internal.pageSize.width \/ 2, currentY, {\n          align: \"center\",\n        }); \/\/ Positioned to be centered\n\n        currentY += 6;\n\n        \/\/Prepare for first table\n        let conclusionText =\n          \"Vaaditun asennuksen tekemiseksi tarvitset seuraavat osat.\";\n\n        doc.setFontSize(10);\n        doc.setLineWidth(0.5);\n        doc.text(conclusionText, tableLeft, currentY);\n        currentY += 4;\n\n        \/\/Draw top line\n        doc.line(tableLeft, currentY, tableRight, currentY);\n        \/\/ Vertical lines for columns\n        doc.line(tableLeft, currentY, tableLeft, currentY + rowHeight2); \/\/ Left\n        doc.line(\n          tableLeft + 47,\n          currentY,\n          tableLeft + 47,\n          currentY + rowHeight2\n        ); \/\/ Middle 1\n        doc.line(\n          tableLeft + 2 * 47,\n          currentY,\n          tableLeft + 2 * 47,\n          currentY + rowHeight2\n        ); \/\/ Middle 2\n        doc.line(\n          tableLeft + 3 * 47,\n          currentY,\n          tableLeft + 3 * 47,\n          currentY + rowHeight2\n        ); \/\/ Middle 3\n        doc.line(tableRight, currentY, tableRight, currentY + rowHeight2); \/\/ Right\n        \/\/ Add head content\n        doc.setFontSize(9);\n        doc.text(\"Tuote\", tableLeft + 5, currentY + 5);\n        doc.text(\"Tarvittava m\u00e4\u00e4r\u00e4\", tableLeft + 47 + 5, currentY + 5);\n        doc.text(\"Myyntiyksikk\u00f6\", tableLeft + 2 * 47 + 5, currentY + 5);\n        doc.text(\"Vaadittu yksikk\u00f6\", tableLeft + 3 * 47 + 5, currentY + 5);\n\n        currentY += rowHeight2 - 1;\n        let isFirstRow = true;\n        const imgWidth = 10; \/\/ Desired width for the image in the PDF\n        const imgHeight = (225 \/ 300) * imgWidth;\n\n        for (let product of productData) {\n          if (product.total > 0) {\n            if (product.code.includes(\"RB\") && !product.code.includes(\"06\")) {\n              rbType = product.code.toLowerCase();\n            }\n\n            let imageAdded = false;\n            if (product.imageUrl) {\n              try {\n                const image = await fetchImageAsBase64(product.imageUrl);\n                doc.addImage(\n                  image,\n                  \"JPEG\",\n                  tableLeft + 5,\n                  currentY + 1,\n                  imgWidth,\n                  imgHeight\n                );\n                imageAdded = true;\n              } catch (error) {\n                console.warn(\"Failed to load product image for PDF:\", product.code, error);\n              }\n            }\n\n            doc.setFontSize(9); \/\/ Set font size\n\n            const textX =\n              tableLeft + 5 + (imageAdded ? imgWidth + 5 : 0);\n            const textY = currentY + 5;\n            const textWidth = doc.getTextWidth(product.code);\n\n            \/\/ Set the hyperlink just for the product code\n            doc.setTextColor(0, 0, 255); \/\/ Set text color to blue\n            doc.text(product.code, textX, textY);\n            const linkX = imageAdded ? tableLeft + 5 : textX;\n            const linkWidth = textWidth + (imageAdded ? imgWidth + 5 : 0);\n            const linkHeight = imageAdded ? Math.max(imgHeight, doc.getFontSize()) : doc.getFontSize();\n            doc.link(linkX, currentY, linkWidth, linkHeight, {\n              url: product.url,\n            });\n\n            \/\/ Draw underline for link\n            doc.setDrawColor(0, 0, 255);\n\n            doc.line(\n              textX,\n              currentY + 6,\n              textX + textWidth,\n              currentY + 6\n            );\n\n            \/\/ Reset text color to black\n            doc.setTextColor(0, 0, 0);\n\n            \/\/ Reset the draw color to black for table lines\n            doc.setDrawColor(0, 0, 0);\n\n            doc.text(`${product.total} kpl`, tableLeft + 47 + 5, currentY + 5);\n\n            doc.text(product.unitText, tableLeft + 2 * 47 + 5, currentY + 5);\n\n            let unitAmount = Math.ceil(product.total \/ product.unitNumber);\n            let content2 = unitAmount + \" \" + product.unit;\n            doc.text(content2, tableLeft + 3 * 47 + 5, currentY + 5);\n\n            \/\/ Draw topmost horizontal line only once when encountering the first product\n            if (isFirstRow) {\n              doc.line(tableLeft, currentY, tableRight, currentY);\n              isFirstRow = false;\n            }\n\n            \/\/ Draw horizontal line for row bottom\n            doc.line(\n              tableLeft,\n              currentY + rowHeight2,\n              tableRight,\n              currentY + rowHeight2\n            );\n\n            \/\/ Vertical lines for columns\n            doc.line(tableLeft, currentY, tableLeft, currentY + rowHeight2); \/\/ Left\n            doc.line(\n              tableLeft + 47,\n              currentY,\n              tableLeft + 47,\n              currentY + rowHeight2\n            ); \/\/ Middle 1\n            doc.line(\n              tableLeft + 2 * 47,\n              currentY,\n              tableLeft + 2 * 47,\n              currentY + rowHeight2\n            ); \/\/ Middle 2\n            doc.line(\n              tableLeft + 3 * 47,\n              currentY,\n              tableLeft + 3 * 47,\n              currentY + rowHeight2\n            ); \/\/ Middle 3\n            doc.line(tableRight, currentY, tableRight, currentY + rowHeight2); \/\/ Right\n\n            \/\/ Move down for next product\n            currentY += rowHeight2;\n          }\n        }\n        currentY += 5;\n        \/\/ Prepare 2nd table\n        doc.setFontSize(10);\n        doc.text(\n          \"Kunkin rivin tiedot n\u00e4kyv\u00e4t alla olevassa taulukossa.\",\n          tableLeft,\n          currentY\n        );\n        currentY += 3;\n\n        const rowColumns = [\n          {\n            label: \"Rivinumero\",\n            weight: 0.9,\n            getValue: (_, rowIndex) => `Rivi ${rowIndex + 1}`,\n          },\n          {\n            label: \"Paneelien m\u00e4\u00e4r\u00e4\",\n            labelLines: [\"Paneelien m\u00e4\u00e4r\u00e4\", \"(kpl)\"],\n            weight: 1.3,\n            getValue: (rowData) => rowData[0],\n          },\n          {\n            label: \"PR11\",\n            labelLines: [\"PR11\", \"(mm)\"],\n            weight: 0.85,\n            getValue: (rowData) => rowData[1],\n          },\n          {\n            label: \"PR11 kpl\",\n            labelLines: [\"PR11\", \"(kpl)\"],\n            weight: 0.8,\n            getValue: (rowData) => rowData[2],\n          },\n          {\n            label: \"PR12\",\n            labelLines: [\"PR12\", \"(kpl)\"],\n            weight: 0.8,\n            getValue: (rowData) => rowData[3],\n          },\n          {\n            label: \"Kattokiinnike\",\n            labelLines: [\"Kattokiinnike\", \"(kpl)\"],\n            weight: 1.05,\n            getValue: (rowData) => rowData[4],\n          },\n          {\n            label: \"CL11\",\n            labelLines: [\"CL11\", \"(kpl)\"],\n            weight: 0.8,\n            getValue: (rowData) => rowData[5],\n          },\n          {\n            label: \"CL02\",\n            labelLines: [\"CL02\", \"(kpl)\"],\n            weight: 0.8,\n            getValue: (rowData) => rowData[6],\n          },\n        ];\n\n        if (hasSS06) {\n          rowColumns.push({\n            label: \"SS06\",\n            labelLines: [\"SS06\", \"(kpl)\"],\n            weight: 0.75,\n            getValue: (rowData) => rowData[4] * 4,\n          });\n        }\n        if (hasSS05) {\n          rowColumns.push({\n            label: \"SS05\",\n            labelLines: [\"SS05\", \"(kpl)\"],\n            weight: 0.75,\n            getValue: (rowData) => rowData[4] * 2,\n          });\n        }\n        if (hasBN) {\n          rowColumns.push({\n            label: \"BN01\",\n            labelLines: [\"BN01\", \"(kpl)\"],\n            weight: 0.75,\n            getValue: (rowData) => rowData[4],\n          });\n        }\n        if (hasRS) {\n          rowColumns.push({\n            label: \"RS01\",\n            labelLines: [\"RS01\", \"(kpl)\"],\n            weight: 0.75,\n            getValue: (rowData) => rowData[4] * 2,\n          });\n          rowColumns.push({\n            label: \"RS02\",\n            labelLines: [\"RS02\", \"(kpl)\"],\n            weight: 0.75,\n            getValue: (rowData) => rowData[4],\n          });\n        }\n\n        const totalWeight = rowColumns.reduce(\n          (sum, column) => sum + column.weight,\n          0\n        );\n        const pageWidth = doc.internal.pageSize.width;\n        const rightMargin = 12;\n        const maxRowTableWidth = pageWidth - tableLeft - rightMargin;\n        let cursorX = tableLeft;\n\n        rowColumns.forEach((column) => {\n          const computedWidth =\n            (column.weight \/ totalWeight) * maxRowTableWidth;\n          const width = Math.max(13, computedWidth);\n          column.start = cursorX;\n          column.end = cursorX + width;\n          column.width = width;\n          cursorX = column.end;\n        });\n\n        const rowTableRight = cursorX;\n\n        const headerLineHeight = 3.2;\n        const maxHeaderLines = rowColumns.reduce(\n          (max, column) => Math.max(max, (column.labelLines?.length || 1)),\n          1\n        );\n        const headerHeight = Math.max(rowHeight, maxHeaderLines * headerLineHeight + 1);\n\n        doc.line(tableLeft, currentY, rowTableRight, currentY); \/\/ Draw top line\n        doc.setFontSize(8);\n\n        rowColumns.forEach((column) => {\n          doc.line(column.end, currentY, column.end, currentY + headerHeight); \/\/ Column borders\n          const labels = column.labelLines || [column.label];\n          labels.forEach((text, index) => {\n            doc.text(text, column.start + 2, currentY + 3 + index * headerLineHeight);\n          });\n        });\n        doc.line(tableLeft, currentY, tableLeft, currentY + headerHeight); \/\/ Left border\n\n        currentY += headerHeight;\n        doc.line(tableLeft, currentY, rowTableRight, currentY);\n\n        eachRowData.forEach(function (rowData, index) {\n          doc.line(tableLeft, currentY, tableLeft, currentY + rowHeight); \/\/ Left border\n          rowColumns.forEach((column) => {\n            const value = column.getValue(rowData, index);\n            doc.text(String(value), column.start + 2, currentY + 3);\n            doc.line(\n              column.end,\n              currentY,\n              column.end,\n              currentY + rowHeight\n            );\n          });\n\n          currentY += rowHeight;\n          doc.line(tableLeft, currentY, rowTableRight, currentY);\n        });\n\n        \/\/ Show Text info\n        currentY += 6;\n\n        doc.setFontSize(10); \/\/ Adjusting the font size for the note\n        doc.text(\n          \"Lumikuorma aurinkopaneelia kohden: \" +\n            snowLoadPerPanel.toFixed(2) +\n            \"kN. Kahden kattotelineen v\u00e4linen et\u00e4isyys on \" +\n            profileSpan +\n            \" mm.\",\n          tableLeft,\n          currentY\n        );\n\n        currentY += 5;\n\n        if (customSpanWarning) {\n          doc.setFontSize(9);\n          const warningLines = doc.splitTextToSize(\n            customSpanWarning,\n            rowTableRight - tableLeft\n          );\n          warningLines.forEach((line, index) => {\n            doc.text(line, tableLeft, currentY + index * 4);\n          });\n          currentY += warningLines.length * 4 + 2;\n        }\n\n        if (rbType === \"rb15\" || rbType === \"rb17\") {\n          const maxSpanText = rbType === \"rb17\" ? \"900\" : \"1200\";\n\n          doc.setFontSize(9);\n          doc.text(\n            `* Suosittelemme vahvasti, ett\u00e4 k\u00e4yt\u00e4tte enint\u00e4\u00e4n ${maxSpanText}mm kiinnikev\u00e4li\u00e4 tiilikatoilla. Harkitse lis\u00e4ksi ylim\u00e4\u00e4r\u00e4isten kattotelineiden lis\u00e4\u00e4mist\u00e4`,\n            tableLeft,\n            currentY\n          );\n          doc.text(\n            \"alimpaan profiililinjaan. Suosittelemme asentamaan kattokiinnikkeen mahdollisimman l\u00e4helle aurinkopaneelin keskikiinnikkeit\u00e4(CL11)\",\n            tableLeft + 2,\n            currentY + 4\n          );\n          doc.text(\n            \"v\u00e4hent\u00e4\u00e4ksesi kattotiilen vaurioitumisen riski\u00e4. Alin profiililinja voi ylikuormittua kasaantuvan lumen seurauksena. Huonokuntoinen\",\n            tableLeft + 2,\n            currentY + 8\n          );\n          doc.text(\n            \"kattotiili voi t\u00e4ll\u00f6in vaurioitua.\",\n            tableLeft + 2,\n            currentY + 12\n          );\n\n          currentY += 17;\n        }\n\n        \/\/ Note after the table\n        doc.setFontSize(9); \/\/ Adjusting the font size for the note\n        doc.text(\n          \"* Laskelmat ovat vain suosituksia. Huomioithan rakennuspaikkasi erityisolosuhteet tehdess\u00e4si p\u00e4\u00e4t\u00f6ksi\u00e4.\",\n          tableLeft,\n          currentY\n        );\n        doc.text(\n          \"* T\u00e4m\u00e4 laskin ei ota huomioon joitakin poikkeuksellisia tilanteita, jotka on mainittu Eurokoodissa, kuten ylemm\u00e4t katot. Erityisesti \",\n          tableLeft,\n          currentY + 7 - 2\n        );\n        doc.text(\n          \"rakennuksen korkeuden ylitt\u00e4ess\u00e4 10 metri\u00e4 ja\/tai sijaitessa hyvin tuulisella ja\/tai lumisella alueella, tullee mitoitus suorittaa riitt\u00e4v\u00e4n \",\n          tableLeft + 2,\n          currentY + 11 - 2\n        );\n        doc.text(\n          \"ammattitaidon omaavan rakennesuunnittelijan toimesta. Lis\u00e4tietoja varten ole hyv\u00e4 ja konsultoi teknist\u00e4 asiantuntijaamme.\",\n          tableLeft + 2,\n          currentY + 15 - 2\n        );\n\n        \/\/ Company information at the bottom\n        const pageHeight = 297; \/\/ A4 size\n        doc.setFontSize(8); \/\/ Adjusting the font size for the footer details\n        doc.text(\"Moisolar Oy\", 10, pageHeight - 10);\n        doc.text(\"Tekniikantie 12, 02150 Espoo, Suomi\", 10, pageHeight - 5);\n\n        const middle = doc.internal.pageSize.width \/ 2 - 20; \/\/ Adjust the -20 as needed\n        doc.text(\"www.moisolar.com\", middle, pageHeight - 13);\n        doc.text(\"info@moisolar.com\", middle, pageHeight - 9);\n        doc.text(\"+358 40 838 2368\", middle, pageHeight - 5);\n\n        doc.text(\"Y-tunnus 3345172-1\", right, pageHeight - 10);\n        doc.text(\"Varasto:01260 Vantaa\", right, pageHeight - 5);\n\n        \/\/ Save the PDF\n        doc.save(\"Laskennan tulos.pdf\");\n      } catch (error) {\n        console.error(\"An error occurred:\", error);\n      }\n    });\n});\n\n\/\/ Fetch the image and return it as a base64 data URI\nasync function fetchImageAsBase64(url) {\n  const response = await fetch(url);\n  const blob = await response.blob();\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onloadend = () => resolve(reader.result);\n    reader.onerror = reject;\n    reader.readAsDataURL(blob);\n  });\n}\n\n  <\/script>\n<\/body>\n<\/html>\n\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<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Solar Panel Fixture Calculator Welcome to our enhanced Solar Panel Fixture Calculator, now leveraging SGS test results for unparalleled accuracy. This intuitive tool simplifies your solar installation planning by instantly estimating the required roof fixtures based on the city information, roof angle, panel size, roof type, and number of solar panels you input. Designed for all project scales, it aids in efficient resource management and smoother installations. Begin optimizing your solar panel installation process today with our scientifically backed calculator. Nordic Slope Fixture Calculator Set City Information Selecting the correct city is important as the general snow load varies by location. This value is used to calculate the specific load on your solar panels. Select Your City Akaa &#8211; 2.3 kN\/m\u00b2Alaja\u0308rvi &#8211; 2.5 kN\/m\u00b2Alavieska &#8211; 2.0 kN\/m\u00b2Alavus &#8211; 2.5 kN\/m\u00b2Asikkala &#8211; 2.5 kN\/m\u00b2Askola &#8211; 2.7 kN\/m\u00b2Aura &#8211; 2.5 kN\/m\u00b2Bra\u0308ndo\u0308 &#8211; 2.0 kN\/m\u00b2Eckero\u0308 &#8211; 2.0 kN\/m\u00b2Enonkoski &#8211; 2.5 kN\/m\u00b2Enontekio\u0308 &#8211; 3.5 kN\/m\u00b2Espoo &#8211; 2.75 kN\/m\u00b2Eura &#8211; 2.1 kN\/m\u00b2Eurajoki &#8211; 2.0 kN\/m\u00b2Evija\u0308rvi &#8211; 2.2 kN\/m\u00b2Finstro\u0308m &#8211; 2.0 kN\/m\u00b2Forssa &#8211; 2.6 kN\/m\u00b2Fo\u0308glo\u0308 &#8211; 2.0 kN\/m\u00b2Geta &#8211; 2.0 kN\/m\u00b2Haapaja\u0308rvi &#8211; 2.5 kN\/m\u00b2Haapavesi &#8211; 2.4 kN\/m\u00b2Hailuoto &#8211; 2.4 kN\/m\u00b2Halsua &#8211; 2.4 kN\/m\u00b2Hamina &#8211; 2.75 kN\/m\u00b2Hammarland &#8211; 2.0 kN\/m\u00b2Hankasalmi &#8211; 2.5 kN\/m\u00b2Hanko &#8211; 2.5 kN\/m\u00b2Harjavalta &#8211; 2.0 kN\/m\u00b2Hartola &#8211; 2.5 kN\/m\u00b2Hattula &#8211; 2.5 kN\/m\u00b2Hausja\u0308rvi &#8211; 2.75 kN\/m\u00b2Heinola &#8211; 2.5 kN\/m\u00b2Heina\u0308vesi &#8211; 2.5 kN\/m\u00b2Helsinki &#8211; 2.75 kN\/m\u00b2Hirvensalmi &#8211; 2.5 kN\/m\u00b2Hollola &#8211; 2.75 kN\/m\u00b2Honkajoki &#8211; 2.5 kN\/m\u00b2Huittinen &#8211; 2.0 kN\/m\u00b2Humppila &#8211; 2.25 kN\/m\u00b2Hyrynsalmi &#8211; 3.5 kN\/m\u00b2Hyvinka\u0308a\u0308 &#8211; 2.75 kN\/m\u00b2Ha\u0308meenkyro\u0308 &#8211; 2.5 kN\/m\u00b2Ha\u0308meenlinna &#8211; 2.5 kN\/m\u00b2Ii &#8211; 2.8 kN\/m\u00b2Iisalmi &#8211; 2.6 kN\/m\u00b2Iitti &#8211; 2.5 kN\/m\u00b2Ikaalinen &#8211; 2.5 kN\/m\u00b2Ilmajoki &#8211; 2.5 kN\/m\u00b2Ilomantsi &#8211; 2.75 kN\/m\u00b2Imatra &#8211; 2.75 kN\/m\u00b2Inari &#8211; 3.0 kN\/m\u00b2Inkoo &#8211; 2.75 kN\/m\u00b2Isojoki &#8211; 2.5 kN\/m\u00b2Isokyro\u0308 &#8211; 2.3 kN\/m\u00b2Janakkala &#8211; 2.75 kN\/m\u00b2Joensuu &#8211; 2.6 kN\/m\u00b2Jokioinen &#8211; 2.6 kN\/m\u00b2Jomala &#8211; 2.0 kN\/m\u00b2Joroinen &#8211; 2.5 kN\/m\u00b2Joutsa &#8211; 2.5 kN\/m\u00b2Juuka &#8211; 2.75 kN\/m\u00b2Juupajoki &#8211; 2.5 kN\/m\u00b2Juva &#8211; 2.5 kN\/m\u00b2Jyva\u0308skyla\u0308 &#8211; 2.5 kN\/m\u00b2Ja\u0308mija\u0308rvi &#8211; 2.5 kN\/m\u00b2Ja\u0308msa\u0308 &#8211; 2.5 kN\/m\u00b2Ja\u0308rvenpa\u0308a\u0308 &#8211; 2.75 kN\/m\u00b2Kaarina &#8211; 2.5 kN\/m\u00b2Kaavi &#8211; 2.65 kN\/m\u00b2Kajaani &#8211; 2.75 kN\/m\u00b2Kalajoki &#8211; 2.0 kN\/m\u00b2Kangasala &#8211; 2.5 kN\/m\u00b2Kangasniemi &#8211; 2.5 kN\/m\u00b2Kankaanpa\u0308a\u0308 &#8211; 2.5 kN\/m\u00b2Kannonkoski &#8211; 2.5 kN\/m\u00b2Kannus &#8211; 2.1 kN\/m\u00b2Karijoki &#8211; 2.5 kN\/m\u00b2Karkkila &#8211; 2.75 kN\/m\u00b2Karstula &#8211; 2.5 kN\/m\u00b2Karvia &#8211; 2.5 kN\/m\u00b2Kaskinen &#8211; 2.0 kN\/m\u00b2Kauhajoki &#8211; 2.5 kN\/m\u00b2Kauhava &#8211; 2.3 kN\/m\u00b2Kauniainen &#8211; 2.75 kN\/m\u00b2Kaustinen &#8211; 2.2 kN\/m\u00b2Keitele &#8211; 2.5 kN\/m\u00b2Kemi &#8211; 3.0 kN\/m\u00b2Kemija\u0308rvi &#8211; 3.0 kN\/m\u00b2Keminmaa &#8211; 3.0 kN\/m\u00b2Kemio\u0308nsaari &#8211; 2.5 kN\/m\u00b2Kempele &#8211; 2.25 kN\/m\u00b2Kerava &#8211; 2.75 kN\/m\u00b2Keuruu &#8211; 2.5 kN\/m\u00b2Kihnio\u0308 &#8211; 2.5 kN\/m\u00b2Kinnula &#8211; 2.5 kN\/m\u00b2Kirkkonummi &#8211; 2.75 kN\/m\u00b2Kitee &#8211; 2.75 kN\/m\u00b2Kittila\u0308 &#8211; 3.0 kN\/m\u00b2Kiuruvesi &#8211; 2.55 kN\/m\u00b2Kivija\u0308rvi &#8211; 2.5 kN\/m\u00b2Kokema\u0308ki &#8211; 2.1 kN\/m\u00b2Kokkola &#8211; 2.0 kN\/m\u00b2Kolari &#8211; 3.0 kN\/m\u00b2Konnevesi &#8211; 2.5 kN\/m\u00b2Kontiolahti &#8211; 2.75 kN\/m\u00b2Korsna\u0308s &#8211; 2.0 kN\/m\u00b2Koski Tl &#8211; 2.6 kN\/m\u00b2Kotka &#8211; 2.65 kN\/m\u00b2Kouvola &#8211; 2.5 kN\/m\u00b2Kristiinankaupunki &#8211; 2.5 kN\/m\u00b2Kruunupyy &#8211; 2.2 kN\/m\u00b2Kuhmo &#8211; 3.0 kN\/m\u00b2Kuhmoinen &#8211; 2.5 kN\/m\u00b2Kumlinge &#8211; 2.0 kN\/m\u00b2Kuopio &#8211; 2.5 kN\/m\u00b2Kuortane &#8211; 2.5 kN\/m\u00b2Kurikka &#8211; 2.5 kN\/m\u00b2Kustavi &#8211; 2.1 kN\/m\u00b2Kuusamo &#8211; 3.5 kN\/m\u00b2Kyyja\u0308rvi &#8211; 2.5 kN\/m\u00b2Ka\u0308rko\u0308la\u0308 &#8211; 2.75 kN\/m\u00b2Ka\u0308rsa\u0308ma\u0308ki &#8211; 2.5 kN\/m\u00b2Ko\u0308kar &#8211; 2.0 kN\/m\u00b2Lahti &#8211; 2.65 kN\/m\u00b2Laihia &#8211; 2.3 kN\/m\u00b2Laitila &#8211; 2.2 kN\/m\u00b2Lapinja\u0308rvi &#8211; 2.5 kN\/m\u00b2Lapinlahti &#8211; 2.6 kN\/m\u00b2Lappaja\u0308rvi &#8211; 2.4 kN\/m\u00b2Lappeenranta &#8211; 2.75 kN\/m\u00b2Lapua &#8211; 2.5 kN\/m\u00b2Laukaa &#8211; 2.5 kN\/m\u00b2Lemi &#8211; 2.7 kN\/m\u00b2Lemland &#8211; 2.0 kN\/m\u00b2Lempa\u0308a\u0308la\u0308 &#8211; 2.4 kN\/m\u00b2Leppa\u0308virta &#8211; 2.5 kN\/m\u00b2Lestija\u0308rvi &#8211; 2.5 kN\/m\u00b2Lieksa &#8211; 2.75 kN\/m\u00b2Lieto &#8211; 2.6 kN\/m\u00b2Liminka &#8211; 2.45 kN\/m\u00b2Liperi &#8211; 2.55 kN\/m\u00b2Lohja &#8211; 2.75 kN\/m\u00b2Loimaa &#8211; 2.65 kN\/m\u00b2Loppi &#8211; 2.75 kN\/m\u00b2Loviisa &#8211; 2.5 kN\/m\u00b2Luhanka &#8211; 2.5 kN\/m\u00b2Lumijoki &#8211; 2.25 kN\/m\u00b2Lumparland &#8211; 2.0 kN\/m\u00b2Luoto &#8211; 2.0 kN\/m\u00b2Luuma\u0308ki &#8211; 2.75 kN\/m\u00b2Maalahti &#8211; 2.0 kN\/m\u00b2Maarianhamina &#8211; 2.0 kN\/m\u00b2Marttila &#8211; 2.6 kN\/m\u00b2Masku &#8211; 2.3 kN\/m\u00b2Merija\u0308rvi &#8211; 2.0 kN\/m\u00b2Merikarvia &#8211; 2.4 kN\/m\u00b2Miehikka\u0308la\u0308 &#8211; 2.75 kN\/m\u00b2Mikkeli &#8211; 2.5 kN\/m\u00b2Muhos &#8211; 2.5 kN\/m\u00b2Multia &#8211; 2.5 kN\/m\u00b2Muonio &#8211; 3.0 kN\/m\u00b2Mustasaari &#8211; 2.0 kN\/m\u00b2Muurame &#8211; 2.5 kN\/m\u00b2Myna\u0308ma\u0308ki &#8211; 2.3 kN\/m\u00b2Myrskyla\u0308 &#8211; 2.65 kN\/m\u00b2Ma\u0308ntsa\u0308la\u0308 &#8211; 2.75 kN\/m\u00b2Ma\u0308ntta\u0308\u2010Vilppula &#8211; 2.5 kN\/m\u00b2Ma\u0308ntyharju &#8211; 2.5 kN\/m\u00b2Naantali &#8211; 2.5 kN\/m\u00b2Nakkila &#8211; 2.0 kN\/m\u00b2Nivala &#8211; 2.35 kN\/m\u00b2Nokia &#8211; 2.45 kN\/m\u00b2Nousiainen &#8211; 2.3 kN\/m\u00b2Nurmes &#8211; 3.0 kN\/m\u00b2Nurmija\u0308rvi &#8211; 2.75 kN\/m\u00b2Na\u0308rpio\u0308 &#8211; 2.1 kN\/m\u00b2Orimattila &#8211; 2.75 kN\/m\u00b2Oripa\u0308a\u0308 &#8211; 2.2 kN\/m\u00b2Orivesi &#8211; 2.5 kN\/m\u00b2Oulainen &#8211; 2.15 kN\/m\u00b2Oulu &#8211; 2.45 kN\/m\u00b2Outokumpu &#8211; 2.6 kN\/m\u00b2Padasjoki &#8211; 2.5 kN\/m\u00b2Paimio &#8211; 2.6 kN\/m\u00b2Paltamo &#8211; 3.3 kN\/m\u00b2Parainen &#8211; 2.5 kN\/m\u00b2Parikkala &#8211; 2.75 kN\/m\u00b2Parkano &#8211; 2.5 kN\/m\u00b2Pederso\u0308ren kunta &#8211; 2.1 kN\/m\u00b2Pelkosenniemi &#8211; 2.9 kN\/m\u00b2Pello &#8211; 3.0 kN\/m\u00b2Perho &#8211; 2.5 kN\/m\u00b2Pertunmaa &#8211; 2.5 kN\/m\u00b2Peta\u0308ja\u0308vesi &#8211; 2.5 kN\/m\u00b2Pieksa\u0308ma\u0308ki &#8211; 2.5 kN\/m\u00b2Pielavesi &#8211; 2.5 kN\/m\u00b2Pietarsaari &#8211; 2.0 kN\/m\u00b2Pihtipudas &#8211; 2.5 kN\/m\u00b2Pirkkala &#8211; 2.4 kN\/m\u00b2Polvija\u0308rvi &#8211; 2.75 kN\/m\u00b2Pomarkku &#8211; 2.4 kN\/m\u00b2Pori &#8211; 2.0 kN\/m\u00b2Pornainen &#8211; 2.75 kN\/m\u00b2Porvoo &#8211; 2.65 kN\/m\u00b2Posio &#8211; 3.5 kN\/m\u00b2Pudasja\u0308rvi &#8211; 3.5 kN\/m\u00b2Pukkila &#8211; 2.75 kN\/m\u00b2Punkalaidun &#8211; 2.1 kN\/m\u00b2Puolanka &#8211; 3.5 kN\/m\u00b2Puumala &#8211; 2.5 kN\/m\u00b2Pyhta\u0308a\u0308 &#8211; 2.5 kN\/m\u00b2Pyha\u0308joki &#8211; 2.05 kN\/m\u00b2Pyha\u0308ja\u0308rvi &#8211; 2.5 kN\/m\u00b2Pyha\u0308nta\u0308 &#8211; 2.75 kN\/m\u00b2Pyha\u0308ranta &#8211; 2.1 kN\/m\u00b2Pa\u0308lka\u0308ne &#8211; 2.5 kN\/m\u00b2Po\u0308ytya\u0308 &#8211; 2.5 kN\/m\u00b2Raahe &#8211; 2.1 kN\/m\u00b2Raasepori &#8211; 2.75 kN\/m\u00b2Raisio &#8211; 2.5 kN\/m\u00b2Rantasalmi &#8211; 2.5 kN\/m\u00b2Ranua &#8211; 3.2 kN\/m\u00b2Rauma &#8211; 2.0 kN\/m\u00b2Rautalampi &#8211; 2.5 kN\/m\u00b2Rautavaara &#8211; 3.0 kN\/m\u00b2Rautja\u0308rvi &#8211; 2.75 kN\/m\u00b2Reisja\u0308rvi &#8211; 2.5 kN\/m\u00b2Riihima\u0308ki &#8211; 2.75 kN\/m\u00b2Ristija\u0308rvi &#8211; 3.5 kN\/m\u00b2Rovaniemi &#8211; 3.0 kN\/m\u00b2Ruokolahti &#8211; 2.75 kN\/m\u00b2Ruovesi &#8211; 2.5 kN\/m\u00b2Rusko &#8211; 2.5 kN\/m\u00b2Ra\u0308a\u0308kkyla\u0308 &#8211; 2.6 kN\/m\u00b2Saarija\u0308rvi &#8211; 2.5 kN\/m\u00b2Salla &#8211; 3.0 kN\/m\u00b2Salo &#8211; 2.7 kN\/m\u00b2Saltvik &#8211; 2.0 kN\/m\u00b2Sastamala &#8211; 2.5 kN\/m\u00b2Sauvo &#8211; 2.6 kN\/m\u00b2Savitaipale &#8211; 2.65 kN\/m\u00b2Savonlinna &#8211; 2.5 kN\/m\u00b2Savukoski &#8211; 3.0 kN\/m\u00b2Seina\u0308joki &#8211; 2.5 kN\/m\u00b2Sievi &#8211; 2.35 kN\/m\u00b2Siikainen &#8211; 2.5 kN\/m\u00b2Siikajoki &#8211; 2.25 kN\/m\u00b2Siikalatva &#8211; 2.5 kN\/m\u00b2Siilinja\u0308rvi &#8211; 2.5 kN\/m\u00b2Simo &#8211; 3.0 kN\/m\u00b2Sipoo &#8211; 2.75 kN\/m\u00b2Siuntio &#8211; 2.75 kN\/m\u00b2Sodankyla\u0308 &#8211; 3.0 kN\/m\u00b2Soini &#8211; 2.5 kN\/m\u00b2Somero &#8211; 2.75 kN\/m\u00b2Sonkaja\u0308rvi &#8211; 3.0 kN\/m\u00b2Sotkamo &#8211; 3.4 kN\/m\u00b2Sottunga &#8211; 2.0 kN\/m\u00b2Sulkava &#8211; 2.5 kN\/m\u00b2Sund &#8211; 2.0 kN\/m\u00b2Suomussalmi &#8211; 3.5 kN\/m\u00b2Suonenjoki &#8211; 2.5 kN\/m\u00b2Sysma\u0308 &#8211; 2.5 kN\/m\u00b2Sa\u0308kyla\u0308 &#8211; 2.1 kN\/m\u00b2Taipalsaari &#8211; 2.7 kN\/m\u00b2Taivalkoski &#8211; 3.5 kN\/m\u00b2Taivassalo &#8211; 2.2 kN\/m\u00b2Tammela &#8211; 2.75 kN\/m\u00b2Tampere &#8211; 2.5 kN\/m\u00b2Tervo &#8211; 2.5 kN\/m\u00b2Tervola &#8211; 3.0 kN\/m\u00b2Teuva &#8211; 2.5 kN\/m\u00b2Tohmaja\u0308rvi &#8211; 2.75 kN\/m\u00b2Toholampi &#8211; 2.25 kN\/m\u00b2Toivakka &#8211; 2.5 kN\/m\u00b2Tornio &#8211; 3.0 kN\/m\u00b2Turku &#8211; 2.5 kN\/m\u00b2Tuusniemi &#8211; 2.5 kN\/m\u00b2Tuusula &#8211; 2.75 kN\/m\u00b2Tyrna\u0308va\u0308 &#8211; 2.4 kN\/m\u00b2Ulvila &#8211; 2.0 kN\/m\u00b2Urjala &#8211; 2.4 kN\/m\u00b2Utaja\u0308rvi &#8211; 2.85 kN\/m\u00b2Utsjoki &#8211; 2.5 kN\/m\u00b2Uurainen &#8211; 2.5 kN\/m\u00b2Uusikaarlepyy &#8211; 2.0 kN\/m\u00b2Uusikaupunki &#8211; 2.1 kN\/m\u00b2Vaala &#8211; 2.75 kN\/m\u00b2Vaasa &#8211; 2.0 kN\/m\u00b2Valkeakoski &#8211; 2.4 kN\/m\u00b2Valtimo &#8211; 3.0 kN\/m\u00b2Vantaa &#8211; 2.75 kN\/m\u00b2Varkaus &#8211; 2.5 kN\/m\u00b2Vehmaa<\/p>\n","protected":false},"author":1,"featured_media":2024,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"om_disable_all_campaigns":false,"_mi_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"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":"","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","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":""},"class_list":["post-862","page","type-page","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.7 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Fixture Calculator - Moisolar<\/title>\n<meta name=\"description\" content=\"The fixture calculator is an intuitive tool simplifies your solar installation planning. Begin optimizing your panel installation today with our calculator. Designed for all project scales, it aids in efficient resource management and smoother installations.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/moisolar.com\/en\/fixture-calculator\/\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Fixture Calculator - Moisolar\" \/>\n<meta property=\"og:description\" content=\"The fixture calculator is an intuitive tool simplifies your solar installation planning. Begin optimizing your panel installation today with our calculator. Designed for all project scales, it aids in efficient resource management and smoother installations.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/moisolar.com\/en\/fixture-calculator\/\" \/>\n<meta property=\"og:site_name\" content=\"Moisolar\" \/>\n<meta property=\"article:modified_time\" content=\"2026-01-14T13:04:38+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/calculatorBackground-1.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1756\" \/>\n\t<meta property=\"og:image:height\" content=\"1144\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/moisolar.com\/kalustelaskuri\/\",\"url\":\"https:\/\/moisolar.com\/kalustelaskuri\/\",\"name\":\"Fixture Calculator - Moisolar\",\"isPartOf\":{\"@id\":\"https:\/\/moisolar.com\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/moisolar.com\/kalustelaskuri\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/moisolar.com\/kalustelaskuri\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/calculatorBackground-1.jpg\",\"datePublished\":\"2023-06-07T07:27:59+00:00\",\"dateModified\":\"2026-01-14T13:04:38+00:00\",\"description\":\"The fixture calculator is an intuitive tool simplifies your solar installation planning. Begin optimizing your panel installation today with our calculator. Designed for all project scales, it aids in efficient resource management and smoother installations.\",\"breadcrumb\":{\"@id\":\"https:\/\/moisolar.com\/kalustelaskuri\/#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/moisolar.com\/kalustelaskuri\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\/\/moisolar.com\/kalustelaskuri\/#primaryimage\",\"url\":\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/calculatorBackground-1.jpg\",\"contentUrl\":\"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/calculatorBackground-1.jpg\",\"width\":1756,\"height\":1144},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/moisolar.com\/kalustelaskuri\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/moisolar.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Fixture Calculator\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/moisolar.com\/en\/#website\",\"url\":\"https:\/\/moisolar.com\/en\/\",\"name\":\"Moisolar\",\"description\":\"Moisolar Oy | Aurinkopaneelin kiinnikkeet ja tasakattotelineet\",\"publisher\":{\"@id\":\"https:\/\/moisolar.com\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/moisolar.com\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-GB\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/moisolar.com\/en\/#organization\",\"name\":\"Moisolar\",\"url\":\"https:\/\/moisolar.com\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\/\/moisolar.com\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/02\/cropped-Screenshot_2023-02-12_at_23.47.03-removebg-preview-2.png\",\"contentUrl\":\"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/02\/cropped-Screenshot_2023-02-12_at_23.47.03-removebg-preview-2.png\",\"width\":1016,\"height\":246,\"caption\":\"Moisolar\"},\"image\":{\"@id\":\"https:\/\/moisolar.com\/en\/#\/schema\/logo\/image\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Fixture Calculator - Moisolar","description":"The fixture calculator is an intuitive tool simplifies your solar installation planning. Begin optimizing your panel installation today with our calculator. Designed for all project scales, it aids in efficient resource management and smoother installations.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/moisolar.com\/en\/fixture-calculator\/","og_locale":"en_GB","og_type":"article","og_title":"Fixture Calculator - Moisolar","og_description":"The fixture calculator is an intuitive tool simplifies your solar installation planning. Begin optimizing your panel installation today with our calculator. Designed for all project scales, it aids in efficient resource management and smoother installations.","og_url":"https:\/\/moisolar.com\/en\/fixture-calculator\/","og_site_name":"Moisolar","article_modified_time":"2026-01-14T13:04:38+00:00","og_image":[{"width":1756,"height":1144,"url":"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/calculatorBackground-1.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/moisolar.com\/kalustelaskuri\/","url":"https:\/\/moisolar.com\/kalustelaskuri\/","name":"Fixture Calculator - Moisolar","isPartOf":{"@id":"https:\/\/moisolar.com\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/moisolar.com\/kalustelaskuri\/#primaryimage"},"image":{"@id":"https:\/\/moisolar.com\/kalustelaskuri\/#primaryimage"},"thumbnailUrl":"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/calculatorBackground-1.jpg","datePublished":"2023-06-07T07:27:59+00:00","dateModified":"2026-01-14T13:04:38+00:00","description":"The fixture calculator is an intuitive tool simplifies your solar installation planning. Begin optimizing your panel installation today with our calculator. Designed for all project scales, it aids in efficient resource management and smoother installations.","breadcrumb":{"@id":"https:\/\/moisolar.com\/kalustelaskuri\/#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/moisolar.com\/kalustelaskuri\/"]}]},{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/moisolar.com\/kalustelaskuri\/#primaryimage","url":"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/calculatorBackground-1.jpg","contentUrl":"https:\/\/moisolar.com\/wp-content\/uploads\/2024\/03\/calculatorBackground-1.jpg","width":1756,"height":1144},{"@type":"BreadcrumbList","@id":"https:\/\/moisolar.com\/kalustelaskuri\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/moisolar.com\/"},{"@type":"ListItem","position":2,"name":"Fixture Calculator"}]},{"@type":"WebSite","@id":"https:\/\/moisolar.com\/en\/#website","url":"https:\/\/moisolar.com\/en\/","name":"Moisolar","description":"Moisolar Oy | Aurinkopaneelin kiinnikkeet ja tasakattotelineet","publisher":{"@id":"https:\/\/moisolar.com\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/moisolar.com\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-GB"},{"@type":"Organization","@id":"https:\/\/moisolar.com\/en\/#organization","name":"Moisolar","url":"https:\/\/moisolar.com\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/moisolar.com\/en\/#\/schema\/logo\/image\/","url":"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/02\/cropped-Screenshot_2023-02-12_at_23.47.03-removebg-preview-2.png","contentUrl":"https:\/\/moisolar.com\/wp-content\/uploads\/2023\/02\/cropped-Screenshot_2023-02-12_at_23.47.03-removebg-preview-2.png","width":1016,"height":246,"caption":"Moisolar"},"image":{"@id":"https:\/\/moisolar.com\/en\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/moisolar.com\/en\/wp-json\/wp\/v2\/pages\/862","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/moisolar.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/moisolar.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/moisolar.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/moisolar.com\/en\/wp-json\/wp\/v2\/comments?post=862"}],"version-history":[{"count":761,"href":"https:\/\/moisolar.com\/en\/wp-json\/wp\/v2\/pages\/862\/revisions"}],"predecessor-version":[{"id":3675,"href":"https:\/\/moisolar.com\/en\/wp-json\/wp\/v2\/pages\/862\/revisions\/3675"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/moisolar.com\/en\/wp-json\/wp\/v2\/media\/2024"}],"wp:attachment":[{"href":"https:\/\/moisolar.com\/en\/wp-json\/wp\/v2\/media?parent=862"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}