{"id":70822,"date":"2026-10-05T16:07:45","date_gmt":"2026-10-05T13:07:45","guid":{"rendered":"https:\/\/kavkom.com\/useful-information\/integrate-telephony-into-your-software-with-the-kavkom-api\/"},"modified":"2026-10-05T16:41:23","modified_gmt":"2026-10-05T13:41:23","slug":"integrate-telephony-into-your-software-with-the-kavkom-api","status":"publish","type":"post","link":"https:\/\/kavkom.com\/en\/lp-en\/ggl-en\/api\/integrate-telephony-into-your-software-with-the-kavkom-api\/","title":{"rendered":"Integrate telephony into your software with the Kavkom API"},"content":{"rendered":"\n<style data-wp-block-html=\"css\">\n\/* =========================================================\n   KAVKOM API LANDING PAGE\n   CSS\n   ========================================================= *\/\n\n\n\/* =========================\n   RESET \/ BASE\n   ========================= *\/\n\n.kavkom-api-page,\n.kavkom-api-page * {\n  box-sizing: border-box;\n}\n\nhtml {\n  scroll-behavior: smooth;\n}\n\nbody {\n  margin: 0;\n}\n\n.kavkom-api-page {\n  --navy: #041545;\n  --blue: #168AFF;\n  --blue-dark: #2563EB;\n  --blue-hover: #1D4ED8;\n  --text: #4A5573;\n  --muted: #8A93AB;\n  --border: #E3EAF4;\n  --light: #F7F9FC;\n  --white: #FFFFFF;\n  --green: #4ADE80;\n\n  width: 100%;\n  min-height: 100vh;\n\n  color: var(--navy);\n  background: #FBFBFC;\n\n  font-family:\n    \"HarmoniaSans\",\n    \"Inter\",\n    -apple-system,\n    BlinkMacSystemFont,\n    \"Segoe UI\",\n    Arial,\n    sans-serif;\n\n  font-size: 16px;\n  line-height: 1.5;\n\n  overflow-x: hidden;\n}\n\n.kavkom-api-page a {\n  text-decoration: none;\n}\n\n.kavkom-api-page img {\n  max-width: 100%;\n}\n\n.kavkom-container {\n  width: 100%;\n  max-width: 1160px;\n  margin-left: auto;\n  margin-right: auto;\n  padding-left: 24px;\n  padding-right: 24px;\n}\n\n\n\/* =========================\n   TYPOGRAPHIE\n   ========================= *\/\n\n.kavkom-api-page h1,\n.kavkom-api-page h2,\n.kavkom-api-page h3,\n.kavkom-api-page p {\n  margin-top: 0;\n}\n\n.kavkom-api-page h1,\n.kavkom-api-page h2 {\n  font-family:\n    \"Degular\",\n    \"Outfit\",\n    -apple-system,\n    BlinkMacSystemFont,\n    \"Segoe UI\",\n    sans-serif;\n\n  color: var(--navy);\n  letter-spacing: -0.025em;\n}\n\n.kavkom-api-page h1 {\n  font-size: clamp(40px, 6vw, 68px);\n  line-height: 1;\n  font-weight: 600;\n}\n\n.kavkom-api-page h2 {\n  font-size: clamp(32px, 4vw, 46px);\n  line-height: 1.05;\n  font-weight: 600;\n}\n\n.kavkom-api-page h3 {\n  color: var(--navy);\n  font-size: 19px;\n  line-height: 1.25;\n  font-weight: 650;\n}\n\n.eyebrow,\n.section-label,\n.form-label {\n  font-family:\n    \"JetBrains Mono\",\n    \"SFMono-Regular\",\n    Consolas,\n    monospace;\n\n  font-size: 12px;\n  font-weight: 500;\n  letter-spacing: 0.14em;\n  text-transform: uppercase;\n}\n\n.eyebrow {\n  color: var(--blue);\n}\n\n.section-heading {\n  margin-bottom: 44px;\n}\n\n.section-heading h2 {\n  max-width: 680px;\n  margin: 16px 0;\n}\n\n.section-heading p {\n  max-width: 650px;\n  color: var(--text);\n  font-size: 17px;\n  line-height: 1.6;\n}\n\n\n\/* =========================\n   HEADER\n   ========================= *\/\n\n.kavkom-header {\n  position: sticky;\n  top: 0;\n  z-index: 999;\n\n  width: 100%;\n\n  background: rgba(251, 251, 252, 0.88);\n  backdrop-filter: blur(10px);\n  -webkit-backdrop-filter: blur(10px);\n\n  border-bottom: 1px solid var(--border);\n}\n\n.kavkom-header-inner {\n  min-height: 64px;\n\n  display: flex;\n  align-items: center;\n\n  gap: 24px;\n\n  padding-top: 16px;\n  padding-bottom: 16px;\n}\n\n.kavkom-logo {\n  display: flex;\n  align-items: center;\n  flex-shrink: 0;\n}\n\n.kavkom-logo img {\n  display: block;\n  width: auto;\n  height: 30px;\n}\n\n.kavkom-nav {\n  display: flex;\n  align-items: center;\n  flex-wrap: wrap;\n\n  gap: 22px;\n\n  margin-left: auto;\n}\n\n.kavkom-nav a {\n  color: var(--text);\n\n  font-size: 14px;\n  font-weight: 500;\n\n  transition: color 0.2s ease;\n}\n\n.kavkom-nav a:hover {\n  color: var(--navy);\n}\n\n\n\/* =========================\n   BOUTONS\n   ========================= *\/\n\n.btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n\n  border-radius: 10px;\n\n  font-size: 15px;\n  font-weight: 600;\n  line-height: 1.25;\n\n  transition:\n    background 0.2s ease,\n    border-color 0.2s ease,\n    color 0.2s ease,\n    transform 0.2s ease,\n    box-shadow 0.2s ease;\n}\n\n.btn-primary {\n  padding: 14px 22px;\n\n  color: #fff !important;\n  background: var(--blue-dark);\n\n  box-shadow:\n    0 8px 20px -8px rgba(37, 99, 235, 0.55);\n}\n\n.btn-primary:hover {\n  color: #fff !important;\n  background: var(--blue-hover);\n\n  transform: translateY(-2px);\n\n  box-shadow:\n    0 12px 28px -8px rgba(37, 99, 235, 0.6);\n}\n\n.btn-outline {\n  padding: 12.5px 20px;\n\n  color: var(--blue-dark) !important;\n  background: #fff;\n\n  border: 1.5px solid var(--blue-dark);\n}\n\n.btn-outline:hover {\n  color: var(--blue-hover) !important;\n\n  background: #EDF4FF;\n  border-color: var(--blue-hover);\n\n  transform: translateY(-2px);\n}\n\n.btn-header {\n  padding: 10px 16px;\n  font-size: 14px;\n  flex-shrink: 0;\n}\n\n\n\/* =========================\n   HERO\n   ========================= *\/\n\n.hero {\n  padding-top: 72px;\n  padding-bottom: 56px;\n\n  display: grid;\n\n  grid-template-columns:\n    repeat(\n      auto-fit,\n      minmax(min(100%, 460px), 1fr)\n    );\n\n  gap: 56px;\n  align-items: center;\n}\n\n.hero-content {\n  min-width: 0;\n}\n\n.hero h1 {\n  margin: 20px 0 22px;\n\n  max-width: 650px;\n\n  text-wrap: balance;\n}\n\n.hero h1 span {\n  color: var(--blue);\n}\n\n.hero-description {\n  max-width: 540px;\n\n  margin-bottom: 28px;\n\n  color: var(--text);\n\n  font-size: 18px;\n  line-height: 1.55;\n\n  text-wrap: pretty;\n}\n\n.hero-description strong {\n  color: var(--navy);\n  font-weight: 600;\n}\n\n.hero-actions {\n  display: flex;\n  flex-wrap: wrap;\n\n  gap: 12px;\n\n  margin-bottom: 28px;\n}\n\n.hero-badges {\n  display: flex;\n  flex-wrap: wrap;\n\n  gap: 8px;\n}\n\n.badge {\n  display: inline-flex;\n  align-items: center;\n\n  gap: 8px;\n\n  padding: 7px 14px;\n\n  color: var(--text);\n  background: #fff;\n\n  border: 1px solid var(--border);\n  border-radius: 999px;\n\n  font-size: 13px;\n}\n\n.badge-dot {\n  display: block;\n\n  width: 7px;\n  height: 7px;\n\n  border-radius: 50%;\n}\n\n.badge-dot-blue {\n  background: var(--blue);\n}\n\n.badge-dot-green {\n  background: #22C55E;\n}\n\n\n\/* =========================\n   CODE WINDOW\n   ========================= *\/\n\n.code-window {\n  width: 100%;\n\n  background: var(--navy);\n\n  border: 1px solid #17326F;\n  border-radius: 18px;\n\n  overflow: hidden;\n\n  box-shadow:\n    0 30px 70px -30px rgba(4, 21, 69, 0.6);\n}\n\n.code-header {\n  min-height: 52px;\n\n  padding: 12px 16px;\n\n  display: flex;\n  align-items: center;\n\n  gap: 10px;\n\n  background: #071B52;\n\n  border-bottom: 1px solid #17326F;\n}\n\n.window-controls {\n  display: flex;\n  align-items: center;\n\n  gap: 6px;\n}\n\n.window-dot {\n  display: block;\n\n  width: 10px;\n  height: 10px;\n\n  border-radius: 50%;\n}\n\n.window-dot.red {\n  background: #FF5F57;\n}\n\n.window-dot.yellow {\n  background: #FEBC2E;\n}\n\n.window-dot.green {\n  background: #28C840;\n}\n\n.code-filename {\n  margin-left: 8px;\n\n  color: #8A9BC4;\n\n  font-family:\n    \"JetBrains Mono\",\n    Consolas,\n    monospace;\n\n  font-size: 12px;\n}\n\n.copy-code-btn {\n  margin-left: auto;\n\n  padding: 4px 10px;\n\n  color: #B6C3E0;\n  background: transparent;\n\n  border: 1px solid #24438A;\n  border-radius: 999px;\n\n  font-family:\n    \"JetBrains Mono\",\n    Consolas,\n    monospace;\n\n  font-size: 11px;\n\n  cursor: pointer;\n\n  transition:\n    color 0.2s ease,\n    border-color 0.2s ease;\n}\n\n.copy-code-btn:hover {\n  color: var(--green);\n  border-color: var(--green);\n}\n\n.live-badge {\n  display: inline-flex;\n  align-items: center;\n\n  gap: 6px;\n\n  padding: 4px 10px;\n\n  color: var(--green);\n  background: #0F2A1E;\n\n  border: 1px solid #1F5A3A;\n  border-radius: 999px;\n\n  font-family:\n    \"JetBrains Mono\",\n    Consolas,\n    monospace;\n\n  font-size: 11px;\n  font-weight: 500;\n\n  letter-spacing: 0.1em;\n}\n\n.live-dot {\n  display: block;\n\n  width: 6px;\n  height: 6px;\n\n  border-radius: 50%;\n\n  background: var(--green);\n\n  animation: kavkomPulse 1.6s infinite;\n}\n\n.code-window pre {\n  margin: 0;\n\n  padding: 20px;\n\n  overflow-x: auto;\n\n  color: #E2E8F0;\n\n  font-family:\n    \"JetBrains Mono\",\n    Consolas,\n    monospace;\n\n  font-size: 13px;\n  line-height: 1.75;\n\n  white-space: pre;\n}\n\n.code-window code {\n  font-family: inherit;\n}\n\n\n\/* =========================\n   INFRA \/ TECHNOLOGIES\n   ========================= *\/\n\n.infra {\n  padding-top: 0;\n  padding-bottom: 72px;\n}\n\n.section-label {\n  padding-bottom: 14px;\n\n  color: var(--text);\n\n  border-bottom: 1px solid var(--border);\n}\n\n.infra-list {\n  display: flex;\n  flex-wrap: wrap;\n\n  gap: 10px 28px;\n\n  padding-top: 22px;\n\n  color: var(--text);\n\n  font-size: 14px;\n}\n\n.infra-list span:nth-child(even) {\n  color: #C2CAD8;\n}\n\n\n\/* =========================\n   SOCIAL PROOF\n   ========================= *\/\n\n.social-proof {\n  padding: 48px 0;\n\n  background: #fff;\n\n  border-top: 1px solid var(--border);\n  border-bottom: 1px solid var(--border);\n}\n\n.stats {\n  display: grid;\n\n  grid-template-columns:\n    repeat(3, 1fr);\n\n  margin-bottom: 42px;\n\n  border: 1px solid var(--border);\n  border-radius: 16px;\n\n  background: #fff;\n\n  overflow: hidden;\n}\n\n.stat {\n  padding: 24px;\n\n  text-align: center;\n}\n\n.stat + .stat {\n  border-left: 1px solid var(--border);\n}\n\n.stat strong {\n  display: block;\n\n  margin-bottom: 4px;\n\n  color: var(--navy);\n\n  font-family:\n    \"Degular\",\n    \"Outfit\",\n    sans-serif;\n\n  font-size: 34px;\n  line-height: 1;\n}\n\n.stat span {\n  color: var(--text);\n  font-size: 13px;\n}\n\n.client-logos {\n  display: flex;\n  align-items: center;\n  justify-content: space-around;\n\n  gap: 48px;\n\n  overflow: hidden;\n\n  -webkit-mask-image:\n    linear-gradient(\n      90deg,\n      transparent,\n      #000 10%,\n      #000 90%,\n      transparent\n    );\n\n  mask-image:\n    linear-gradient(\n      90deg,\n      transparent,\n      #000 10%,\n      #000 90%,\n      transparent\n    );\n}\n\n.client-logos img {\n  width: 68px;\n  height: 68px;\n\n  flex: none;\n\n  object-fit: contain;\n\n  filter: grayscale(1) contrast(1.1);\n\n  opacity: 0.75;\n\n  transition:\n    opacity 0.25s ease,\n    filter 0.25s ease;\n}\n\n.client-logos img:hover {\n  opacity: 1;\n  filter: grayscale(0);\n}\n\n\n\/* =========================\n   SECTIONS GENERALES\n   ========================= *\/\n\n.features-section,\n.webhooks-section,\n.sip-section,\n.quickstart-section,\n.usecases-section,\n.comparison-section,\n.infrastructure-section,\n.pricing-section,\n.faq-section,\n.contact-section {\n  scroll-margin-top: 80px;\n}\n\n.features-section,\n.sip-section,\n.usecases-section,\n.infrastructure-section,\n.faq-section {\n  background: #fff;\n}\n\n.webhooks-section,\n.quickstart-section,\n.comparison-section,\n.pricing-section {\n  background: #F7F9FC;\n}\n\n.features-section,\n.webhooks-section,\n.sip-section,\n.quickstart-section,\n.usecases-section,\n.comparison-section,\n.infrastructure-section,\n.pricing-section {\n  border-bottom: 1px solid var(--border);\n}\n\n.features-section > .kavkom-container,\n.webhooks-section > .kavkom-container,\n.sip-section > .kavkom-container,\n.quickstart-section > .kavkom-container,\n.usecases-section > .kavkom-container,\n.comparison-section > .kavkom-container,\n.infrastructure-section > .kavkom-container,\n.pricing-section > .kavkom-container {\n  padding-top: 88px;\n  padding-bottom: 88px;\n}\n\n\n\/* =========================\n   FEATURE CARDS\n   ========================= *\/\n\n.features-grid {\n  display: grid;\n\n  grid-template-columns:\n    repeat(\n      auto-fill,\n      minmax(min(100%, 300px), 1fr)\n    );\n\n  gap: 16px;\n}\n\n.feature-card {\n  position: relative;\n\n  display: flex;\n  flex-direction: column;\n\n  gap: 10px;\n\n  min-height: 230px;\n\n  padding: 24px;\n\n  background: #fff;\n\n  border: 1px solid var(--border);\n  border-radius: 16px;\n\n  box-shadow:\n    0 1px 2px rgba(4, 21, 69, 0.04),\n    0 8px 24px -14px rgba(4, 21, 69, 0.12);\n\n  transition:\n    box-shadow 0.25s ease,\n    transform 0.25s ease,\n    border-color 0.25s ease;\n}\n\n.feature-card:hover {\n  transform: translateY(-4px);\n\n  border-color: var(--blue);\n\n  box-shadow:\n    0 2px 4px rgba(4, 21, 69, 0.05),\n    0 20px 40px -16px rgba(4, 21, 69, 0.22);\n}\n\n.feature-number,\n.step-number,\n.pricing-number {\n  color: var(--blue);\n\n  font-family:\n    \"JetBrains Mono\",\n    Consolas,\n    monospace;\n\n  font-size: 12px;\n  letter-spacing: 0.08em;\n}\n\n.feature-card h3 {\n  margin: 2px 0 0;\n}\n\n.feature-card p {\n  flex-grow: 1;\n\n  margin-bottom: 8px;\n\n  color: var(--text);\n\n  font-size: 14px;\n  line-height: 1.55;\n}\n\n.feature-card code {\n  width: fit-content;\n\n  padding: 5px 8px;\n\n  color: var(--blue-dark);\n  background: #EDF4FF;\n\n  border-radius: 6px;\n\n  font-family:\n    \"JetBrains Mono\",\n    Consolas,\n    monospace;\n\n  font-size: 11px;\n}\n\n\n\/* =========================\n   TWO COLUMNS\n   ========================= *\/\n\n.two-columns {\n  display: grid;\n\n  grid-template-columns:\n    repeat(\n      2,\n      minmax(0, 1fr)\n    );\n\n  gap: 70px;\n\n  align-items: center;\n}\n\n\n\/* =========================\n   CALL DEMO\n   ========================= *\/\n\n.call-demo {\n  padding: 26px;\n\n  color: #fff;\n  background: var(--navy);\n\n  border: 1px solid #183472;\n  border-radius: 18px;\n\n  box-shadow:\n    0 25px 60px -30px rgba(4, 21, 69, 0.7);\n}\n\n.call-demo-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n\n  margin-bottom: 32px;\n\n  color: #B6C3E0;\n\n  font-size: 13px;\n}\n\n.call-demo-number {\n  margin-bottom: 30px;\n\n  color: #fff;\n\n  font-family:\n    \"JetBrains Mono\",\n    Consolas,\n    monospace;\n\n  font-size: clamp(22px, 3vw, 32px);\n}\n\n.call-demo-statuses {\n  display: grid;\n\n  grid-template-columns:\n    repeat(4, 1fr);\n\n  gap: 7px;\n\n  margin-bottom: 28px;\n}\n\n.call-demo-statuses span {\n  padding: 8px 4px;\n\n  color: #8A93AB;\n\n  border-top: 3px solid #D7E0EE;\n\n  font-family:\n    \"JetBrains Mono\",\n    Consolas,\n    monospace;\n\n  font-size: 10px;\n\n  text-align: center;\n}\n\n.call-demo-statuses span.active {\n  color: var(--blue);\n  border-color: var(--blue);\n}\n\n.call-demo-time {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n\n  padding-top: 18px;\n\n  border-top: 1px solid #17326F;\n\n  color: #8A9BC4;\n\n  font-size: 13px;\n}\n\n.call-demo-time strong {\n  color: #fff;\n\n  font-family:\n    \"JetBrains Mono\",\n    Consolas,\n    monospace;\n\n  font-size: 20px;\n}\n\n\n\/* =========================\n   SIP\n   ========================= *\/\n\n.sip-grid {\n  display: grid;\n\n  grid-template-columns:\n    repeat(4, 1fr);\n\n  gap: 16px;\n}\n\n.sip-card {\n  padding: 24px;\n\n  border: 1px solid var(--border);\n  border-radius: 16px;\n\n  background: #fff;\n\n  transition:\n    transform 0.25s ease,\n    border-color 0.25s ease,\n    box-shadow 0.25s ease;\n}\n\n.sip-card:hover {\n  transform: translateY(-3px);\n\n  border-color: #B9D9FF;\n\n  box-shadow:\n    0 15px 30px -18px rgba(4, 21, 69, 0.25);\n}\n\n.sip-card h3 {\n  margin-bottom: 10px;\n}\n\n.sip-card p {\n  margin-bottom: 0;\n\n  color: var(--text);\n\n  font-size: 14px;\n  line-height: 1.55;\n}\n\n\n\/* =========================\n   QUICKSTART\n   ========================= *\/\n\n.quickstart-grid {\n  display: grid;\n\n  grid-template-columns:\n    repeat(3, 1fr);\n\n  gap: 20px;\n\n  counter-reset: quickstart;\n}\n\n.quickstart-step {\n  position: relative;\n\n  padding: 28px;\n\n  background: #fff;\n\n  border: 1px solid var(--border);\n  border-radius: 16px;\n}\n\n.quickstart-step h3 {\n  margin: 12px 0 10px;\n\n  font-size: 20px;\n}\n\n.quickstart-step p {\n  margin: 0;\n\n  color: var(--text);\n\n  font-size: 14px;\n  line-height: 1.6;\n}\n\n\n\/* =========================\n   USE CASES\n   ========================= *\/\n\n.usecases-grid {\n  display: grid;\n\n  grid-template-columns:\n    repeat(4, 1fr);\n\n  gap: 16px;\n}\n\n.usecases-grid article {\n  padding: 24px;\n\n  background: #fff;\n\n  border: 1px solid var(--border);\n  border-radius: 16px;\n}\n\n.usecases-grid h3 {\n  margin-bottom: 10px;\n}\n\n.usecases-grid p {\n  margin: 0;\n\n  color: var(--text);\n\n  font-size: 14px;\n  line-height: 1.55;\n}\n\n\n\/* =========================\n   TABLE COMPARAISON\n   ========================= *\/\n\n.comparison-table-wrapper {\n  overflow-x: auto;\n\n  background: #fff;\n\n  border: 1px solid var(--border);\n  border-radius: 18px;\n}\n\n.comparison-table {\n  width: 100%;\n  min-width: 700px;\n\n  border-collapse: collapse;\n}\n\n.comparison-table th,\n.comparison-table td {\n  padding: 18px 22px;\n\n  border-bottom: 1px solid var(--border);\n\n  text-align: left;\n}\n\n.comparison-table th {\n  color: var(--text);\n  background: #F8FAFD;\n\n  font-size: 13px;\n  font-weight: 600;\n}\n\n.comparison-table td {\n  color: var(--text);\n\n  font-size: 14px;\n}\n\n.comparison-table td:nth-child(2) {\n  color: var(--navy);\n\n  font-weight: 600;\n}\n\n.comparison-table tr:last-child td {\n  border-bottom: 0;\n}\n\n\n\/* =========================\n   INFRASTRUCTURE\n   ========================= *\/\n\n.infrastructure-grid {\n  display: grid;\n\n  grid-template-columns:\n    repeat(4, 1fr);\n\n  gap: 16px;\n}\n\n.infrastructure-grid article {\n  padding: 24px;\n\n  border: 1px solid var(--border);\n  border-radius: 16px;\n\n  background: #fff;\n}\n\n.infrastructure-grid h3 {\n  margin-bottom: 10px;\n}\n\n.infrastructure-grid p {\n  margin: 0;\n\n  color: var(--text);\n\n  font-size: 14px;\n  line-height: 1.55;\n}\n\n\n\/* =========================\n   TARIFS\n   ========================= *\/\n\n.pricing-grid {\n  display: grid;\n\n  grid-template-columns:\n    repeat(3, 1fr);\n\n  gap: 18px;\n}\n\n.pricing-card {\n  padding: 28px;\n\n  background: #fff;\n\n  border: 1px solid var(--border);\n  border-radius: 18px;\n\n  box-shadow:\n    0 10px 30px -22px rgba(4, 21, 69, 0.3);\n}\n\n.pricing-card-highlight {\n  border-color: #9CCBFF;\n\n  box-shadow:\n    0 20px 50px -26px rgba(22, 138, 255, 0.5);\n}\n\n.pricing-card h3 {\n  margin: 12px 0 10px;\n\n  font-size: 21px;\n}\n\n.pricing-card p {\n  margin: 0;\n\n  color: var(--text);\n\n  font-size: 14px;\n  line-height: 1.6;\n}\n\n.pricing-cta {\n  margin-top: 32px;\n}\n\n\n\/* =========================\n   FAQ\n   ========================= *\/\n\n.faq-section {\n  padding-top: 88px;\n  padding-bottom: 88px;\n\n  display: grid;\n\n  grid-template-columns:\n    minmax(240px, 0.7fr)\n    minmax(0, 1.3fr);\n\n  gap: 72px;\n}\n\n.faq-intro h2 {\n  margin: 16px 0;\n}\n\n.faq-intro p {\n  color: var(--text);\n}\n\n.faq-list {\n  border-top: 1px solid var(--border);\n}\n\n.faq-item {\n  border-bottom: 1px solid var(--border);\n}\n\n.faq-question {\n  width: 100%;\n\n  padding: 20px 0;\n\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n\n  gap: 20px;\n\n  color: var(--navy);\n  background: transparent;\n\n  border: 0;\n\n  font: inherit;\n  font-size: 16px;\n  font-weight: 600;\n\n  text-align: left;\n\n  cursor: pointer;\n}\n\n.faq-icon {\n  width: 30px;\n  height: 30px;\n\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  flex: none;\n\n  color: var(--blue);\n\n  background: #EDF4FF;\n\n  border-radius: 50%;\n\n  font-size: 20px;\n  font-weight: 400;\n\n  transition: transform 0.25s ease;\n}\n\n.faq-item.active .faq-icon {\n  transform: rotate(45deg);\n}\n\n.faq-answer {\n  display: none;\n\n  max-width: 650px;\n\n  padding: 0 45px 20px 0;\n}\n\n.faq-item.active .faq-answer {\n  display: block;\n}\n\n.faq-answer p {\n  margin: 0;\n\n  color: var(--text);\n\n  font-size: 14px;\n  line-height: 1.65;\n}\n\n\n\/* =========================\n   CONTACT\n   ========================= *\/\n\n.contact-section {\n  padding: 92px 0 0;\n\n  color: #fff;\n\n  background: var(--navy);\n}\n\n.contact-heading {\n  max-width: 720px;\n\n  margin-bottom: 40px;\n}\n\n.contact-heading .eyebrow {\n  color: #9CCBFF;\n}\n\n.contact-heading h2 {\n  margin: 16px 0;\n\n  color: #fff;\n}\n\n.contact-heading p {\n  color: #B6C3E0;\n\n  font-size: 17px;\n  line-height: 1.6;\n}\n\n.contact-heading strong {\n  color: #fff;\n}\n\n.contact-form-wrapper {\n  max-width: 760px;\n\n  margin-bottom: 54px;\n}\n\n.contact-form {\n  padding: 28px;\n\n  color: var(--navy);\n  background: #fff;\n\n  border-radius: 18px;\n\n  box-shadow:\n    0 20px 60px -25px rgba(0, 0, 0, 0.35);\n}\n\n.form-label {\n  margin-bottom: 20px;\n\n  color: var(--blue);\n}\n\n.form-benefits {\n  display: flex;\n  flex-wrap: wrap;\n\n  gap: 10px 20px;\n\n  margin-top: 20px;\n  padding-top: 18px;\n\n  color: var(--text);\n\n  border-top: 1px solid var(--border);\n\n  font-size: 13px;\n}\n\n\n\/* =========================\n   HUBSPOT\n   ========================= *\/\n\n#kvklp26-hsform-main .hs-form {\n  font-family: inherit !important;\n}\n\n#kvklp26-hsform-main .hs-form-field {\n  margin-bottom: 16px;\n}\n\n#kvklp26-hsform-main label {\n  display: block !important;\n\n  margin-bottom: 6px !important;\n\n  color: var(--navy) !important;\n\n  font-family: inherit !important;\n  font-size: 13px !important;\n  font-weight: 600 !important;\n}\n\n#kvklp26-hsform-main input,\n#kvklp26-hsform-main textarea,\n#kvklp26-hsform-main select {\n  width: 100% !important;\n\n  padding: 12px 14px !important;\n\n  color: var(--navy) !important;\n  background: #fff !important;\n\n  border: 1px solid #D7E0EE !important;\n  border-radius: 9px !important;\n\n  font-family: inherit !important;\n  font-size: 15px !important;\n\n  outline: none !important;\n\n  transition:\n    border-color 0.2s ease,\n    box-shadow 0.2s ease !important;\n}\n\n#kvklp26-hsform-main input:focus,\n#kvklp26-hsform-main textarea:focus,\n#kvklp26-hsform-main select:focus {\n  border-color: var(--blue) !important;\n\n  box-shadow:\n    0 0 0 3px rgba(22, 138, 255, 0.12) !important;\n}\n\n#kvklp26-hsform-main input[type=\"submit\"],\n#kvklp26-hsform-main .hs-button {\n  width: auto !important;\n\n  padding: 13px 22px !important;\n\n  color: #fff !important;\n  background: var(--blue-dark) !important;\n\n  border: 0 !important;\n  border-radius: 10px !important;\n\n  font-weight: 600 !important;\n\n  cursor: pointer !important;\n}\n\n#kvklp26-hsform-main input[type=\"submit\"]:hover,\n#kvklp26-hsform-main .hs-button:hover {\n  background: var(--blue-hover) !important;\n}\n\n\n\/* =========================\n   CONTACT LINKS\n   ========================= *\/\n\n.contact-links {\n  display: grid;\n\n  grid-template-columns:\n    repeat(4, 1fr);\n\n  border-top: 1px solid #17326F;\n  border-bottom: 1px solid #17326F;\n}\n\n.contact-link {\n  padding: 24px 20px;\n\n  color: #fff;\n\n  transition: background 0.2s ease;\n}\n\n.contact-link + .contact-link {\n  border-left: 1px solid #17326F;\n}\n\n.contact-link:hover {\n  background: rgba(255, 255, 255, 0.04);\n}\n\n.contact-link span {\n  display: block;\n\n  margin-bottom: 8px;\n\n  color: #8A9BC4;\n\n  font-family:\n    \"JetBrains Mono\",\n    Consolas,\n    monospace;\n\n  font-size: 10px;\n  letter-spacing: 0.12em;\n}\n\n.contact-link strong {\n  color: #fff;\n\n  font-size: 14px;\n}\n\n\n\/* =========================\n   FOOTER\n   ========================= *\/\n\n.kavkom-footer {\n  min-height: 110px;\n\n  display: flex;\n  align-items: center;\n\n  gap: 24px;\n\n  padding-top: 26px;\n  padding-bottom: 26px;\n\n  color: #8A9BC4;\n\n  font-size: 13px;\n}\n\n.kavkom-footer img {\n  width: auto;\n  height: 28px;\n}\n\n.footer-badges {\n  display: flex;\n  flex-wrap: wrap;\n\n  gap: 8px;\n\n  margin-left: auto;\n}\n\n.footer-badges span {\n  padding: 6px 10px;\n\n  color: #B6C3E0;\n\n  border: 1px solid #24438A;\n  border-radius: 999px;\n\n  font-size: 11px;\n}\n\n\n\/* =========================\n   CTA FLOTTANT\n   ========================= *\/\n\n.sticky-demo-cta {\n  position: fixed;\n\n  right: 24px;\n  bottom: 24px;\n\n  z-index: 900;\n\n  transition:\n    opacity 0.25s ease,\n    transform 0.25s ease;\n}\n\n.sticky-demo-cta .btn {\n  box-shadow:\n    0 14px 34px -10px rgba(37, 99, 235, 0.6),\n    0 4px 10px rgba(4, 21, 69, 0.18);\n}\n\n\n\/* =========================\n   ANIMATIONS\n   ========================= *\/\n\n@keyframes kavkomPulse {\n\n  0%,\n  100% {\n    opacity: 1;\n  }\n\n  50% {\n    opacity: 0.35;\n  }\n}\n\n\n\/* =========================\n   TABLET\n   ========================= *\/\n\n@media (max-width: 1024px) {\n\n  .kavkom-nav {\n    display: none;\n  }\n\n  .btn-header {\n    margin-left: auto;\n  }\n\n  .hero {\n    grid-template-columns: 1fr;\n\n    gap: 42px;\n\n    padding-top: 56px;\n  }\n\n  .two-columns {\n    grid-template-columns: 1fr;\n\n    gap: 40px;\n  }\n\n  .sip-grid,\n  .usecases-grid,\n  .infrastructure-grid {\n    grid-template-columns:\n      repeat(2, 1fr);\n  }\n\n  .quickstart-grid,\n  .pricing-grid {\n    grid-template-columns: 1fr;\n  }\n\n  .faq-section {\n    grid-template-columns: 1fr;\n\n    gap: 32px;\n  }\n\n  .contact-links {\n    grid-template-columns:\n      repeat(2, 1fr);\n  }\n\n  .contact-link:nth-child(3) {\n    border-left: 0;\n    border-top: 1px solid #17326F;\n  }\n\n  .contact-link:nth-child(4) {\n    border-top: 1px solid #17326F;\n  }\n\n}\n\n\n\/* =========================\n   MOBILE\n   ========================= *\/\n\n@media (max-width: 767px) {\n\n  .kavkom-container {\n    padding-left: 18px;\n    padding-right: 18px;\n  }\n\n  .kavkom-header-inner {\n    padding-top: 12px;\n    padding-bottom: 12px;\n  }\n\n  .kavkom-logo img {\n    height: 26px;\n  }\n\n  .btn-header {\n    padding: 9px 12px;\n\n    font-size: 12px;\n  }\n\n  .hero {\n    padding-top: 42px;\n    padding-bottom: 44px;\n\n    gap: 34px;\n  }\n\n  .hero h1 {\n    font-size: clamp(38px, 12vw, 54px);\n  }\n\n  .hero-description {\n    font-size: 16px;\n  }\n\n  .hero-actions {\n    flex-direction: column;\n  }\n\n  .hero-actions .btn {\n    width: 100%;\n  }\n\n  .hero-badges {\n    align-items: flex-start;\n    flex-direction: column;\n  }\n\n  .code-header {\n    flex-wrap: wrap;\n  }\n\n  .code-window pre {\n    padding: 16px;\n\n    font-size: 11px;\n  }\n\n  .live-badge {\n    display: none;\n  }\n\n  .infra-list {\n    gap: 8px 14px;\n  }\n\n  .stats {\n    grid-template-columns: 1fr;\n  }\n\n  .stat + .stat {\n    border-left: 0;\n    border-top: 1px solid var(--border);\n  }\n\n  .client-logos {\n    justify-content: flex-start;\n\n    overflow-x: auto;\n\n    gap: 38px;\n\n    padding: 10px 10px 18px;\n  }\n\n  .features-section > .kavkom-container,\n  .webhooks-section > .kavkom-container,\n  .sip-section > .kavkom-container,\n  .quickstart-section > .kavkom-container,\n  .usecases-section > .kavkom-container,\n  .comparison-section > .kavkom-container,\n  .infrastructure-section > .kavkom-container,\n  .pricing-section > .kavkom-container {\n    padding-top: 64px;\n    padding-bottom: 64px;\n  }\n\n  .features-grid,\n  .sip-grid,\n  .usecases-grid,\n  .infrastructure-grid {\n    grid-template-columns: 1fr;\n  }\n\n  .call-demo-statuses {\n    grid-template-columns:\n      repeat(2, 1fr);\n  }\n\n  .faq-section {\n    padding-top: 64px;\n    padding-bottom: 64px;\n  }\n\n  .contact-section {\n    padding-top: 64px;\n  }\n\n  .contact-form {\n    padding: 20px;\n  }\n\n  .contact-links {\n    grid-template-columns: 1fr;\n  }\n\n  .contact-link + .contact-link {\n    border-left: 0;\n    border-top: 1px solid #17326F;\n  }\n\n  .kavkom-footer {\n    align-items: flex-start;\n    flex-direction: column;\n  }\n\n  .footer-badges {\n    margin-left: 0;\n  }\n\n  .sticky-demo-cta {\n    right: 14px;\n    bottom: 14px;\n    left: 14px;\n  }\n\n  .sticky-demo-cta .btn {\n    width: 100%;\n  }\n\n}\n\n\n\/* =========================\n   PETITS MOBILES\n   ========================= *\/\n\n@media (max-width: 480px) {\n\n  .hero h1 {\n    font-size: 40px;\n  }\n\n  .kavkom-api-page h2 {\n    font-size: 34px;\n  }\n\n  .hero {\n    padding-top: 34px;\n  }\n\n  .feature-card,\n  .sip-card,\n  .quickstart-step,\n  .pricing-card,\n  .infrastructure-grid article,\n  .usecases-grid article {\n    padding: 20px;\n  }\n\n  .code-filename {\n    max-width: 120px;\n\n    overflow: hidden;\n\n    white-space: nowrap;\n\n    text-overflow: ellipsis;\n  }\n\n}\n\n\n<\/style>\n\n<script data-wp-block-html=\"js\">\n\/\/ GENERATED from dc-runtime\/src\/*.ts \u2014 do not edit. Rebuild with `cd dc-runtime && bun run build`.\n\"use strict\";\n(() => {\n  var __defProp = Object.defineProperty;\n  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\n  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== \"symbol\" ? key + \"\" : key, value);\n\n  \/\/ src\/react.ts\n  function getReact() {\n    const R = window.React;\n    if (!R) throw new Error(\"dc-runtime: window.React is not available yet\");\n    return R;\n  }\n  function getReactDOM() {\n    const RD = window.ReactDOM;\n    if (!RD) throw new Error(\"dc-runtime: window.ReactDOM is not available yet\");\n    return RD;\n  }\n  var h = ((...args) => getReact().createElement(\n    ...args\n  ));\n\n  \/\/ src\/parse.ts\n  function parseDcDocument(doc) {\n    const dc = doc.querySelector(\"x-dc\");\n    if (!dc) return null;\n    const scriptEl = doc.querySelector(\"script[data-dc-script]\");\n    const { props, preview } = parseDataProps(\n      scriptEl?.getAttribute(\"data-props\") ?? null\n    );\n    return {\n      template: dc.innerHTML,\n      js: scriptEl ? scriptEl.textContent || \"\" : \"\",\n      props,\n      preview\n    };\n  }\n  function parseDcText(src) {\n    const openMatch = \/<x-dc(?:\\s[^>]*)?>\/.exec(src);\n    if (!openMatch) return null;\n    const close = src.lastIndexOf(\"\");\n    if (close === -1 || close < openMatch.index) return null;\n    const template = src.slice(openMatch.index + openMatch[0].length, close);\n    const doc = new DOMParser().parseFromString(src, \"text\/html\");\n    const scriptEl = doc.querySelector(\"script[data-dc-script]\");\n    const { props, preview } = parseDataProps(\n      scriptEl?.getAttribute(\"data-props\") ?? null\n    );\n    return {\n      template,\n      js: scriptEl ? scriptEl.textContent || \"\" : \"\",\n      props,\n      preview\n    };\n  }\n  function parseDataProps(raw) {\n    if (!raw) return { props: null, preview: null };\n    let parsed;\n    try {\n      parsed = JSON.parse(raw);\n    } catch {\n      return { props: null, preview: null };\n    }\n    if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n      return { props: null, preview: null };\n    }\n    const obj = parsed;\n    const preview = obj.$preview &#038;&#038; typeof obj.$preview === \"object\" ? obj.$preview : null;\n    const rest = {};\n    for (const k of Object.keys(obj)) {\n      if (k[0] !== \"$\") rest[k] = obj[k];\n    }\n    return { props: Object.keys(rest).length ? rest : null, preview };\n  }\n  function dcNameFromPath(pathname) {\n    let p = pathname || \"\";\n    try {\n      p = decodeURIComponent(p);\n    } catch {\n    }\n    const base = p.split(\"\/\").pop() || \"Root\";\n    return base.replace(\/\\.dc\\.html$\/, \"\").replace(\/\\.html?$\/, \"\") || \"Root\";\n  }\n\n  \/\/ src\/boot.ts\n  var BASE_CSS = `\n    .sc-placeholder{background:color-mix(in srgb,currentColor 8%,transparent);\n      border:1px solid color-mix(in srgb,currentColor 50%,transparent);\n      border-radius:2px;box-sizing:border-box;overflow:hidden}\n    @keyframes sc-shine{0%{background-position:100% 50%}100%{background-position:0% 50%}}\n    html.sc-dc-streaming .sc-placeholder,\n    html.sc-dc-streaming .sc-interp.sc-missing{position:relative;\n      background:color-mix(in srgb,currentColor 5%,transparent);\n      border-color:transparent}\n    html.sc-dc-streaming .sc-placeholder::before,\n    html.sc-dc-streaming .sc-interp.sc-missing::before{content:'';\n      position:absolute;inset:0;pointer-events:none;\n      background:linear-gradient(90deg,rgba(217,119,87,0) 25%,rgba(247,225,211,.95) 37%,rgba(217,119,87,0) 63%);\n      background-size:400% 100%;animation:sc-shine 1.4s ease infinite}\n    html.sc-dc-streaming .sc-placeholder:nth-child(n+9 of .sc-placeholder)::before,\n    html.sc-dc-streaming .sc-interp.sc-missing:nth-child(n+9 of .sc-interp.sc-missing)::before{animation:none;\n      background:color-mix(in srgb,currentColor 8%,transparent)}\n    .sc-placeholder-error{padding:4px 8px;font:11px\/1.4 ui-monospace,monospace;\n      color:color-mix(in srgb,currentColor 70%,transparent);word-break:break-word}\n    .sc-interp.sc-missing{display:inline-block;width:2em;height:1em;overflow:hidden;\n      vertical-align:text-bottom;background:rgba(255,255,255,.3);border:1px solid rgba(0,0,0,.5);\n      border-radius:2px;box-sizing:border-box;color:transparent;\n      user-select:none}\n    .sc-interp.sc-unresolved{font-family:ui-monospace,monospace;font-size:.85em;\n      color:color-mix(in srgb,currentColor 50%,transparent);\n      background:color-mix(in srgb,currentColor 10%,transparent);border-radius:3px;\n      padding:0 3px}\n    .sc-host.sc-has-error{position:relative}\n    .sc-logic-error{position:absolute;top:8px;left:8px;z-index:2147483647;max-width:60ch;\n      padding:6px 10px;background:#b00020;color:#fff;font:12px\/1.4 ui-monospace,monospace;\n      border-radius:4px;white-space:pre-wrap;pointer-events:none}\n    \/* Mirrors PRINT_BASELINE_CSS in apps\/web deck-stage-export.ts \\u2014 keep both\n       in sync until dc-runtime regains a build step. *\/\n    @media print {\n      @page { margin: 0.5cm; }\n      figure, table { break-inside: avoid; }\n      #dc-root, #dc-root > .sc-host { height: auto; }\n      *, *::before, *::after {\n        print-color-adjust: exact; -webkit-print-color-adjust: exact;\n        backdrop-filter: none !important; -webkit-backdrop-filter: none !important;\n        animation-delay: -99s !important; animation-duration: .001s !important;\n        animation-iteration-count: 1 !important; animation-fill-mode: both !important;\n        animation-play-state: running !important; transition-duration: 0s !important;\n      }\n    }\n  `;\n  var FULL_PAGE_CSS = \"html,body{height:100%;margin:0}#dc-root,#dc-root>.sc-host{height:100%}\";\n  function rootNameForDocument(doc, loc) {\n    let bootPath = loc.pathname || \"\";\n    if (!\/\\.dc\\.html?$\/i.test(safeDecode(bootPath))) {\n      try {\n        bootPath = new URL(doc.baseURI || \"\/\").pathname;\n      } catch {\n      }\n    }\n    return dcNameFromPath(bootPath);\n  }\n  function safeDecode(s) {\n    try {\n      return decodeURIComponent(s);\n    } catch {\n      return s;\n    }\n  }\n  function boot(runtime, doc = document) {\n    const parsed = parseDcDocument(doc);\n    if (!parsed) return null;\n    const React = getReact();\n    const rootName = rootNameForDocument(doc, location);\n    runtime.markFetched(rootName);\n    runtime.setRootName(rootName);\n    runtime.adoptParsed(rootName, parsed);\n    if (!window.__resources) {\n      fetch(location.href).then((res) => res.ok ? res.text() : \"\").then((t) => {\n        const raw = t ? parseDcText(t) : null;\n        if (raw?.template) runtime.updateHtml(rootName, raw.template);\n      }).catch(() => {\n      });\n    }\n    const dc = doc.querySelector(\"x-dc\");\n    const hostEl = doc.createElement(\"div\");\n    hostEl.id = \"dc-root\";\n    dc.replaceWith(hostEl);\n    if (!parsed.preview) {\n      const s = doc.createElement(\"style\");\n      s.textContent = FULL_PAGE_CSS;\n      doc.head.appendChild(s);\n    }\n    const Root = runtime.getDC(rootName);\n    const entry = runtime.registry.get(rootName);\n    function StandaloneRoot() {\n      const [, setTick] = React.useState(0);\n      React.useEffect(() => {\n        const sub = () => setTick((n) => n + 1);\n        entry.subs.add(sub);\n        return () => {\n          entry.subs.delete(sub);\n        };\n      }, []);\n      const defaults = React.useMemo(() => {\n        const d = {};\n        for (const k in entry.propsMeta || {}) {\n          const v = entry.propsMeta?.[k]?.default;\n          if (v !== void 0) d[k] = v;\n        }\n        return d;\n      }, [entry.propsMeta]);\n      return h(Root, { ...defaults, ...entry.propOverrides || {} });\n    }\n    const ReactDOM = getReactDOM();\n    if (ReactDOM.createRoot)\n      ReactDOM.createRoot(hostEl).render(h(StandaloneRoot));\n    else ReactDOM.render(h(StandaloneRoot), hostEl);\n    return rootName;\n  }\n\n  \/\/ src\/expr.ts\n  var IDENT_RE = \/^[A-Za-z_$][A-Za-z0-9_$]*\/;\n  var NUMBER_RE = \/^-?\\d+(\\.\\d+)?$\/;\n  function resolve(vals, src) {\n    const expr = String(src).trim();\n    if (!expr) return void 0;\n    if (expr[0] === \"(\" && expr[expr.length - 1] === \")\" && parensWrapWhole(expr)) {\n      return resolve(vals, expr.slice(1, -1));\n    }\n    const eq = findTopLevelEquality(expr);\n    if (eq) {\n      const lv = resolve(vals, expr.slice(0, eq.index));\n      const rv = resolve(vals, expr.slice(eq.index + eq.op.length));\n      switch (eq.op) {\n        case \"===\":\n          return lv === rv;\n        case \"!==\":\n          return lv !== rv;\n        case \"==\":\n          return lv == rv;\n        default:\n          return lv != rv;\n      }\n    }\n    if (expr[0] === \"!\") return !resolve(vals, expr.slice(1));\n    if (expr === \"true\") return true;\n    if (expr === \"false\") return false;\n    if (expr === \"null\") return null;\n    if (expr === \"undefined\") return void 0;\n    if (NUMBER_RE.test(expr)) return Number(expr);\n    if (expr.length >= 2 && (expr[0] === '\"' || expr[0] === \"'\") && expr[expr.length - 1] === expr[0]) {\n      return expr.slice(1, -1);\n    }\n    return resolvePath(vals, expr);\n  }\n  function parensWrapWhole(expr) {\n    let depth = 0;\n    for (let i = 0; i < expr.length - 1; i++) {\n      if (expr[i] === \"(\") depth++;\n      else if (expr[i] === \")\") {\n        depth--;\n        if (depth === 0) return false;\n      }\n    }\n    return true;\n  }\n  function findTopLevelEquality(expr) {\n    let depth = 0;\n    for (let i = 0; i < expr.length; i++) {\n      const c = expr[i];\n      if (c === \"[&quot; || c === &quot;(&quot;) depth++;\n      else if (c === &quot;]\" || c === \")\") depth--;\n      else if (depth === 0 &#038;&#038; (c === \"=\" || c === \"!\") &#038;&#038; expr[i + 1] === \"=\") {\n        if (i > 0 && (expr[i - 1] === \"=\" || expr[i - 1] === \"!\")) continue;\n        if (!expr.slice(0, i).trim()) continue;\n        const op = expr[i + 2] === \"=\" ? c + \"==\" : c + \"=\";\n        return { index: i, op };\n      }\n    }\n    return null;\n  }\n  function resolvePath(vals, expr) {\n    const head = expr.match(IDENT_RE);\n    if (!head) return void 0;\n    let cur = vals == null ? void 0 : vals[head[0]];\n    let i = head[0].length;\n    while (i < expr.length) {\n      if (expr[i] === \".\") {\n        const m = expr.slice(i + 1).match(IDENT_RE) || expr.slice(i + 1).match(\/^\\d+\/);\n        if (!m) return void 0;\n        cur = cur == null ? void 0 : cur[m[0]];\n        i += 1 + m[0].length;\n      } else if (expr[i] === \"[\") {\n        let depth = 1;\n        let j = i + 1;\n        while (j < expr.length &#038;&#038; depth > 0) {\n          if (expr[j] === \"[\") depth++;\n          else if (expr[j] === \"]\") {\n            depth--;\n            if (depth === 0) break;\n          }\n          j++;\n        }\n        if (depth !== 0) return void 0;\n        const key = resolve(vals, expr.slice(i + 1, j));\n        cur = cur == null ? void 0 : cur[key];\n        i = j + 1;\n      } else {\n        return void 0;\n      }\n    }\n    return cur;\n  }\n\n  \/\/ src\/encode.ts\n  var CAMEL_ATTR = \"sc-camel-\";\n  var INLINE_TEXT_TAGS = new Set(\n    \"a abbr b bdi bdo br cite code del dfn em i ins kbd mark q s samp small span strike strong sub sup u var wbr\".split(\n      \" \"\n    )\n  );\n  var RAW_WRAP = {\n    select: \"sc-raw-select\",\n    table: \"sc-raw-table\",\n    tbody: \"sc-raw-tbody\",\n    thead: \"sc-raw-thead\",\n    tfoot: \"sc-raw-tfoot\",\n    tr: \"sc-raw-tr\",\n    td: \"sc-raw-td\",\n    th: \"sc-raw-th\",\n    caption: \"sc-raw-caption\"\n  };\n  var RAW_UNWRAP = Object.fromEntries(\n    Object.entries(RAW_WRAP).map(([k, v]) => [v, k])\n  );\n  var EVENT_MAP = {\n    onclick: \"onClick\",\n    onchange: \"onChange\",\n    oninput: \"onInput\",\n    onsubmit: \"onSubmit\",\n    onkeydown: \"onKeyDown\",\n    onkeyup: \"onKeyUp\",\n    onkeypress: \"onKeyPress\",\n    onmousedown: \"onMouseDown\",\n    onmouseup: \"onMouseUp\",\n    onmouseenter: \"onMouseEnter\",\n    onmouseleave: \"onMouseLeave\",\n    onfocus: \"onFocus\",\n    onblur: \"onBlur\",\n    ondoubleclick: \"onDoubleClick\",\n    oncontextmenu: \"onContextMenu\",\n    onmousemove: \"onMouseMove\",\n    onmouseover: \"onMouseOver\",\n    onmouseout: \"onMouseOut\",\n    onpointerdown: \"onPointerDown\",\n    onpointerup: \"onPointerUp\",\n    onpointermove: \"onPointerMove\",\n    onpointerenter: \"onPointerEnter\",\n    onpointerleave: \"onPointerLeave\",\n    onpointercancel: \"onPointerCancel\",\n    onpointerover: \"onPointerOver\",\n    onpointerout: \"onPointerOut\",\n    ongotpointercapture: \"onGotPointerCapture\",\n    onlostpointercapture: \"onLostPointerCapture\",\n    ontouchstart: \"onTouchStart\",\n    ontouchend: \"onTouchEnd\",\n    ontouchmove: \"onTouchMove\",\n    ontouchcancel: \"onTouchCancel\",\n    ondragstart: \"onDragStart\",\n    ondragend: \"onDragEnd\",\n    ondragenter: \"onDragEnter\",\n    ondragleave: \"onDragLeave\",\n    ondragover: \"onDragOver\",\n    onanimationstart: \"onAnimationStart\",\n    onanimationend: \"onAnimationEnd\",\n    onanimationiteration: \"onAnimationIteration\",\n    ontransitionend: \"onTransitionEnd\"\n  };\n  var ATTRS = `(?:[^>\"']|\"[^\"]*\"|'[^']*')*`;\n  var IMPORT_SELF_CLOSE_RE = new RegExp(\n    \"<(x-import|dc-import)(\" + ATTRS + \")\/>\",\n    \"gi\"\n  );\n  var CAMEL_ATTR_RE = \/(\\s)([a-z]+[A-Z][A-Za-z0-9]*)(\\s*=)\/g;\n  function encodeCamelAttrs(html) {\n    return html.replace(\n      CAMEL_ATTR_RE,\n      (_, sp, name, eq) => sp + CAMEL_ATTR + name.replace(\/[A-Z]\/g, (c) => \"-\" + c.toLowerCase()) + eq\n    );\n  }\n  function encodeCase(html) {\n    html = html.replace(\n      IMPORT_SELF_CLOSE_RE,\n      (_, t, a) => \"<\" + t + a + \"><\/\" + t + \">\"\n    );\n    html = html.replace(\/<helmet(\\s|>)\/gi, \"<sc-helmet$1\");\n    html = html.replace(\/<\\\/helmet\\s*>\/gi, \"\");\n    html = encodeCamelAttrs(html);\n    for (const [real, alias] of Object.entries(RAW_WRAP)) {\n      html = html.replace(\n        new RegExp(\"(<\/?)\" + real + \"(?=[\\\\s>])\", \"gi\"),\n        \"$1\" + alias\n      );\n    }\n    return html;\n  }\n  function kebabToCamel(s) {\n    return s.replace(\/-([a-z])\/g, (_, c) => c.toUpperCase());\n  }\n  function cssToObj(css) {\n    const o = {};\n    for (const decl of css.split(\";\")) {\n      const i = decl.indexOf(\":\");\n      if (i < 0) continue;\n      const prop = decl.slice(0, i).trim();\n      o[prop.startsWith(&quot;--&quot;) ? prop : kebabToCamel(prop)] = decl.slice(i + 1).trim();\n    }\n    return o;\n  }\n  function compileAttr(raw) {\n    const whole = raw.match(\/^\\s*\\{\\{([\\s\\S]+?)\\}\\}\\s*$\/);\n    if (whole) {\n      const path = whole[1];\n      return (vals) => resolve(vals, path);\n    }\n    if (raw.includes(\"{{\")) {\n      const parts = raw.split(\/\\{\\{([\\s\\S]+?)\\}\\}\/g);\n      return (vals) => parts.map((s, i) => i & 1 ? resolve(vals, s) ?? \"\" : s).join(\"\");\n    }\n    return () => raw;\n  }\n\n  \/\/ src\/compile.ts\n  function collectProps(node, kind, host) {\n    const propGetters = [];\n    const pseudoClasses = [];\n    let hintSize = null;\n    for (const { name, value } of [...node.attributes]) {\n      if (name === \"sc-name\" || name === \"data-dc-tpl\") continue;\n      let key = name;\n      if (key.startsWith(CAMEL_ATTR))\n        key = kebabToCamel(key.slice(CAMEL_ATTR.length));\n      if (key === \"hint-size\") {\n        hintSize = value;\n        continue;\n      }\n      if (key.startsWith(\"style-\")) {\n        pseudoClasses.push(host.pseudoClass(key.slice(6), value));\n        continue;\n      }\n      if (kind !== \"dom\") {\n        if (key.includes(\"-\") && !(kind === \"x-import\" && (key.startsWith(\"aria-\") || key.startsWith(\"data-\"))))\n          key = kebabToCamel(key);\n      } else {\n        if (key === \"class\") key = \"className\";\n        else if (key === \"for\") key = \"htmlFor\";\n        else if (key.startsWith(\"on\"))\n          key = EVENT_MAP[key] || \"on\" + key[2].toUpperCase() + key.slice(3);\n      }\n      propGetters.push([key, compileAttr(value)]);\n    }\n    return { propGetters, pseudoClasses, hintSize };\n  }\n  var HOST_STYLE_PROPS = \/* @__PURE__ *\/ new Set([\n    \"position\",\n    \"left\",\n    \"right\",\n    \"top\",\n    \"bottom\",\n    \"inset\",\n    \"width\",\n    \"height\",\n    \"z-index\",\n    \"transform\"\n  ]);\n  function hostPositionStyle(style) {\n    const all = typeof style === \"string\" ? cssToObj(style) : style != null && typeof style === \"object\" ? style : null;\n    if (!all) return void 0;\n    const out = {};\n    for (const [k, v] of Object.entries(all)) {\n      const kebab = k.replace(\/[A-Z]\/g, (c) => \"-\" + c.toLowerCase());\n      if (HOST_STYLE_PROPS.has(kebab)) out[k] = v;\n    }\n    return Object.keys(out).length ? out : void 0;\n  }\n  function compileTemplate(html, host) {\n    const tpl = document.createElement(\"template\");\n    \/\/! nosemgrep: direct-inner-html-assignment\n    tpl.innerHTML = encodeCase(html);\n    let tplN = 0;\n    (function stamp(node) {\n      if (node.nodeType === Node.ELEMENT_NODE) {\n        node.setAttribute(\"data-dc-tpl\", String(tplN++));\n      }\n      for (const c of node.childNodes) stamp(c);\n    })(tpl.content);\n    const builders = walkChildren(tpl.content, host);\n    const render = ((vals, ctx) => builders.map((b, i) => b(vals || {}, ctx, i)));\n    render.__annotated = tpl.innerHTML;\n    return render;\n  }\n  function walkChildren(node, host) {\n    return [...node.childNodes].map((c) => walk(c, host)).filter((b) => b != null);\n  }\n  var SLIDE_ID_VALUE_RE = \/^[0-9a-f]{8}$\/;\n  var DECK_CONTROL_FLOW_RE = \/^(sc-if|sc-for|sc-else|dc-import|x-import)$\/;\n  var DECK_AUX_RE = \/^(template|script|style|sc-helmet|helmet)$\/;\n  function isDeckMountTag(el) {\n    if (el.localName === \"deck-stage\") return true;\n    return el.localName === \"x-import\" && (el.getAttribute(\"component-from-global-scope\") || \"\") === \"deck-stage\";\n  }\n  function walkDeckChildren(el, host) {\n    const pairs = [...el.childNodes].map((c) => ({ c, b: walk(c, host) })).filter((p) => p.b !== null);\n    const kids = pairs.map((p) => p.b);\n    const seen = \/* @__PURE__ *\/ new Set();\n    const wsSeen = \/* @__PURE__ *\/ new Map();\n    const keys = [];\n    const nextSlideId = new Array(pairs.length);\n    {\n      let upcoming = null;\n      for (let j = pairs.length - 1; j >= 0; j--) {\n        const n = pairs[j].c;\n        if (n.nodeType === Node.ELEMENT_NODE) {\n          const t = n.localName;\n          upcoming = !DECK_AUX_RE.test(t) && !DECK_CONTROL_FLOW_RE.test(t) ? n.getAttribute(\"data-om-slide-id\") : null;\n        }\n        nextSlideId[j] = upcoming;\n      }\n    }\n    for (let j = 0; j < pairs.length; j++) {\n      const { c } = pairs[j];\n      if (c.nodeType === Node.TEXT_NODE) {\n        if ((c.nodeValue ?? \"\").trim() === \"\") {\n          const base = nextSlideId[j] ? \"omid-ws:\" + nextSlideId[j] : \"omid-ws:aux\";\n          const n = wsSeen.get(base) ?? 0;\n          wsSeen.set(base, n + 1);\n          keys.push(n === 0 ? base : base + \":\" + n);\n          continue;\n        }\n        return { kids, keys: null };\n      }\n      if (c.nodeType !== Node.ELEMENT_NODE) {\n        keys.push(j);\n        continue;\n      }\n      const child = c;\n      const tag = child.localName;\n      if (DECK_AUX_RE.test(tag)) {\n        keys.push(j);\n        continue;\n      }\n      if (DECK_CONTROL_FLOW_RE.test(tag)) return { kids, keys: null };\n      const v = child.getAttribute(\"data-om-slide-id\");\n      if (!v || !SLIDE_ID_VALUE_RE.test(v) || seen.has(v)) {\n        return { kids, keys: null };\n      }\n      seen.add(v);\n      keys.push(\"omid:\" + v);\n    }\n    return { kids, keys };\n  }\n  function renderDeckKids(kids, kidKeys, vals, ctx) {\n    return kids.map((b, j) => {\n      const k = kidKeys ? kidKeys[j] : j;\n      const out = b(vals, ctx, k);\n      return kidKeys != null && typeof out === \"string\" ? h(getReact().Fragment, { key: k }, out) : out;\n    });\n  }\n  function walk(node, host) {\n    if (node.nodeType === Node.TEXT_NODE) return walkText(node);\n    if (node.nodeType !== Node.ELEMENT_NODE) return null;\n    const el = node;\n    const tag = el.tagName.toLowerCase();\n    if (tag === \"sc-for\") return walkFor(el, host);\n    if (tag === \"sc-if\") return walkIf(el, host);\n    if (tag === \"x-import\") return walkXImport(el, host);\n    if (tag === \"sc-helmet\") return host.helmet(el);\n    if (tag === \"dc-import\") return walkComponent(el, host);\n    return walkElement(el, host);\n  }\n  var warnedHoles = \/* @__PURE__ *\/ new Set();\n  function warnUnresolved(ctx, what) {\n    const key = (ctx?.__name || \"?\") + \"\\0\" + what;\n    if (warnedHoles.has(key)) return;\n    warnedHoles.add(key);\n    console.warn(\"[dc-runtime] \" + (ctx?.__name || \"template\") + \": \" + what);\n  }\n  function walkText(node) {\n    const txt = node.nodeValue ?? \"\";\n    if (!txt.includes(\"{{\")) {\n      if (!txt.trim() && !txt.includes(\" \")) return null;\n      return () => txt;\n    }\n    const parts = txt.split(\/\\{\\{([\\s\\S]+?)\\}\\}\/g);\n    return (vals, ctx, key) => h(\n      getReact().Fragment,\n      { key },\n      ...parts.map((p, i) => {\n        if (!(i & 1)) return p;\n        const v = resolve(vals, p);\n        if (v === void 0) {\n          if (!ctx?.__streamingNow) {\n            if (document.body?.hasAttribute(\"data-dc-editor-on\")) {\n              return h(\n                \"span\",\n                { key: i, className: \"sc-interp sc-unresolved\" },\n                \"{{ \" + p.trim() + \" }}\"\n              );\n            }\n            warnUnresolved(\n              ctx,\n              \"{{ \" + p.trim() + \" }} never resolved \\u2014 rendered as empty\"\n            );\n            return null;\n          }\n          return h(\n            \"span\",\n            { key: i, className: \"sc-interp sc-missing\" },\n            p.trim()\n          );\n        }\n        if (getReact().isValidElement(v) || Array.isArray(v)) {\n          return h(getReact().Fragment, { key: i }, v);\n        }\n        if (v === null || typeof v === \"boolean\") return null;\n        return h(\"span\", { key: i, className: \"sc-interp\" }, String(v));\n      })\n    );\n  }\n  function walkFor(el, host) {\n    const listGet = compileAttr(el.getAttribute(\"list\") || \"\");\n    const asName = el.getAttribute(\"as\") || \"item\";\n    const hintN = parseInt(el.getAttribute(\"hint-placeholder-count\") || \"0\", 10);\n    const kids = walkChildren(el, host);\n    const listSrc = el.getAttribute(\"list\") || \"\";\n    return (vals, ctx, key) => {\n      let list = listGet(vals);\n      if (!Array.isArray(list)) {\n        if (!ctx?.__streamingNow) {\n          if (list !== void 0 && list !== null) {\n            warnUnresolved(\n              ctx,\n              'sc-for list=\"' + listSrc + '\" is not an array (' + typeof list + \")\"\n            );\n          }\n          list = [];\n        } else {\n          list = hintN > 0 ? Array(hintN).fill(void 0) : [];\n        }\n      }\n      return h(\n        getReact().Fragment,\n        { key },\n        list.map((item, i) => {\n          const sub = { ...vals, [asName]: item, $index: i };\n          return h(\n            getReact().Fragment,\n            { key: i },\n            kids.map((b, j) => b(sub, ctx, j))\n          );\n        })\n      );\n    };\n  }\n  function walkIf(el, host) {\n    const valGet = compileAttr(el.getAttribute(\"value\") || \"\");\n    const hintRaw = el.getAttribute(\"hint-placeholder-val\");\n    const hintGet = hintRaw != null ? compileAttr(hintRaw) : null;\n    const kids = walkChildren(el, host);\n    return (vals, ctx, key) => {\n      let v = valGet(vals);\n      if (v === void 0 && hintGet && ctx?.__streamingNow) v = hintGet(vals);\n      return v ? h(\n        getReact().Fragment,\n        { key },\n        kids.map((b, j) => b(vals, ctx, j))\n      ) : null;\n    };\n  }\n  function walkComponent(el, host) {\n    const name = el.getAttribute(\"name\") || el.getAttribute(\"component\") || \"\";\n    el.removeAttribute(\"name\");\n    el.removeAttribute(\"component\");\n    const tplId = el.getAttribute(\"data-dc-tpl\");\n    const styleRaw = el.getAttribute(\"style\");\n    el.removeAttribute(\"style\");\n    const styleGet = styleRaw != null ? compileAttr(styleRaw) : null;\n    const { propGetters, hintSize } = collectProps(el, \"dc-import\", host);\n    const kids = walkChildren(el, host);\n    return (vals, ctx, key) => {\n      const props = {\n        key,\n        __hintSize: hintSize,\n        __tplId: tplId,\n        __hostStyle: styleGet ? hostPositionStyle(styleGet(vals)) : void 0\n      };\n      for (const [k, g] of propGetters) {\n        const v = g(vals);\n        if (k === \"dcProps\") {\n          if (v && typeof v === \"object\") Object.assign(props, v);\n          continue;\n        }\n        props[k] = v;\n      }\n      if (kids.length) props.children = kids.map((b, j) => b(vals, ctx, j));\n      return h(host.component(name), props);\n    };\n  }\n  function walkXImport(el, host) {\n    const globalNameGet = compileAttr(\n      el.getAttribute(\"component-from-global-scope\") || \"\"\n    );\n    const exportNameGet = compileAttr(\n      el.getAttribute(\"component\") || el.getAttribute(\"name\") || \"\"\n    );\n    const fromRaw = el.getAttribute(\"from\") || (el.getAttribute(\"component-from-global-scope\") ? \"\" : el.getAttribute(\"src\") || el.getAttribute(\"import\") || \"\");\n    const urls = fromRaw.trim() ? fromRaw.trim().split(\/\\s+\/) : [];\n    const url = urls.length ? urls[urls.length - 1] : \"\";\n    const kindOf = (u) => \/\\.(jsx|tsx)(\\?|#|$)\/i.test(u) ? \"jsx\" : \"js\";\n    const tplId = el.getAttribute(\"data-dc-tpl\");\n    const styleRaw = el.getAttribute(\"style\");\n    el.removeAttribute(\"style\");\n    const styleGet = styleRaw != null ? compileAttr(styleRaw) : null;\n    const wrap = tplId != null || styleGet != null;\n    const { propGetters, hintSize } = collectProps(el, \"x-import\", host);\n    const hasContent = el.children.length > 0 || !!(el.textContent || \"\").trim();\n    const deckKeyed = hasContent && isDeckMountTag(el) ? walkDeckChildren(el, host) : null;\n    const kids = deckKeyed ? deckKeyed.kids : hasContent ? walkChildren(el, host) : [];\n    const kidKeys = deckKeyed?.keys ?? null;\n    const urlBindable = fromRaw.includes(\"{{\");\n    if (urls.length && !urlBindable) {\n      let prev;\n      for (const u of urls) prev = host.loadExternal(kindOf(u), u, prev);\n    }\n    const evalName = (g, vals) => {\n      const v = g(vals);\n      const s = v == null ? \"\" : String(v);\n      return s.includes(\"{{\") ? \"\" : s;\n    };\n    return (vals, ctx, key) => {\n      const globalName = evalName(globalNameGet, vals);\n      const name = globalName || evalName(exportNameGet, vals);\n      const C = !name || urlBindable ? null : globalName ? host.resolveExternalGlobal(url, globalName) : host.resolveExternal(url, name);\n      const hostStyle = styleGet ? hostPositionStyle(styleGet(vals)) : void 0;\n      const wrapper = wrap ? {\n        key,\n        className: \"sc-host-x\",\n        \"data-dc-tpl\": tplId,\n        style: hostStyle || { display: \"contents\" }\n      } : null;\n      if (!C) {\n        const error = urlBindable ? \"x-import `from` cannot contain {{ \\u2026 }} \\u2014 module URLs are resolved at parse time; use a literal URL\" : host.resolveExternalError(url, name);\n        const ph = host.placeholder({\n          key: wrapper ? void 0 : key,\n          name,\n          hintSize,\n          error\n        });\n        return wrapper ? h(\"div\", wrapper, ph) : ph;\n      }\n      const props = wrapper ? {} : { key };\n      let unresolvedHole = false;\n      for (const [k, g] of propGetters) {\n        if (k === \"component\" || k === \"componentFromGlobalScope\" || k === \"from\") {\n          continue;\n        }\n        const v = g(vals);\n        if (v === void 0) unresolvedHole = true;\n        if (k === \"dcProps\") {\n          if (v && typeof v === \"object\") Object.assign(props, v);\n          continue;\n        }\n        props[k] = v;\n      }\n      if (unresolvedHole && ctx?.__htmlStreamingNow) {\n        const ph = host.placeholder({\n          key: wrapper ? void 0 : key,\n          name,\n          hintSize,\n          error: null\n        });\n        return wrapper ? h(\"div\", wrapper, ph) : ph;\n      }\n      if (kids.length) {\n        props.children = renderDeckKids(kids, kidKeys, vals, ctx);\n      }\n      return wrapper ? h(\"div\", wrapper, h(C, props)) : h(C, props);\n    };\n  }\n  function contentKey(el) {\n    const clone = el.cloneNode(true);\n    for (const d of clone.querySelectorAll(\"*\")) {\n      while (d.attributes.length) d.removeAttribute(d.attributes[0].name);\n    }\n    const s = clone.innerHTML;\n    let h2 = 5381;\n    for (let i = 0; i <s.length; i++) h2 = (h2 <<5) + h2 + s.charCodeAt(i) | 0; return s.length + \".\" + (h2>>> 0).toString(36);\n  }\n  var NEVER_CONTENT_KEYED = new Set(\n    \"script style textarea option title select canvas iframe video audio\".split(\n      \" \"\n    )\n  );\n  var NOT_INLINE_SELECTOR = \":not(\" + [...INLINE_TEXT_TAGS].join(\",\") + \")\";\n  function walkElement(el, host) {\n    const realTag = RAW_UNWRAP[el.localName] || el.localName;\n    const tplId = el.getAttribute(\"data-dc-tpl\");\n    const inlineOnly = el.childNodes.length > 0 && !NEVER_CONTENT_KEYED.has(realTag) && el.querySelector(NOT_INLINE_SELECTOR) === null;\n    const keySuffix = inlineOnly ? \"|\" + contentKey(el) : \"\";\n    const { propGetters, pseudoClasses } = collectProps(el, \"dom\", host);\n    const deckKeyed = isDeckMountTag(el) ? walkDeckChildren(el, host) : null;\n    const kids = deckKeyed ? deckKeyed.kids : walkChildren(el, host);\n    const kidKeys = deckKeyed?.keys ?? null;\n    return (vals, ctx, key) => {\n      const props = {\n        key: key + keySuffix,\n        \"data-dc-tpl\": tplId\n      };\n      for (const [k, g] of propGetters) {\n        let v = g(vals);\n        if (k === \"style\" && typeof v === \"string\") v = cssToObj(v);\n        if ((k === \"value\" || k === \"checked\") && v === void 0) {\n          v = k === \"checked\" ? false : \"\";\n        }\n        props[k] = v;\n      }\n      if (pseudoClasses.length) {\n        props.className = [props.className, ...pseudoClasses].filter(Boolean).join(\" \");\n      }\n      return h(realTag, props, ...renderDeckKids(kids, kidKeys, vals, ctx));\n    };\n  }\n\n  \/\/ src\/logic.ts\n  var StreamableLogic = class {\n    constructor(props) {\n      __publicField(this, \"props\");\n      __publicField(this, \"state\", {});\n      \/** Back-pointer to the wrapper component, installed after construction. *\/\n      __publicField(this, \"__host\");\n      this.props = props || {};\n    }\n    setState(update, cb) {\n      this.__host && this.__host.__setLogicState(update, cb);\n    }\n    forceUpdate() {\n      this.__host && this.__host.forceUpdate();\n    }\n    componentDidMount() {\n    }\n    componentDidUpdate(_prevProps) {\n    }\n    componentWillUnmount() {\n    }\n    \/** The flat object the template renders against (merged over props). *\/\n    renderVals() {\n      return {};\n    }\n  };\n  function evalDcLogic(src) {\n    \/\/! nosemgrep: eval-and-function-constructor\n    const fn = new Function(\n      \"DCLogic\",\n      \"StreamableLogic\",\n      \"React\",\n      src + '\\n;return (typeof Component!==\"undefined\"&&Component)||undefined;'\n    );\n    return fn(StreamableLogic, StreamableLogic, getReact());\n  }\n\n  \/\/ src\/component.ts\n  function shallowEqual(a, b) {\n    if (!b) return false;\n    const ak = Object.keys(a).filter((k) => k !== \"children\");\n    const bk = Object.keys(b).filter((k) => k !== \"children\");\n    if (ak.length !== bk.length) return false;\n    for (const k of ak) if (a[k] !== b[k]) return false;\n    return true;\n  }\n  function Placeholder({\n    name,\n    hintSize,\n    streaming,\n    error\n  }) {\n    const [w, hgt] = (hintSize || \"100%,60px\").split(\",\");\n    return h(\n      \"div\",\n      {\n        className: \"sc-placeholder\" + (streaming ? \" sc-streaming\" : \"\"),\n        style: { width: w.trim(), height: hgt && hgt.trim() },\n        title: name\n      },\n      error ? h(\n        \"div\",\n        { className: \"sc-placeholder-error\" },\n        (name ? name + \": \" : \"\") + error\n      ) : null\n    );\n  }\n  function hintToMin(hint) {\n    if (!hint) return void 0;\n    const [w, hgt] = hint.split(\",\");\n    return { minWidth: w.trim(), minHeight: hgt && hgt.trim() };\n  }\n  function createComponentFactory(registry, ensureFetched) {\n    const React = getReact();\n    const AncestorContext = React.createContext([]);\n    class StreamableComponent extends React.Component {\n      constructor(props) {\n        super(props);\n        __publicField(this, \"__name\");\n        __publicField(this, \"__sub\");\n        __publicField(this, \"__needsDidMount\", false);\n        \/** Snapshot of the registry's streaming flags taken at render time \u2014\n         *  builders read it off the RenderCtx (this) to pick placeholder vs\n         *  render-nothing for unresolved values. *\/\n        __publicField(this, \"__streamingNow\", false);\n        __publicField(this, \"__htmlStreamingNow\", false);\n        \/** When a construct throws, remember the (class, registry.ver, props)\n         *  triple so render-time reconcile doesn't re-attempt it on every parent\n         *  re-render. A registry bump (new class, template, external module\n         *  resolving via bumpAll) changes `ver` and breaks the memo so an\n         *  env-dependent constructor can self-heal. *\/\n        __publicField(this, \"__failedLogic\", null);\n        __publicField(this, \"__failedUserProps\", null);\n        __publicField(this, \"__failedVer\", -1);\n        \/** Per-instance constructor error \u2014 kept here (not on the registry entry)\n         *  so one instance's successful construct can't hide a sibling's failure,\n         *  and a construct can never wipe an eval error `updateJs` recorded on\n         *  `r.logicError`. *\/\n        __publicField(this, \"__ctorError\", null);\n        __publicField(this, \"logic\");\n        this.__name = props.__name;\n        this.state = { __v: 0, __err: null };\n        this.__sub = () => {\n          if (this.state.__err) this.setState({ __err: null });\n          this.forceUpdate();\n        };\n        this.__makeLogic(registry.get(this.__name).Logic, null);\n        ensureFetched(this.__name);\n      }\n      \/** Error-boundary hook: a render crash anywhere in this DC's subtree\n       *  (its own template, an x-import'd component, a child DC without its\n       *  own deeper boundary) lands here instead of unmounting the page. *\/\n      static getDerivedStateFromError(e) {\n        return { __err: e instanceof Error && e.message ? e.message : String(e) };\n      }\n      componentDidCatch(e, info) {\n        console.error(\n          \"[dc-runtime] render error in <\" + this.__name + \">:\",\n          e,\n          info?.componentStack || \"\"\n        );\n      }\n      \/** Instantiate the logic class (or the no-op base) and adopt `prevState`\n       *  over its initial state \u2014 used both at mount and on hot-swap. *\/\n      __makeLogic(Logic, prevState) {\n        const L = Logic || StreamableLogic;\n        try {\n          this.logic = new L(this.__userProps());\n          this.__failedLogic = null;\n          this.__failedUserProps = null;\n          this.__ctorError = null;\n        } catch (e) {\n          console.error(e);\n          this.__failedLogic = Logic;\n          this.__failedUserProps = this.__userProps();\n          this.__failedVer = registry.get(this.__name).ver;\n          this.__ctorError = this.__name + \": \" + (e instanceof Error && e.message ? e.message : String(e));\n          this.logic = new StreamableLogic(\n            this.__userProps()\n          );\n        }\n        this.logic.__host = this;\n        if (prevState)\n          this.logic.state = { ...this.logic.state || {}, ...prevState };\n      }\n      \/** The props the author's logic + template see \u2014 internal __-prefixed\n       *  wiring stripped. *\/\n      __userProps() {\n        const { __name, __hintSize, __tplId, __hostStyle, ...rest } = this.props;\n        return rest;\n      }\n      __setLogicState(update, cb) {\n        const prev = this.logic.state;\n        const patch = typeof update === \"function\" ? update(prev) : update;\n        this.logic.state = { ...prev, ...patch };\n        this.setState((s) => ({ __v: s.__v + 1 }), cb);\n      }\n      \/** Swap the logic instance when the registry's Logic class changed\n       *  (streaming completion, hot reload). State carries over; didMount\n       *  re-fires after the swap commits so refs exist. *\/\n      __reconcileLogic() {\n        const r = registry.get(this.__name);\n        const Next = r.Logic;\n        const Cur = this.logic.constructor;\n        if (Next === Cur || !Next && Cur === StreamableLogic || Next === this.__failedLogic && r.ver === this.__failedVer && shallowEqual(this.__userProps(), this.__failedUserProps)) {\n          return;\n        }\n        if (!this.__needsDidMount) {\n          try {\n            this.logic.componentWillUnmount();\n          } catch (e) {\n            console.error(e);\n          }\n        }\n        this.__makeLogic(Next, this.logic.state);\n        this.__needsDidMount = true;\n      }\n      componentDidMount() {\n        registry.get(this.__name).subs.add(this.__sub);\n        try {\n          this.logic.componentDidMount();\n        } catch (e) {\n          console.error(e);\n        }\n      }\n      componentDidUpdate(prevProps) {\n        this.logic.props = this.__userProps();\n        if (this.__needsDidMount) {\n          if (this.state.__err || !registry.get(this.__name).tpl) return;\n          this.__needsDidMount = false;\n          try {\n            this.logic.componentDidMount();\n          } catch (e) {\n            console.error(e);\n          }\n        } else {\n          try {\n            this.logic.componentDidUpdate(prevProps);\n          } catch (e) {\n            console.error(e);\n          }\n        }\n      }\n      componentWillUnmount() {\n        registry.get(this.__name).subs.delete(this.__sub);\n        if (!this.__needsDidMount) {\n          try {\n            this.logic.componentWillUnmount();\n          } catch (e) {\n            console.error(e);\n          }\n        }\n      }\n      render() {\n        const r = registry.get(this.__name);\n        const cls = \"sc-host\" + (r.htmlStreaming ? \" sc-streaming-html\" : \"\") + (r.jsStreaming ? \" sc-streaming-js\" : \"\");\n        const hintStyle = r.htmlStreaming ? hintToMin(this.props.__hintSize) : void 0;\n        const hostStyle = this.props.__hostStyle || hintStyle ? { ...hintStyle || {}, ...this.props.__hostStyle || {} } : void 0;\n        const hostBase = {\n          className: cls,\n          style: hostStyle,\n          \"data-sc-name\": this.__name,\n          \"data-dc-tpl\": this.props.__tplId\n        };\n        const chain = Array.isArray(this.context) ? this.context : [];\n        if (chain.includes(this.__name)) {\n          const cycle = [\n            ...chain.slice(chain.indexOf(this.__name)),\n            this.__name\n          ].join(\" \\u2192 \");\n          return h(\n            \"div\",\n            { ...hostBase, className: cls + \" sc-has-error\" },\n            h(Placeholder, {\n              name: this.__name,\n              hintSize: this.props.__hintSize,\n              error: \"circular import: \" + cycle\n            })\n          );\n        }\n        if (this.state.__err) {\n          return h(\n            \"div\",\n            { ...hostBase, className: cls + \" sc-has-error\" },\n            h(\n              \"div\",\n              { className: \"sc-logic-error\", \"data-omelette-chrome\": \"\" },\n              this.__name + \": \" + this.state.__err\n            ),\n            h(Placeholder, {\n              name: this.__name,\n              hintSize: this.props.__hintSize,\n              error: this.state.__err\n            })\n          );\n        }\n        this.__reconcileLogic();\n        if (!r.tpl) {\n          return h(\n            \"div\",\n            hostBase,\n            h(Placeholder, { name: this.__name, hintSize: this.props.__hintSize })\n          );\n        }\n        const userProps = this.__userProps();\n        this.logic.props = userProps;\n        let vals = userProps;\n        let renderErr = r.logicError || this.__ctorError;\n        try {\n          vals = { ...userProps, ...this.logic.renderVals() || {} };\n        } catch (e) {\n          console.error(e);\n          renderErr = this.__name + \".renderVals(): \" + (e instanceof Error && e.message ? e.message : String(e));\n        }\n        this.__streamingNow = !!(r.htmlStreaming || r.jsStreaming);\n        this.__htmlStreamingNow = !!r.htmlStreaming;\n        return h(\n          \"div\",\n          { ...hostBase, className: cls + (renderErr ? \" sc-has-error\" : \"\") },\n          renderErr && h(\n            \"div\",\n            { className: \"sc-logic-error\", \"data-omelette-chrome\": \"\" },\n            renderErr\n          ),\n          h(\n            AncestorContext.Provider,\n            { value: [...chain, this.__name] },\n            r.tpl(vals, this)\n          )\n        );\n      }\n    }\n    __publicField(StreamableComponent, \"contextType\", AncestorContext);\n    const named = \/* @__PURE__ *\/ new Map();\n    function getDC(name) {\n      const hit = named.get(name);\n      if (hit) return hit;\n      function Dispatcher(p) {\n        const [, setTick] = React.useState(0);\n        React.useEffect(() => {\n          const sub = () => setTick((n) => n + 1);\n          registry.get(name).subs.add(sub);\n          return () => {\n            registry.get(name).subs.delete(sub);\n          };\n        }, []);\n        ensureFetched(name);\n        return h(StreamableComponent, { ...p, __name: name });\n      }\n      Dispatcher.displayName = name;\n      named.set(name, Dispatcher);\n      return Dispatcher;\n    }\n    return {\n      getDC,\n      StreamableComponent\n    };\n  }\n\n  \/\/ src\/bundled.ts\n  function bundledBlob(url) {\n    const blobs = window.__resourceBlobs;\n    const b = blobs ? blobs[url.split(\"#\")[0]] : void 0;\n    return b instanceof Blob ? b : null;\n  }\n\n  \/\/ src\/cdn.ts\n  var REACT_URL = \"https:\/\/unpkg.com\/react@18.3.1\/umd\/react.production.min.js\";\n  var REACT_SRI = \"sha384-DGyLxAyjq0f9SPpVevD6IgztCFlnMF6oW\/XQGmfe+IsZ8TqEiDrcHkMLKI6fiB\/Z\";\n  var REACT_DOM_URL = \"https:\/\/unpkg.com\/react-dom@18.3.1\/umd\/react-dom.production.min.js\";\n  var REACT_DOM_SRI = \"sha384-gTGxhz21lVGYNMcdJOyq01Edg0jhn\/c22nsx0kyqP0TxaV5WVdsSH1fSDUf5YJj1\";\n  var BABEL_URL = \"https:\/\/unpkg.com\/@babel\/standalone@7.29.0\/babel.min.js\";\n  var BABEL_SRI = \"sha384-m08KidiNqLdpJqLq95G\/LEi8Qvjl\/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y\";\n  function cdnScriptFor(url, sri) {\n    const res = window.__resources;\n    const v = res ? res[url] : void 0;\n    return typeof v === \"string\" && v ? { src: v } : { src: url, integrity: sri };\n  }\n\n  \/\/ src\/external.ts\n  var isCustomElementName = (n) => !n.includes(\".\") && n.includes(\"-\");\n  function isRenderableType(g) {\n    if (typeof g === \"function\") return !isElementClass(g);\n    return typeof g === \"object\" && g !== null && typeof g.$$typeof === \"symbol\";\n  }\n  function resolveDottedPath(root, name) {\n    let cur = root;\n    for (const seg of name.split(\".\")) {\n      if (cur == null) return void 0;\n      cur = cur[seg];\n    }\n    return cur;\n  }\n  var GLOBAL_POLL_INTERVAL_MS = 50;\n  var GLOBAL_POLL_TIMEOUT_MS = 3e4;\n  function createExternalModules(onResolved) {\n    const cache = \/* @__PURE__ *\/ new Map();\n    let babelLoading = null;\n    const reportedMissing = \/* @__PURE__ *\/ new Map();\n    const polling = \/* @__PURE__ *\/ new Set();\n    function ensureBabel() {\n      if (window.Babel) return Promise.resolve();\n      if (babelLoading) return babelLoading;\n      const babel = cdnScriptFor(BABEL_URL, BABEL_SRI);\n      babelLoading = new Promise((res, rej) => {\n        const s = document.createElement(\"script\");\n        s.src = babel.src;\n        if (babel.integrity) {\n          s.integrity = babel.integrity;\n          s.crossOrigin = \"anonymous\";\n        }\n        s.onload = () => res();\n        s.onerror = rej;\n        document.head.appendChild(s);\n      });\n      return babelLoading;\n    }\n    const pending = \/* @__PURE__ *\/ new Map();\n    function load(kind, url, after) {\n      const existing = pending.get(url);\n      if (existing) return existing;\n      cache.set(url, null);\n      console.info(\"[dc-runtime] x-import: loading\", url, \"(\" + kind + \")\");\n      const ready = Promise.all([\n        kind === \"jsx\" ? ensureBabel() : Promise.resolve(),\n        after ?? Promise.resolve()\n      ]);\n      const p = ready.then(() => {\n        const pre = bundledBlob(url);\n        if (pre) return pre.text();\n        return fetch(url).then((r) => {\n          if (!r.ok) throw new Error(\"HTTP \" + r.status);\n          return r.text();\n        });\n      }).then((src) => {\n        const code = kind === \"jsx\" ? window.Babel.transform(src, {\n          filename: url,\n          presets: [\"react\", \"typescript\"]\n        }).code : src;\n        const module = { exports: {} };\n        const before = new Set(Object.keys(window));\n        \/\/! nosemgrep: eval-and-function-constructor\n        new Function(\"React\", \"module\", \"exports\", \"require\", code)(\n          getReact(),\n          module,\n          module.exports,\n          () => ({})\n        );\n        const globals = {};\n        for (const k of Object.keys(window)) {\n          if (!before.has(k) && typeof window[k] === \"function\") {\n            globals[k] = window[k];\n          }\n        }\n        cache.set(url, { mod: module.exports, globals });\n        console.info(\n          \"[dc-runtime] x-import: loaded\",\n          url,\n          \"\\u2014 exports:\",\n          Object.keys(module.exports),\n          \"window globals:\",\n          Object.keys(globals)\n        );\n        onResolved();\n      }).catch((e) => {\n        cache.set(url, {\n          mod: {},\n          globals: {},\n          error: \"failed to load: \" + (e instanceof Error && e.message ? e.message : String(e))\n        });\n        console.error(\n          \"[dc-runtime] x-import: FAILED to load\",\n          url,\n          \"(\" + kind + \")\",\n          e\n        );\n        onResolved();\n      });\n      pending.set(url, p);\n      return p;\n    }\n    function resolve2(url, name) {\n      const entry = cache.get(url);\n      if (!entry) return null;\n      const { mod, globals } = entry;\n      const C = mod && mod[name] || globals && globals[name] || typeof window !== \"undefined\" && window[name] || mod && mod.default;\n      if (typeof C === \"function\") return C;\n      const key = url + \"\\0\" + name;\n      if (!reportedMissing.has(key)) {\n        reportedMissing.set(\n          key,\n          entry.error || 'no export named \"' + name + '\" (has: ' + Object.keys(mod).join(\", \") + \")\"\n        );\n        console.error(\n          \"[dc-runtime] x-import: module\",\n          url,\n          \"loaded but has no component named\",\n          JSON.stringify(name),\n          \"\\u2014 available exports:\",\n          Object.keys(mod),\n          \"window globals:\",\n          Object.keys(globals),\n          \". The module must `module.exports = {\" + name + \"}` or set `window.\" + name + \"`.\"\n        );\n      }\n      return null;\n    }\n    function waitForGlobal(name) {\n      if (polling.has(name)) return;\n      polling.add(name);\n      const started = Date.now();\n      const isCE = isCustomElementName(name);\n      const tick = () => {\n        const found = isCE ? customElements.get(name) : isRenderableType(resolveDottedPath(window, name));\n        if (found) {\n          polling.delete(name);\n          onResolved();\n          return;\n        }\n        if (Date.now() - started >= GLOBAL_POLL_TIMEOUT_MS) {\n          console.warn(\n            \"[dc-runtime] x-import: global\",\n            JSON.stringify(name),\n            \"never appeared on window after \" + GLOBAL_POLL_TIMEOUT_MS + \"ms\"\n          );\n          return;\n        }\n        setTimeout(tick, GLOBAL_POLL_INTERVAL_MS);\n      };\n      setTimeout(tick, GLOBAL_POLL_INTERVAL_MS);\n    }\n    function resolveGlobal(url, name) {\n      const isCE = isCustomElementName(name);\n      if (!url) {\n        if (isCE) {\n          if (customElements.get(name)) return name;\n          waitForGlobal(name);\n          return null;\n        }\n        const g2 = resolveDottedPath(window, name);\n        if (isRenderableType(g2)) return g2;\n        waitForGlobal(name);\n        return null;\n      }\n      const entry = cache.get(url);\n      if (!entry) return null;\n      if (isCE && customElements.get(name)) return name;\n      const g = entry.globals[name] ?? resolveDottedPath(window, name);\n      if (isRenderableType(g)) return g;\n      if (name.includes(\".\")) return null;\n      const key = url + \"\\0global\\0\" + name;\n      if (!reportedMissing.has(key)) {\n        reportedMissing.set(key, null);\n        if (isCE && !customElements.get(name)) {\n          console.warn(\n            \"[dc-runtime] x-import:\",\n            url,\n            \"loaded but no custom element\",\n            JSON.stringify(name),\n            \"is registered and window.\" + name + \" is not a function \\u2014 rendering <\" + name + \"> as an unknown element.\"\n          );\n        }\n      }\n      return name;\n    }\n    function getError(url, name) {\n      const entry = cache.get(url);\n      if (entry?.error) return entry.error;\n      return reportedMissing.get(url + \"\\0\" + name) || null;\n    }\n    return { load, resolve: resolve2, resolveGlobal, getError };\n  }\n  function isElementClass(g) {\n    try {\n      return typeof g === \"function\" && typeof HTMLElement !== \"undefined\" && g.prototype instanceof HTMLElement;\n    } catch {\n      return false;\n    }\n  }\n\n  \/\/ src\/atomics.ts\n  var ATOMIC_CSS = (\n    \/\/ layout\n    \".fx{display:flex}.col{display:flex;flex-direction:column}.grid{display:grid}.ac{align-items:center}.jc{justify-content:center}.jb{justify-content:space-between}.f1{flex:1}.noshrink{flex-shrink:0}.wrap{flex-wrap:wrap}.fw5{font-weight:500}.fw6{font-weight:600}.fw7{font-weight:700}.fw8{font-weight:800}.fs11{font-size:11px}.fs12{font-size:12px}.fs13{font-size:13px}.fs14{font-size:14px}.fs15{font-size:15px}.fs16{font-size:16px}.fs20{font-size:20px}.fs22{font-size:22px}.upper{text-transform:uppercase}.tc{text-align:center}.nowrap{white-space:nowrap}.gap8{gap:8px}.gap10{gap:10px}.gap12{gap:12px}.gap16{gap:16px}.gap24{gap:24px}.m0{margin:0}.mt8{margin-top:8px}.mt12{margin-top:12px}.mt16{margin-top:16px}.mb8{margin-bottom:8px}.mb12{margin-bottom:12px}.mb16{margin-bottom:16px}.posrel{position:relative}.posabs{position:absolute}.round{border-radius:50%}.ohide{overflow:hidden}.bbox{box-sizing:border-box}.pointer{cursor:pointer}.w100{width:100%}.b0{border:none}\"\n  );\n\n  \/\/ src\/helmet.ts\n  var DESIGN_DOC_MODE_RE = \/<meta\\b[^>]*\\bname\\s*=\\s*[\"']design_doc_mode[\"'][^>]*\\b(?:content|value)\\s*=\\s*[\"'](\\w+)[\"']\/i;\n  var CANVAS_BG_LIGHT = \"#f0eee6\";\n  var CANVAS_BG_DARK = \"#2e2c26\";\n  function createHelmetManager(doc, isStreaming) {\n    const mounted = \/* @__PURE__ *\/ new Set();\n    const live = \/* @__PURE__ *\/ new Map();\n    let designDocMode = null;\n    let canvasStyleEl = null;\n    let appTheme = \"light\";\n    try {\n      const ds = doc.documentElement.dataset.theme;\n      appTheme = ds === \"dark\" || ds === \"light\" ? ds : new URLSearchParams(doc.defaultView?.location.search ?? \"\").get(\n        \"theme\"\n      ) === \"dark\" ? \"dark\" : \"light\";\n    } catch {\n    }\n    function applyCanvasBg() {\n      if (!canvasStyleEl) return;\n      const bg = appTheme === \"dark\" ? CANVAS_BG_DARK : CANVAS_BG_LIGHT;\n      canvasStyleEl.textContent = `html,body{background:${bg}}#dc-root>.sc-host{position:relative}`;\n    }\n    function postDesignMode(mode) {\n      if (window.parent === window) return;\n      try {\n        window.parent.postMessage({ type: \"__dc_design_mode\", mode }, \"*\");\n      } catch {\n      }\n    }\n    function setDesignDocMode(mode) {\n      if (mode === designDocMode) return;\n      designDocMode = mode;\n      postDesignMode(mode);\n      if (mode === \"canvas\") {\n        doc.documentElement.setAttribute(\"data-dc-canvas\", \"\");\n        canvasStyleEl = doc.createElement(\"style\");\n        canvasStyleEl.setAttribute(\"data-dc-canvas\", \"\");\n        applyCanvasBg();\n        doc.head.appendChild(canvasStyleEl);\n      } else {\n        doc.documentElement.removeAttribute(\"data-dc-canvas\");\n        canvasStyleEl?.remove();\n        canvasStyleEl = null;\n      }\n    }\n    window.addEventListener(\"message\", (e) => {\n      const type = e.data && e.data.type;\n      if (type === \"__dc_theme\") {\n        const t = e.data.theme;\n        if (t === \"light\" || t === \"dark\") {\n          appTheme = t;\n          applyCanvasBg();\n        }\n        return;\n      }\n      if (!designDocMode || type !== \"__dc_probe\") return;\n      postDesignMode(designDocMode);\n    });\n    function compile(node) {\n      const raw = [...node.children];\n      const helmetClosed = node.nextSibling != null || node.parentNode?.nextSibling != null;\n      if (node.hasAttribute(\"data-dc-atomics\") && !mounted.has(\"__dc-atomics\")) {\n        mounted.add(\"__dc-atomics\");\n        const el = doc.createElement(\"style\");\n        el.id = \"__dc-atomics\";\n        el.textContent = ATOMIC_CSS;\n        doc.head.appendChild(el);\n      }\n      return (_vals, ctx) => {\n        const name = ctx && ctx.__name || \"\";\n        const streaming = !!(name && isStreaming(name));\n        for (let i = 0; i < raw.length; i++) {\n          const child = raw[i];\n          const tag = child.tagName;\n          const mayBePartial = streaming &#038;&#038; !helmetClosed &#038;&#038; i === raw.length - 1;\n          if (tag === \"SCRIPT\") {\n            if (mayBePartial) continue;\n            const key = \"SCRIPT|\" + (child.getAttribute(\"src\") || child.textContent || \"\");\n            if (mounted.has(key)) continue;\n            mounted.add(key);\n            const el = doc.createElement(\"script\");\n            for (const { name: an, value } of [...child.attributes])\n              el.setAttribute(an, value);\n            if (child.textContent) el.textContent = child.textContent;\n            doc.head.appendChild(el);\n          } else if (tag === \"LINK\" || tag === \"META\") {\n            if (mayBePartial) continue;\n            const key = tag + \"|\" + (child.getAttribute(\"href\") || child.getAttribute(\"src\") || child.outerHTML);\n            if (mounted.has(key)) continue;\n            mounted.add(key);\n            if (tag === \"LINK\") {\n              const rel = (child.getAttribute(\"rel\") || \"\").toLowerCase().split(\/\\s+\/);\n              const href = (child.getAttribute(\"href\") || \"\").trim();\n              const res = window.__resources;\n              const pre = res &#038;&#038; rel.includes(\"stylesheet\") &#038;&#038; !rel.includes(\"alternate\") ? res[href] : void 0;\n              const blob = typeof pre === \"string\" &#038;&#038; pre ? bundledBlob(pre) : null;\n              if (blob) {\n                const el = doc.createElement(\"style\");\n                if (child.hasAttribute(\"disabled\")) {\n                  el.setAttribute(\"media\", \"not all\");\n                } else if (child.getAttribute(\"media\")) {\n                  el.setAttribute(\"media\", child.getAttribute(\"media\"));\n                }\n                if (child.getAttribute(\"title\"))\n                  el.setAttribute(\"title\", child.getAttribute(\"title\"));\n                void blob.text().then((css) => {\n                  el.textContent = css;\n                });\n                doc.head.appendChild(el);\n                continue;\n              }\n            }\n            doc.head.appendChild(child.cloneNode(true));\n          } else {\n            const key = name + \"|\" + i;\n            let el = live.get(key);\n            if (!el || el.tagName !== tag) {\n              if (el) el.remove();\n              el = doc.createElement(tag.toLowerCase());\n              live.set(key, el);\n              doc.head.appendChild(el);\n            }\n            for (const { name: an, value } of [...child.attributes]) {\n              if (el.getAttribute(an) !== value) el.setAttribute(an, value);\n            }\n            if (el.textContent !== child.textContent)\n              el.textContent = child.textContent;\n          }\n        }\n        return null;\n      };\n    }\n    return { compile, setDesignDocMode };\n  }\n\n  \/\/ src\/pseudo.ts\n  function scanUnquotedUrl(css, i) {\n    if (css[i] !== \"u\" && css[i] !== \"U\" || css.slice(i, i + 4).toLowerCase() !== \"url(\" || \/[a-z0-9_-]\/i.test(css[i - 1] ?? \"\")) {\n      return -1;\n    }\n    let j = i + 4;\n    while (j < css.length &#038;&#038; \/\\s\/.test(css[j])) j++;\n    if (css[j] === '\"' || css[j] === \"'\") return -1;\n    while (j < css.length &#038;&#038; css[j] !== \")\") {\n      if (css[j] === \"\\\\\") j++;\n      j++;\n    }\n    return j < css.length ? j + 1 : css.length;\n  }\n  function stripComments(css) {\n    let out = \"\";\n    let quote = \"\";\n    for (let i = 0; i < css.length; i++) {\n      const c = css[i];\n      if (quote) {\n        if (c === \"\\\\\") {\n          out += c + (css[i + 1] ?? \"\");\n          i++;\n          continue;\n        }\n        if (c === quote) quote = \"\";\n        out += c;\n      } else if (c === \"'\" || c === '\"') {\n        quote = c;\n        out += c;\n      } else if (c === \"\/\" &#038;&#038; css[i + 1] === \"*\") {\n        const end = css.indexOf(\"*\/\", i + 2);\n        i = end === -1 ? css.length : end + 1;\n        out += \" \";\n      } else {\n        const end = scanUnquotedUrl(css, i);\n        if (end === -1) out += c;\n        else {\n          out += css.slice(i, end);\n          i = end - 1;\n        }\n      }\n    }\n    return out;\n  }\n  function importantify(css) {\n    css = stripComments(css);\n    const decls = [];\n    let start = 0;\n    let depth = 0;\n    let quote = \"\";\n    for (let i = 0; i < css.length; i++) {\n      const c = css[i];\n      if (quote) {\n        if (c === \"\\\\\") i++;\n        else if (c === quote) quote = \"\";\n      } else if (c === \"'\" || c === '\"') quote = c;\n      else if (c === \"(\") depth++;\n      else if (c === \")\") depth = Math.max(0, depth - 1);\n      else if (c === \";\" &#038;&#038; depth === 0) {\n        decls.push(css.slice(start, i));\n        start = i + 1;\n      } else {\n        const end = scanUnquotedUrl(css, i);\n        if (end !== -1) i = end - 1;\n      }\n    }\n    decls.push(css.slice(start));\n    return decls.map((d) => d.trim()).filter(Boolean).map((d) => \/!\\s*important$\/i.test(d) ? d : d + \" !important\").join(\";\");\n  }\n  function createPseudoSheet(doc) {\n    let el = null;\n    const cache = \/* @__PURE__ *\/ new Map();\n    let n = 0;\n    return (pseudo, css) => {\n      const k = pseudo + \"|\" + css;\n      const hit = cache.get(k);\n      if (hit) return hit;\n      if (!el) {\n        el = doc.createElement(\"style\");\n        doc.head.appendChild(el);\n      }\n      const cls = \"scp\" + (n++).toString(36);\n      const isPseudoElement = pseudo === \"before\" || pseudo === \"after\";\n      const sel = isPseudoElement ? \".\" + cls + \"::\" + pseudo : \".\" + cls + \":\" + pseudo;\n      el.sheet.insertRule(\n        sel + \"{\" + (isPseudoElement ? css : importantify(css)) + \"}\",\n        el.sheet.cssRules.length\n      );\n      cache.set(k, cls);\n      return cls;\n    };\n  }\n\n  \/\/ src\/registry.ts\n  function createRegistry() {\n    const entries = \/* @__PURE__ *\/ Object.create(null);\n    function get(name) {\n      return entries[name] || (entries[name] = {\n        html: \"\",\n        tpl: null,\n        Logic: null,\n        jsStreaming: false,\n        htmlStreaming: false,\n        ver: 0,\n        subs: \/* @__PURE__ *\/ new Set(),\n        fetched: false\n      });\n    }\n    function bump(name) {\n      const r = get(name);\n      r.ver++;\n      for (const fn of r.subs) fn();\n    }\n    return {\n      entries,\n      get,\n      bump,\n      bumpAll() {\n        for (const n in entries) bump(n);\n      }\n    };\n  }\n\n  \/\/ src\/runtime.ts\n  var COMPONENT_DIR = \".\";\n  function createRuntime(doc = document) {\n    const registry = createRegistry();\n    const pseudoClass = createPseudoSheet(doc);\n    const helmet = createHelmetManager(\n      doc,\n      (name) => registry.get(name).htmlStreaming\n    );\n    const external = createExternalModules(() => registry.bumpAll());\n    const factory = createComponentFactory(registry, ensureFetched);\n    const host = {\n      component: (name) => factory.getDC(name),\n      placeholder: (props) => h(Placeholder, props),\n      helmet: (node) => helmet.compile(node),\n      loadExternal: (kind, url, after) => external.load(kind, url, after),\n      resolveExternal: (url, name) => external.resolve(url, name),\n      resolveExternalGlobal: (url, name) => external.resolveGlobal(url, name),\n      resolveExternalError: (url, name) => external.getError(url, name),\n      pseudoClass\n    };\n    function ensureFetched(name) {\n      const r = registry.get(name);\n      if (r.fetched) return;\n      r.fetched = true;\n      const url = COMPONENT_DIR + \"\/\" + encodeURIComponent(name) + \".dc.html\";\n      const res = window.__resources;\n      const pre = res ? res[url] : void 0;\n      const target = typeof pre === \"string\" && pre ? pre : url;\n      const blob = bundledBlob(target);\n      (blob ? blob.text() : fetch(target).then((res2) => {\n        if (!res2.ok) {\n          console.error(\n            '[dc-runtime] sibling fetch for \"' + name + '\" failed:',\n            url,\n            \"returned\",\n            res2.status,\n            \"\\u2014 the reference renders as an empty placeholder.\"\n          );\n          return \"\";\n        }\n        return res2.text();\n      })).then((t) => {\n        if (!t) return;\n        const parsed = parseDcText(t);\n        if (!parsed) {\n          console.error(\n            '[dc-runtime] sibling fetch for \"' + name + '\":',\n            url,\n            \"has no <x-dc> block \\u2014 not a Design Component.\"\n          );\n          return;\n        }\n        if (parsed.props) r.propsMeta = parsed.props;\n        if (parsed.preview) r.preview = parsed.preview;\n        if (parsed.template && !r.html) updateHtml(name, parsed.template);\n        if (parsed.js && !r.Logic) updateJs(name, parsed.js);\n      }).catch(\n        (e) => console.error(\n          '[dc-runtime] sibling fetch for \"' + name + '\" threw:',\n          url,\n          e\n        )\n      );\n    }\n    let rootName = null;\n    function updateHtml(name, html) {\n      const r = registry.get(name);\n      r.html = html;\n      if (name === rootName) {\n        const mode = DESIGN_DOC_MODE_RE.exec(html)?.[1] ?? null;\n        if (mode || !r.htmlStreaming) helmet.setDesignDocMode(mode);\n      }\n      try {\n        r.tpl = compileTemplate(html, host);\n      } catch (e) {\n        console.error(\"[dc-runtime] template compile FAILED for\", name, e);\n      }\n      registry.bump(name);\n    }\n    function updateJs(name, src) {\n      const r = registry.get(name);\n      const seq = r.jsSeq = (r.jsSeq || 0) + 1;\n      try {\n        const Cls = evalDcLogic(src);\n        if (r.jsSeq !== seq) return;\n        if (typeof Cls !== \"function\") {\n          r.logicError = name + \".dc.html: <script data-dc-script> must define `class Component extends DCLogic`\";\n        } else {\n          r.logicError = null;\n          r.Logic = Cls;\n        }\n      } catch (e) {\n        if (r.jsSeq !== seq) return;\n        console.error(\n          \"[dc-runtime] logic class eval FAILED for\",\n          name,\n          \"\\u2014 the template renders with props only.\",\n          e\n        );\n        r.logicError = name + \": \" + (e instanceof Error && e.message ? e.message : String(e));\n      }\n      registry.bump(name);\n    }\n    function setStreaming(name, kind, on) {\n      const r = registry.get(name);\n      if (kind === \"html\") r.htmlStreaming = !!on;\n      else r.jsStreaming = !!on;\n      let any = false;\n      for (const n in registry.entries) {\n        const e = registry.entries[n];\n        if (e && (e.htmlStreaming || e.jsStreaming)) {\n          any = true;\n          break;\n        }\n      }\n      doc.documentElement.classList.toggle(\"sc-dc-streaming\", any);\n      registry.bump(name);\n    }\n    function dcUpdate(name, kind, content, streaming) {\n      if (streaming) registry.get(name).fetched = true;\n      if (kind === \"html\") {\n        setStreaming(name, \"html\", !!streaming);\n        updateHtml(name, content);\n      } else if (kind === \"js\") {\n        setStreaming(name, \"js\", !!streaming);\n        if (!streaming) updateJs(name, content);\n      } else if (kind === \"props\") {\n        const { props, preview } = parseDataProps(content);\n        const r = registry.get(name);\n        r.propsMeta = props ?? void 0;\n        r.preview = preview;\n        registry.bump(name);\n      }\n    }\n    function setProps(name, overrides) {\n      registry.get(name).propOverrides = overrides && typeof overrides === \"object\" ? { ...overrides } : null;\n      registry.bump(name);\n    }\n    function adoptParsed(name, parsed) {\n      if (!parsed) return;\n      const r = registry.get(name);\n      if (parsed.props) r.propsMeta = parsed.props;\n      if (parsed.preview) r.preview = parsed.preview;\n      if (parsed.template) updateHtml(name, parsed.template);\n      if (parsed.js) updateJs(name, parsed.js);\n    }\n    return {\n      registry,\n      getDC: factory.getDC,\n      updateHtml,\n      updateJs,\n      dcUpdate,\n      setProps,\n      adoptParsed,\n      setRootName: (name) => {\n        rootName = name;\n      },\n      markFetched: (name) => {\n        registry.get(name).fetched = true;\n      },\n      annotatedTemplate: (name) => {\n        const r = registry.get(name);\n        return r.tpl && r.tpl.__annotated || null;\n      },\n      templateSource: (name) => registry.get(name).html || null,\n      StreamableLogic\n    };\n  }\n\n  \/\/ src\/stream-state.ts\n  function createStreamTracker(staleMs = 6e4, now = Date.now) {\n    const since = \/* @__PURE__ *\/ new Map();\n    const liveOne = (n) => {\n      const t = since.get(n);\n      if (t === void 0) return false;\n      if (now() - t > staleMs) {\n        since.delete(n);\n        return false;\n      }\n      return true;\n    };\n    return {\n      push(name, streaming, viewportKey) {\n        if (viewportKey === \"dc-model\") return;\n        if (streaming) since.set(name, now());\n        else since.delete(name);\n      },\n      live(name) {\n        if (name !== void 0) return liveOne(name);\n        for (const n of [...since.keys()]) if (liveOne(n)) return true;\n        return false;\n      }\n    };\n  }\n\n  \/\/ src\/index.ts\n  function hideRawTemplate() {\n    const s = document.createElement(\"style\");\n    s.textContent = \"x-dc{display:none!important}\";\n    document.head.appendChild(s);\n  }\n  function loadScript(src, integrity) {\n    return new Promise((resolve2, reject) => {\n      \/\/! nosemgrep: create-script-element\n      const s = document.createElement(\"script\");\n      s.src = src;\n      if (integrity) {\n        s.integrity = integrity;\n        s.crossOrigin = \"anonymous\";\n      }\n      s.async = false;\n      s.onload = () => resolve2();\n      s.onerror = () => reject(new Error(`failed to load ${src}`));\n      document.head.appendChild(s);\n    });\n  }\n  function loadReactUmd() {\n    const w = window;\n    if (w.React && w.ReactDOM) return Promise.resolve();\n    const react = cdnScriptFor(REACT_URL, REACT_SRI);\n    const reactDom = cdnScriptFor(REACT_DOM_URL, REACT_DOM_SRI);\n    return Promise.all([\n      loadScript(react.src, react.integrity),\n      loadScript(reactDom.src, reactDom.integrity)\n    ]).then(() => void 0);\n  }\n  function init() {\n    const runtime = createRuntime(document);\n    let rootName = \"Root\";\n    const baseCss = document.createElement(\"style\");\n    baseCss.textContent = BASE_CSS;\n    document.head.prepend(baseCss);\n    const notifyHost = () => {\n      if (window.parent === window) return;\n      const r = runtime.registry.entries[rootName];\n      try {\n        window.parent.postMessage(\n          {\n            type: \"__dc_booted\",\n            rootName,\n            propsMeta: r && r.propsMeta || null,\n            preview: r && r.preview || null\n          },\n          \"*\"\n        );\n      } catch {\n      }\n    };\n    const streams = createStreamTracker();\n    const api = {\n      __dcUpdate: (name, kind, content, streaming, viewportKey) => {\n        streams.push(name, streaming, viewportKey);\n        runtime.dcUpdate(name, kind, content, streaming);\n        if (name === rootName && !streaming && kind === \"props\") notifyHost();\n      },\n      __dcStreaming: (name) => streams.live(name),\n      __dcSetProps: (name, overrides) => runtime.setProps(name, overrides),\n      \/** Name of the component currently mounted as the page root \u2014 DC tools\n       *  push their template-stream here when targeting \"the open page\". *\/\n      __dcRootName: () => rootName,\n      \/** Editor bridge \u2014 the encoded, `data-dc-tpl`-annotated template source.\n       *  The host editor parses this into its own template DOM so it can map a\n       *  rendered node (carrying the same `data-dc-tpl`) back to the source\n       *  node that emitted it. Returns the encoded form (`sc-camel-*` attrs,\n       *  `<sc-raw-*>`\/`<sc-helmet>` tags); the editor decodes on serialize. *\/\n      __dcAnnotatedTemplate: (name) => runtime.annotatedTemplate(name),\n      \/** Editor bridge \u2014 the *original* (decoded) template source. *\/\n      __dcTemplateSource: (name) => runtime.templateSource(name),\n      __dcBoot: () => {\n        rootName = boot(runtime, document) ?? rootName;\n        notifyHost();\n      },\n      __dcRegistry: runtime.registry.entries,\n      getDC: (name) => runtime.getDC(name),\n      \/\/ `DCLogic` is the documented base class name; `StreamableLogic` is the\n      \/\/ implementation alias kept for any project that already references it.\n      DCLogic: runtime.StreamableLogic,\n      StreamableLogic: runtime.StreamableLogic\n    };\n    Object.assign(window, api);\n    window.__dcContentKeyed = true;\n    if (document.readyState !== \"loading\") api.__dcBoot();\n    else document.addEventListener(\"DOMContentLoaded\", () => api.__dcBoot());\n  }\n  hideRawTemplate();\n  loadReactUmd().then(init).catch((err) => {\n    console.error(\"[dc] failed to load React or boot:\", err);\n    throw err;\n  });\n})();\n<\/script> \n\n<div class=\"kavkom-api-page\">\n\n  <!-- HEADER -->\n  <header class=\"kavkom-header\">\n    <div class=\"kavkom-container kavkom-header-inner\">\n\n      <a href=\"#top\" class=\"kavkom-logo\">\n        <img decoding=\"async\" src=\"https:\/\/kavkom.com\/wp-content\/uploads\/2024\/07\/Logo_Kavkom_bleu-bg.svg\" alt=\"Kavkom\"\/>\n      <\/a>\n\n      <nav class=\"kavkom-nav\">\n        <a href=\"#fonctionnalites\">Features<\/a>\n        <a href=\"#webhooks\">Webhooks<\/a>\n        <a href=\"#sip\">SIP Trunk<\/a>\n        <a href=\"#quickstart\">Quick Start<\/a>\n        <a href=\"#tarifs\">Pricing<\/a>\n        <a href=\"#faq\">FAQ<\/a>\n      <\/nav>\n\n      <a href=\"#formulaire\" class=\"btn btn-primary btn-header\">\n         Schedule a demo\n      <\/a>\n\n    <\/div>\n  <\/header>\n\n\n  <main id=\"top\">\n\n    <!-- HERO -->\n    <section class=\"hero kavkom-container\">\n\n      <div class=\"hero-content\">\n\n        <div class=\"eyebrow\">\n           Voice & Telephony API \u2014 for SaaS providers\n        <\/div>\n\n        <h1>\n           The telephony module of your product,\n <span>         in a few lines.<\/span>\n        <\/h1>\n\n        <p class=\"hero-description\">\n           Phone numbers, outbound calls, click-to-call, webphone,\n         recording and real-time events.\n <strong>         You build the experience; we run the network.<\/strong>\n        <\/p>\n\n        <div class=\"hero-actions\">\n           <a href=\"#formulaire\" class=\"btn btn-primary\">\n           Discover Kavkom with a personalized demo\n         <\/a>\n\n          <a href=\"#formulaire\" class=\"btn btn-outline\">\n           Ask an advisor for your free trial \u2192\n         <\/a> \n        <\/div>\n\n        <div class=\"hero-badges\">\n\n          <span class=\"badge\">\n            <span class=\"badge-dot badge-dot-blue\"><\/span>\n             ARCEP-Registered Operator\n          <\/span>\n\n          <span class=\"badge\">\n            <span class=\"badge-dot badge-dot-green\"><\/span>\n             Data Hosted in Europe (GDPR)\n          <\/span>\n\n          <span class=\"badge\">\n            <span class=\"badge-dot badge-dot-green\"><\/span>\n             No commitment & white label\n          <\/span>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- CODE DEMO -->\n      <div class=\"code-window hero-code\">\n\n        <div class=\"code-header\">\n\n          <div class=\"window-controls\">\n            <span class=\"window-dot red\"><\/span>\n            <span class=\"window-dot yellow\"><\/span>\n            <span class=\"window-dot green\"><\/span>\n          <\/div>\n\n          <span class=\"code-filename\">\n             click-to-call.sh\n          <\/span>\n\n           <button type=\"button\" class=\"copy-code-btn\" data-copy-target=\"hero-code-content\">\n           Copy\n         <\/button>\n\n          <span class=\"live-badge\">\n            <span class=\"live-dot\"><\/span>\n           LIVE\n         <\/span> \n\n        <\/div>\n\n        <pre id=\"hero-code-content\"><code># Lancer un appel : sonne l'agent, puis bridge vers le client\ncurl -X POST \"https:\/\/api.kavkom.com\/api\/pbx\/v1\/active_call\/call\" \\\n  -H \"X-API-TOKEN: $TOKEN\" \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\n    \"domain_uuid\": \"ddcfeb62-110f-4bd7-...\",\n    \"src\": \"901\",\n    \"destination\": \"33612345678\",\n    \"auto_answer\": true\n  }'\n\n# \u2192 enregistrement automatique + identifiant d'appel\n{\n  \"success\": true,\n  \"call_uuid\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"recording\": \"+OK\"\n}<\/code><\/pre>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- INFRA -->\n    <section class=\"infra kavkom-container\">\n\n      <div class=\"section-label\">\n         Voice infrastructure, without having to build the network\n      <\/div>\n\n      <div class=\"infra-list\">\n        <span>U.S. and International Numbers<\/span>\n        <span>\/<\/span>\n        <span>HD VoIP<\/span>\n        <span>\/<\/span>\n        <span>WebRTC<\/span>\n        <span>\/<\/span>\n        <span>SIP Trunk<\/span>\n        <span>\/<\/span>\n        <span>Registration<\/span>\n        <span>\/<\/span>\n        <span>Real-time WebSocket<\/span>\n        <span>\/<\/span>\n        <span>French-language support<\/span>\n      <\/div>\n\n    <\/section>\n\n\n    <!-- PREUVE SOCIALE -->\n    <section class=\"social-proof\">\n\n      <div class=\"kavkom-container\">\n\n        <div class=\"stats\">\n\n          <div class=\"stat\">\n            <strong>2,500+<\/strong>\n            <span>companies equipped<\/span>\n          <\/div>\n\n          <div class=\"stat\">\n            <strong>99.9%<\/strong>\n            <span>availability<\/span>\n          <\/div>\n\n          <div class=\"stat\">\n            <strong>4.7\/5<\/strong>\n            <span>average rating<\/span>\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"client-logos\">\n\n          <img decoding=\"async\" src=\"https:\/\/kavkom.com\/wp-content\/uploads\/2023\/07\/logo-orpi-immo-150x150.webp\" alt=\"Orpi\" loading=\"lazy\"\/>\n\n          <img decoding=\"async\" src=\"https:\/\/kavkom.com\/wp-content\/uploads\/2023\/07\/century21-150x150.png\" alt=\"Century 21\" loading=\"lazy\"\/>\n\n          <img decoding=\"async\" src=\"https:\/\/kavkom.com\/wp-content\/uploads\/2023\/07\/Bnology-150x150.webp\" alt=\"Bnology\" loading=\"lazy\"\/>\n\n          <img decoding=\"async\" src=\"https:\/\/kavkom.com\/wp-content\/uploads\/2025\/01\/Logo-Carrefour-Market1-150x150.webp\" alt=\"Carrefour Market\" loading=\"lazy\"\/>\n\n          <img decoding=\"async\" src=\"https:\/\/kavkom.com\/wp-content\/uploads\/2023\/07\/lafoirfouille1-150x150.png\" alt=\"The Foir'Fouille\" loading=\"lazy\"\/>\n\n          <img decoding=\"async\" src=\"https:\/\/kavkom.com\/wp-content\/uploads\/2023\/07\/Vip-CALL-CENTER-1-150x150.webp\" alt=\"VIP Call Center\" loading=\"lazy\"\/>\n\n          <img decoding=\"async\" src=\"https:\/\/kavkom.com\/wp-content\/uploads\/2023\/07\/FiTec-150x150.png\" alt=\"FiTec\" loading=\"lazy\"\/>\n\n        <\/div>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- FONCTIONNALIT\u00c9S -->\n    <section id=\"fonctionnalites\" class=\"features-section\">\n\n      <div class=\"kavkom-container\">\n\n        <div class=\"section-heading\">\n          <div class=\"eyebrow\">Full-Featured Voice API<\/div>\n\n          <h2>\n           Everything you need to add voice to your SaaS\n         <\/h2>\n\n          <p>\n           A single infrastructure to manage phone numbers,\n           calls, WebRTC, recording, and real-time events\n           .\n         <\/p> \n        <\/div>\n\n\n        <div class=\"features-grid\">\n\n          <article class=\"feature-card\">\n            <span class=\"feature-number\">01<\/span>\n            <h3>On-Demand Numbers<\/h3>\n            <p>\n             Provision geographic or international DID numbers\n             via API, routed to the extension of your choice.\n           <\/p> \n            <code>POST \/order \u00b7 type=did<\/code>\n          <\/article>\n\n\n          <article class=\"feature-card\">\n            <span class=\"feature-number\">02<\/span>\n            <h3>Calls & Click-to-Call<\/h3>\n            <p>\n             Initiate a call with a single click: the agent\u2019s phone rings,\n             then the call is transferred to the recipient.\n             Customizable Caller ID.\n           <\/p> \n            <code>POST \/active_call\/call<\/code>\n          <\/article>\n\n\n          <article class=\"feature-card\">\n            <span class=\"feature-number\">03<\/span>\n            <h3>WebRTC Webphone<\/h3>\n            <p>\n             A browser-based softphone using SIP.js:\n             call, receive, hold, mute, transfer, and DTMF.\n           <\/p> \n            <code>SIP.js \u00b7 SimpleUser<\/code>\n          <\/article>\n\n\n          <article class=\"feature-card\">\n            <span class=\"feature-number\">04<\/span>\n            <h3>Automatic Logging<\/h3>\n            <p>\n             Every call generated by the API is logged\n             and associated with its call_uuid.\n           <\/p> \n            <code>recording: \"+OK\"<\/code>\n          <\/article>\n\n\n          <article class=\"feature-card\">\n            <span class=\"feature-number\">05<\/span>\n            <h3>Real-Time Events<\/h3>\n            <p>\n             A WebSocket pushes the status of each call:\n             ringing, active, held, and finished.\n           <\/p> \n            <code>wss:\/\/...\/calls?ext=1001<\/code>\n          <\/article>\n\n\n          <article class=\"feature-card\">\n            <span class=\"feature-number\">06<\/span>\n            <h3>Lines & Extensions<\/h3>\n            <p>\n             Create lines via API and expand your user base.\n             IVR, call queues, and platform-level statistics.\n           <\/p> \n            <code>POST \/order \u00b7 type=line<\/code>\n          <\/article>\n\n\n          <article class=\"feature-card\">\n            <span class=\"feature-number\">07<\/span>\n            <h3>SIP Trunk<\/h3>\n            <p>\n             Already have an Asterisk, 3CX, or FreeSWITCH <a href=\"https:\/\/kavkom.com\/en\/articles\/telephony\/all-you-need-to-know-about-ipbx-a-complete-guide-to-modernizing-your-telephone-system\/\">IPBX<\/a>?\n             Connect it directly to our network.\n           <\/p> \n            <code>SIP \u00b7 TLS \/ SRTP<\/code>\n          <\/article>\n\n\n          <article class=\"feature-card\">\n            <span class=\"feature-number\">08<\/span>\n            <h3>International Coverage<\/h3>\n            <p>\n             Local numbers in many countries\n             and calls to destinations worldwide.\n           <\/p> \n            <code>DID \u00b7 International<\/code>\n          <\/article>\n\n        <\/div>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- WEBHOOKS -->\n    <section id=\"webhooks\" class=\"webhooks-section\">\n\n      <div class=\"kavkom-container two-columns\">\n\n        <div class=\"section-heading\">\n           <div class=\"eyebrow\">\n           WebSocket & Events\n         <\/div>\n\n          <h2>\n           Your calls pushed to your tools in real time\n         <\/h2>\n\n          <p>\n           Respond instantly to changes in a call\u2019s status\n           in your <a href=\"https:\/\/kavkom.com\/en\/articles\/telephony\/why-should-your-crm-be-linked-to-your-call-center-software\/\">CRM<\/a>, SaaS, or business interface.\n         <\/p> \n        <\/div>\n\n\n        <div class=\"call-demo\">\n\n           <div class=\"call-demo-header\">\n            <span>Call in Progress<\/span>\n            <span class=\"live-badge\">\n              <span class=\"live-dot\"><\/span>\n             LIVE\n           <\/span>\n          <\/div>\n\n          <div class=\"call-demo-number\">\n           +33 6 12 34 56 78\n         <\/div> \n\n          <div class=\"call-demo-statuses\">\n            <span data-call-state=\"ringing\">ringing<\/span>\n            <span data-call-state=\"active\">active<\/span>\n            <span data-call-state=\"held\">held<\/span>\n            <span data-call-state=\"finished\">finished<\/span>\n          <\/div>\n\n          <div class=\"call-demo-time\">\n            <span>Duration<\/span>\n            <strong id=\"call-demo-timer\">00:00<\/strong>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- SIP -->\n    <section id=\"sip\" class=\"sip-section\">\n\n      <div class=\"kavkom-container\">\n\n        <div class=\"section-heading\">\n          <div class=\"eyebrow\">SIP Trunk<\/div>\n\n          <h2>\n           Already have an IPBX? Connect it via a SIP Trunk.\n         <\/h2>\n\n          <p>\n           Keep your existing infrastructure and use\n           the Kavkom network for your incoming and outgoing calls.\n         <\/p> \n        <\/div>\n\n\n        <div class=\"sip-grid\">\n\n           <article class=\"sip-card\">\n            <h3>Asterisk<\/h3>\n            <p>\n             Connect your Asterisk infrastructure to the Kavkom network.\n           <\/p>\n          <\/article>\n\n          <article class=\"sip-card\">\n            <h3>3CX<\/h3>\n            <p>\n             Use your existing 3CX installation.\n           <\/p>\n          <\/article> \n\n           <article class=\"sip-card\">\n            <h3>FreeSWITCH<\/h3>\n            <p>\n             Route your calls through Kavkom.\n           <\/p>\n          <\/article>\n\n          <article class=\"sip-card\">\n            <h3>Your IPBX<\/h3>\n            <p>\n             Any compatible SIP infrastructure can be connected.\n           <\/p>\n          <\/article> \n\n        <\/div>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- QUICKSTART -->\n    <section id=\"quickstart\" class=\"quickstart-section\">\n\n      <div class=\"kavkom-container\">\n\n        <div class=\"section-heading\">\n           <div class=\"eyebrow\">\n           Quickstart\n         <\/div>\n\n          <h2>\n           From token to first call in 3 steps\n         <\/h2> \n        <\/div>\n\n\n        <div class=\"quickstart-grid\">\n\n          <article class=\"quickstart-step\" data-step=\"1\">\n            <span class=\"step-number\">01<\/span>\n\n            <h3>\n             Provision a number and line\n           <\/h3>\n\n            <p>\n             Order a DID and an extension via the billing API.\n             Incoming routing is configured automatically.\n           <\/p> \n          <\/article>\n\n\n          <article class=\"quickstart-step\" data-step=\"2\">\n            <span class=\"step-number\">02<\/span>\n\n            <h3>\n             Make a call\n           <\/h3>\n\n            <p>\n             All it takes is a POST request: ring the source,\n             bridge the destination, recording starts\n             and you retrieve the call_uuid.\n           <\/p> \n          <\/article>\n\n\n          <article class=\"quickstart-step\" data-step=\"3\">\n            <span class=\"step-number\">03<\/span>\n\n            <h3>\n             Follow the call live at\n           <\/h3>\n\n            <p>\n             Connect to the WebSocket and respond\n             to every state change in your interface,\n             in real time.\n           <\/p> \n          <\/article>\n\n        <\/div>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- USE CASES -->\n    <section class=\"usecases-section\">\n\n      <div class=\"kavkom-container\">\n\n        <div class=\"section-heading\">\n           <div class=\"eyebrow\">\n          \n Use Cases          <\/div>\n\n          <h2>\n           Designed for products that need to call\n         <\/h2> \n        <\/div>\n\n\n        <div class=\"usecases-grid\">\n\n           <article>\n            <h3>CRM<\/h3>\n            <p>\n             Add click-to-call and call tracking\n             directly to your CRM.\n           <\/p>\n          <\/article>\n\n          <article>\n            <h3>SaaS Platforms<\/h3>\n            <p>\n             Integrate telephony natively into your product.\n           <\/p>\n          <\/article> \n\n          <article>\n            <h3>Call Centers<\/h3>\n            <p>\n             Automate calls and retrieve events\n             in real time.\n           <\/p> \n          <\/article>\n\n          <article>\n            <h3>Marketplaces<\/h3>\n            <p>\n             Connect your users\n             without revealing personal phone numbers.\n           <\/p> \n          <\/article>\n\n        <\/div>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- COMPARAISON -->\n    <section class=\"comparison-section\">\n\n      <div class=\"kavkom-container\">\n\n        <div class=\"section-heading\">\n           <div class=\"eyebrow\">\n           Why Kavkom\n         <\/div>\n\n          <h2>\n           A European alternative, designed for voice\n         <\/h2> \n        <\/div>\n\n\n        <div class=\"comparison-table-wrapper\">\n\n          <table class=\"comparison-table\">\n\n            <thead>\n              <tr>\n                <th>Criterion<\/th>\n                <th>Kavkom<\/th>\n                <th>International APIs<\/th>\n              <\/tr>\n            <\/thead>\n\n            <tbody>\n\n              <tr>\n                <td>French-language support<\/td>\n                <td>Yes<\/td>\n                <td>Varies<\/td>\n              <\/tr>\n\n              <tr>\n                <td>Data hosted in Europe<\/td>\n                <td>Yes<\/td>\n                <td>Varies<\/td>\n              <\/tr>\n\n              <tr>\n                <td>SIP + API + WebRTC<\/td>\n                <td>Yes<\/td>\n                <td>Depending on the solution<\/td>\n              <\/tr>\n\n              <tr>\n                <td>No obligation<\/td>\n                <td>Yes<\/td>\n                <td>Variable<\/td>\n              <\/tr>\n\n              <tr>\n                <td>Personalized Support<\/td>\n                <td>Yes<\/td>\n                <td>Often limited<\/td>\n              <\/tr>\n\n            <\/tbody>\n\n          <\/table>\n\n        <\/div>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- INFRASTRUCTURE -->\n    <section class=\"infrastructure-section\">\n\n      <div class=\"kavkom-container\">\n\n        <div class=\"section-heading\">\n           <div class=\"eyebrow\">\n          \n Infrastructure          <\/div>\n\n          <h2>\n           A sovereign voice infrastructure\n         <\/h2>\n\n          <p>\n           A European infrastructure designed for businesses\n           that want to integrate telephony without building\n           their own network.\n         <\/p> \n        <\/div>\n\n\n        <div class=\"infrastructure-grid\">\n\n           <article>\n            <h3>European operator<\/h3>\n            <p>\n             Telecom infrastructure operated directly by Kavkom.\n           <\/p>\n          <\/article>\n\n          <article>\n            <h3>GDPR<\/h3>\n            <p>\n             Data hosted in Europe.\n           <\/p>\n          <\/article> \n\n           <article>\n            <h3>High Availability<\/h3>\n            <p>\n             Infrastructure designed to ensure\n             the continuity of your communications.\n           <\/p>\n          <\/article>\n\n          <article>\n            <h3>French-speaking support<\/h3>\n            <p>\n             A team available to assist with your onboarding.\n           <\/p>\n          <\/article> \n\n        <\/div>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- TARIFS -->\n    <section id=\"tarifs\" class=\"pricing-section\">\n\n      <div class=\"kavkom-container\">\n\n        <div class=\"section-heading\">\n           <div class=\"eyebrow\">\n          \n Rates          <\/div>\n\n          <h2>\n           A customized rate plan, tailored to your call volume\n         <\/h2>\n\n          <p>\n           Choose the plan that best suits your needs\n           and your call volume.\n         <\/p> \n        <\/div>\n\n\n        <div class=\"pricing-grid\">\n\n          <article class=\"pricing-card\">\n            <span class=\"pricing-number\">01<\/span>\n\n            <h3>\n             Per second\n           <\/h3>\n\n            <p>\n             You pay for the actual duration of your calls,\n             with no rounding up to the nearest minute.\n           <\/p> \n          <\/article>\n\n\n          <article class=\"pricing-card pricing-card-highlight\">\n            <span class=\"pricing-number\">02<\/span>\n\n            <h3>\n            \n Unlimited Plan            <\/h3>\n\n            <p>\n             Unlimited calls, fixed price:\n             a predictable bill, with no surprises.\n           <\/p> \n          <\/article>\n\n\n          <article class=\"pricing-card\">\n            <span class=\"pricing-number\">03<\/span>\n\n            <h3>\n             No commitment\n           <\/h3>\n\n            <p>\n             Billed on a pro-rata basis, cancellable,\n             no hidden fees.\n           <\/p> \n          <\/article>\n\n        <\/div>\n\n\n        <div class=\"pricing-cta\">\n          <a href=\"#formulaire\" class=\"btn btn-primary\">\n             Get a personalized quote\n          <\/a>\n        <\/div>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- FAQ -->\n    <section id=\"faq\" class=\"faq-section kavkom-container\">\n\n      <div class=\"faq-intro\">\n\n         <div class=\"eyebrow\">\n        \n FAQ        <\/div>\n\n        <h2>\n         Frequently Asked Questions\n       <\/h2>\n\n        <p>\n         The basics before we talk.\n       <\/p> \n\n      <\/div>\n\n\n      <div class=\"faq-list\">\n\n\n        <div class=\"faq-item\">\n           <button class=\"faq-question\" type=\"button\">\n            <span>Can I keep my current phone numbers?<\/span>\n            <span class=\"faq-icon\">+<\/span>\n          <\/button>\n\n          <div class=\"faq-answer\">\n            <p>\n             Yes. We\u2019ll port your numbers without any service interruption.\n           <\/p>\n          <\/div> \n        <\/div>\n\n\n        <div class=\"faq-item\">\n           <button class=\"faq-question\" type=\"button\">\n            <span>Is there a minimum contract term?<\/span>\n            <span class=\"faq-icon\">+<\/span>\n          <\/button>\n\n          <div class=\"faq-answer\">\n            <p>\n             No. The service has no contract and no hidden fees.\n           <\/p>\n          <\/div> \n        <\/div>\n\n\n        <div class=\"faq-item\">\n           <button class=\"faq-question\" type=\"button\">\n            <span>What does the free audit include?<\/span>\n            <span class=\"faq-icon\">+<\/span>\n          <\/button>\n\n          <div class=\"faq-answer\">\n            <p>\n             A detailed comparison of your current setup\n             with Kavkom.\n           <\/p>\n          <\/div> \n        <\/div>\n\n\n        <div class=\"faq-item\">\n           <button class=\"faq-question\" type=\"button\">\n            <span>Should we replace our phone system?<\/span>\n            <span class=\"faq-icon\">+<\/span>\n          <\/button>\n\n          <div class=\"faq-answer\">\n            <p>\n             No. Using the API or a SIP trunk, we integrate with your existing system:\n             IPBX, softphone, or directly into your application.\n           <\/p>\n          <\/div> \n        <\/div>\n\n\n        <div class=\"faq-item\">\n           <button class=\"faq-question\" type=\"button\">\n            <span>How long does it take to settle in?<\/span>\n            <span class=\"faq-icon\">+<\/span>\n          <\/button>\n\n          <div class=\"faq-answer\">\n            <p>\n             The initial setup takes just a few minutes.\n             Full integration depends on the scope of your project,\n             and our team is here to support you.\n           <\/p>\n          <\/div> \n        <\/div>\n\n\n        <div class=\"faq-item\">\n           <button class=\"faq-question\" type=\"button\">\n            <span>In which countries do you have phone numbers?<\/span>\n            <span class=\"faq-icon\">+<\/span>\n          <\/button>\n\n          <div class=\"faq-answer\">\n            <p>\n             Local DID numbers in many countries,\n             and outgoing calls to destinations worldwide.\n           <\/p>\n          <\/div> \n        <\/div>\n\n\n        <div class=\"faq-item\">\n           <button class=\"faq-question\" type=\"button\">\n            <span>How are calls billed?<\/span>\n            <span class=\"faq-icon\">+<\/span>\n          <\/button>\n\n          <div class=\"faq-answer\">\n            <p>\n             By the second or as an unlimited plan,\n             depending on your usage\u2014no commitment required.\n           <\/p>\n          <\/div> \n        <\/div>\n\n\n        <div class=\"faq-item\">\n           <button class=\"faq-question\" type=\"button\">\n            <span>Are calls recorded?<\/span>\n            <span class=\"faq-icon\">+<\/span>\n          <\/button>\n\n          <div class=\"faq-answer\">\n            <p>\n             Yes, automatically, with a unique identifier\n             and a configurable retention period.\n           <\/p>\n          <\/div> \n        <\/div>\n\n\n        <div class=\"faq-item\">\n           <button class=\"faq-question\" type=\"button\">\n            <span>Do you offer private-label products?<\/span>\n            <span class=\"faq-icon\">+<\/span>\n          <\/button>\n\n          <div class=\"faq-answer\">\n            <p>\n             Yes: a reseller and white-label program\n             is available for integrators and software publishers.\n           <\/p>\n          <\/div> \n        <\/div>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- CONTACT -->\n    <section id=\"contact\" class=\"contact-section\">\n\n      <div class=\"kavkom-container\">\n\n        <div class=\"contact-heading\">\n\n           <div class=\"eyebrow\">\n           Let's talk about your project\n         <\/div>\n\n          <h2>\n           Get a quote tailored to your volume\n         <\/h2>\n\n          <p>\n           Contact us: we\u2019ll get back to you\n <strong>           within 24 hours<\/strong>\n           with a quote tailored to your volume\n           and a trial account.\n         <\/p> \n\n        <\/div>\n\n\n        <!-- FORMULAIRE HUBSPOT -->\n        <div class=\"contact-form-wrapper\">\n\n          <div id=\"formulaire\" class=\"contact-form\">\n\n            <div class=\"form-label\">\n               CONTACT FORM\n            <\/div>\n\n            <div id=\"kvklp26-hsform-main\"><\/div>\n\n\n            <div class=\"form-benefits\">\n\n               <span>\n               \u2713 Quick response\n             <\/span>\n\n              <span>\n               \u2713 No obligation\n             <\/span>\n\n              <span>\n               \u2713 Customized assessment\n             <\/span> \n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n\n        <!-- CONTACT LINKS -->\n        <div class=\"contact-links\">\n\n           <a href=\"https:\/\/kavkom.com\/en\/\" class=\"contact-link\">\n            <span>WEBSITE<\/span>\n            <strong>kavkom.com<\/strong>\n          <\/a>\n\n          <a href=\"tel:+33178902698\" class=\"contact-link\">\n            <span>PHONE<\/span>\n            <strong>+33 1 78 90 26 98<\/strong>\n          <\/a> \n\n           <a href=\"mailto:contact@kavkom.com\" class=\"contact-link\">\n            <span>EMAIL<\/span>\n            <strong>contact@kavkom.com<\/strong>\n          <\/a>\n\n          <a href=\"https:\/\/www.linkedin.com\/company\/kavkom\" class=\"contact-link\" target=\"_blank\" rel=\"noopener\">\n            <span>LINKEDIN<\/span>\n            <strong>\/company\/kavkom<\/strong>\n          <\/a> \n\n        <\/div>\n\n\n        <!-- FOOTER -->\n        <footer class=\"kavkom-footer\">\n\n          <img decoding=\"async\" src=\"https:\/\/kavkom.com\/wp-content\/uploads\/2023\/09\/Kavkom-logo-white-footer.webp\" alt=\"Kavkom\"\/>\n\n           <span>\n           The telephony component of your SaaS.\n         <\/span>\n\n          <div class=\"footer-badges\">\n            <span>European carrier \u00b7 GDPR<\/span>\n            <span>No commitment<\/span>\n          <\/div> \n\n        <\/footer>\n\n      <\/div>\n\n    <\/section>\n\n\n    <!-- CTA FLOTTANT -->\n    <div class=\"sticky-demo-cta\">\n\n      <a href=\"#formulaire\" class=\"btn btn-primary\">\n         Schedule My Demo\n      <\/a>\n\n    <\/div>\n\n  <\/main>\n\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Features Webhooks SIP Trunk Quick Start Pricing FAQ Schedule a demo Voice &#038; Telephony API \u2014 for SaaS providers The telephony module of your product, in a few lines. Phone numbers, outbound calls, click-to-call, webphone, recording and real-time events. You build the experience; we run the network. Discover Kavkom with a personalized demo Ask an [&hellip;]<\/p>\n","protected":false},"author":15,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"elementor_canvas","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[199],"tags":[],"class_list":["post-70822","post","type-post","status-publish","format-standard","hentry","category-api"],"acf":[],"_links":{"self":[{"href":"https:\/\/kavkom.com\/en\/wp-json\/wp\/v2\/posts\/70822","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kavkom.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kavkom.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kavkom.com\/en\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/kavkom.com\/en\/wp-json\/wp\/v2\/comments?post=70822"}],"version-history":[{"count":2,"href":"https:\/\/kavkom.com\/en\/wp-json\/wp\/v2\/posts\/70822\/revisions"}],"predecessor-version":[{"id":70827,"href":"https:\/\/kavkom.com\/en\/wp-json\/wp\/v2\/posts\/70822\/revisions\/70827"}],"wp:attachment":[{"href":"https:\/\/kavkom.com\/en\/wp-json\/wp\/v2\/media?parent=70822"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kavkom.com\/en\/wp-json\/wp\/v2\/categories?post=70822"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kavkom.com\/en\/wp-json\/wp\/v2\/tags?post=70822"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}