src/ApplicationBundle/Modules/HoneybeeWeb/Resources/views/pages/pricing.html.twig line 1

Open in your IDE?
  1. {% include '@Application/inc/central_header.html.twig' %}
  2. <style>
  3. /* ── Design Tokens ─────────────────────────────────────────────────────────── */
  4. :root {
  5.     --n-cream:     #F7F5F0;
  6.     --n-cream-2:   #F0EDE5;
  7.     --n-white:     #FFFFFF;
  8.     --n-dark:      #1A1D2E;
  9.     --n-dark-2:    #252840;
  10.     --n-amber:     #C07D2A;
  11.     --n-amber-lt:  #D4954A;
  12.     --n-amber-dim: rgba(192,125,42,.10);
  13.     --n-sage:      #3D6B52;
  14.     --n-sage-dim:  rgba(61,107,82,.09);
  15.     --n-muted:     #6B6E7F;
  16.     --n-muted-2:   #9395A5;
  17.     --n-border:    rgba(26,29,46,.07);
  18.     --n-border-md: rgba(26,29,46,.12);
  19.     --n-shadow-sm: 0 2px 12px rgba(26,29,46,.07);
  20.     --n-shadow-md: 0 8px 32px rgba(26,29,46,.09);
  21.     --n-shadow-lg: 0 20px 64px rgba(26,29,46,.10);
  22.     --n-radius:    14px;
  23.     --n-radius-sm: 8px;
  24.     --n-font:      'DM Sans', 'Poppins', system-ui, sans-serif;
  25. }
  26. *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
  27. body { background: var(--n-cream); font-family: var(--n-font); color: var(--n-dark); text-align: left; }
  28. a { text-decoration: none; }
  29. /* ── Navbar ─────────────────────────────────────────────────────────────────── */
  30. .navbar {
  31.     background: rgba(247,245,240,.96) !important;
  32.     backdrop-filter: blur(16px) saturate(180%);
  33.     border-bottom: 1px solid var(--n-border) !important;
  34.     box-shadow: none !important;
  35. }
  36. .navbar .nav-link        { color: var(--n-dark) !important; font-size: 13.5px; font-weight: 500; opacity: .8; }
  37. .navbar .nav-link:hover  { opacity: 1; color: var(--n-dark) !important; }
  38. .navbar .login-btn       { background: var(--n-dark) !important; color: #fff !important; border: none !important; border-radius: 8px !important; padding: 8px 20px !important; font-size: 13px !important; font-weight: 600 !important; }
  39. /* ── Utilities ──────────────────────────────────────────────────────────────── */
  40. .n-wrap    { max-width: 1100px; margin: 0 auto; padding: 0 28px; }
  41. .n-wrap-sm { max-width: 820px;  margin: 0 auto; padding: 0 28px; }
  42. .n-eyebrow {
  43.     display: inline-flex; align-items: center; gap: 8px;
  44.     font-size: 11px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
  45.     color: var(--n-amber); margin-bottom: 16px;
  46. }
  47. .n-eyebrow::before { content:''; width: 18px; height: 1.5px; background: currentColor; border-radius: 2px; }
  48. .n-center { text-align: center; }
  49. .n-center .n-eyebrow { justify-content: center; }
  50. /* ── Hero ────────────────────────────────────────────────────────────────────── */
  51. .n-pricing-hero {
  52.     background: var(--n-cream); padding: 100px 0 64px;
  53.     border-bottom: 1px solid var(--n-border);
  54. }
  55. .n-pricing-hero h1 {
  56.     font-family: 'Montserrat', sans-serif;
  57.     font-size: clamp(2rem, 4.5vw, 3.2rem); font-weight: 900;
  58.     color: var(--n-dark); margin: 0 0 18px; letter-spacing: -.028em; line-height: 1.07;
  59.     max-width: 680px;
  60. }
  61. .n-pricing-hero h1 em { font-style: italic; color: var(--n-amber); }
  62. .n-pricing-hero .n-lead {
  63.     font-size: 1.05rem; color: var(--n-muted); max-width: 620px; line-height: 1.75; margin-bottom: 28px;
  64. }
  65. /* Trigger note box */
  66. .n-trigger-note {
  67.     background: var(--n-amber-dim); border: 1px solid rgba(192,125,42,.22);
  68.     border-radius: 10px; padding: 18px 22px; margin-top: 8px;
  69.     display: flex; gap: 14px; align-items: flex-start; max-width: 700px;
  70. }
  71. .n-trigger-icon {
  72.     font-family: monospace; font-size: .85rem; font-weight: 600;
  73.     color: var(--n-amber); flex-shrink: 0; margin-top: 1px;
  74. }
  75. .n-trigger-note p { font-size: .9rem; color: var(--n-dark); margin: 0; line-height: 1.6; }
  76. .n-trigger-note strong { font-weight: 700; }
  77. /* ── Pricing cards ───────────────────────────────────────────────────────────── */
  78. .n-pricing-section { background: var(--n-white); padding: 68px 0 56px; }
  79. .n-pricing-grid {
  80.     display: grid;
  81.     grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  82.     gap: 20px; max-width: 1100px; margin: 0 auto; padding: 0 28px;
  83.     align-items: stretch;   /* equal height across all cards */
  84. }
  85. .n-pricing-card {
  86.     background: var(--n-cream); border: 1.5px solid var(--n-border-md);
  87.     border-radius: var(--n-radius); padding: 30px 26px;
  88.     display: flex; flex-direction: column; gap: 0;
  89.     height: 100%;           /* fill full row height */
  90.     transition: box-shadow .25s, transform .2s, border-color .2s;
  91.     cursor: pointer; position: relative; overflow: visible;
  92. }
  93. .n-pricing-card:hover {
  94.     border-color: rgba(192,125,42,.35);
  95.     box-shadow: var(--n-shadow-md); transform: translateY(-3px);
  96. }
  97. .n-pricing-card.active-card {
  98.     border-color: var(--n-amber);
  99.     box-shadow: 0 8px 40px rgba(192,125,42,.18);
  100.     transform: translateY(-4px);
  101. }
  102. .n-pricing-card.active-card::before {
  103.     content: '✓ Selected'; position: absolute; top: -12px; right: 18px;
  104.     font-size: .68rem; font-weight: 700; background: var(--n-amber); color: #fff;
  105.     padding: 3px 12px; border-radius: 20px; letter-spacing: .06em; text-transform: uppercase;
  106. }
  107. /* Free plan — forest green accent */
  108. .n-pricing-card.n-plan-free {
  109.     border-color: rgba(61,107,82,.3);
  110.     background: rgba(61,107,82,.03);
  111. }
  112. .n-pricing-card.n-plan-free:hover { border-color: var(--n-sage); box-shadow: 0 8px 32px rgba(61,107,82,.12); }
  113. .n-pricing-card.n-plan-free.active-card { border-color: var(--n-sage); box-shadow: 0 8px 40px rgba(61,107,82,.2); }
  114. .n-pricing-card.n-plan-free.active-card::before { background: var(--n-sage); }
  115. /* Featured (dark) plan */
  116. .n-pricing-card.n-plan-featured {
  117.     background: var(--n-dark); border-color: transparent;
  118.     box-shadow: var(--n-shadow-lg);
  119.     /* no margin-top — equal height grid handles elevation via box-shadow only */
  120. }
  121. .n-pricing-card.n-plan-featured:hover  { border-color: var(--n-amber); transform: translateY(-6px); }
  122. .n-pricing-card.n-plan-featured.active-card { border-color: var(--n-amber); transform: translateY(-8px); box-shadow: 0 16px 56px rgba(192,125,42,.25); }
  123. .n-pricing-card.n-plan-featured .n-pc-name  { color: rgba(255,255,255,.9); }
  124. .n-pricing-card.n-plan-featured .n-pc-badge { color: rgba(255,255,255,.45); }
  125. .n-pricing-card.n-plan-featured .n-pc-price { color: #fff; }
  126. .n-pricing-card.n-plan-featured .n-pc-price small  { color: rgba(255,255,255,.45); }
  127. .n-pricing-card.n-plan-featured .n-pc-price-sec    { color: rgba(255,255,255,.45); }
  128. .n-pricing-card.n-plan-featured .n-pc-billing      { color: rgba(255,255,255,.4); }
  129. .n-pricing-card.n-plan-featured .n-pc-divider      { border-color: rgba(255,255,255,.09); }
  130. .n-pricing-card.n-plan-featured .n-pc-features li  { color: rgba(255,255,255,.78); border-bottom-color: rgba(255,255,255,.08); }
  131. .n-pricing-card.n-plan-featured .n-pc-features li::before { border-color: var(--n-amber); background: radial-gradient(circle, var(--n-amber) 30%, transparent 32%); }
  132. .n-pricing-card.n-plan-featured .n-pc-btn { background: var(--n-amber); color: #fff; }
  133. .n-pricing-card.n-plan-featured .n-pc-btn:hover { background: var(--n-amber-lt); }
  134. /* plan-tag pill */
  135. .n-plan-tag {
  136.     position: absolute; top: 16px; right: 16px;
  137.     font-family: monospace; font-size: .62rem; letter-spacing: .08em; text-transform: uppercase;
  138.     padding: 4px 10px; border-radius: 100px; font-weight: 700;
  139. }
  140. .n-plan-tag.popular { color: var(--n-amber); background: var(--n-amber-dim); }
  141. .n-plan-tag.free-tag { color: var(--n-sage); background: var(--n-sage-dim); }
  142. .n-pc-name {
  143.     font-family: 'Montserrat', sans-serif; font-size: 1.05rem; font-weight: 800;
  144.     color: var(--n-dark); margin: 0 0 5px;
  145. }
  146. .n-pc-badge { font-size: .82rem; color: var(--n-muted); margin-bottom: 20px; line-height: 1.45; min-height: 2.4em; }
  147. .n-pc-price {
  148.     font-family: 'Montserrat', sans-serif; font-size: 2.6rem; font-weight: 900;
  149.     color: var(--n-dark); line-height: 1; display: flex; align-items: baseline; gap: 6px;
  150. }
  151. .n-pc-price small  { font-size: .85rem; font-weight: 400; color: var(--n-muted); font-family: var(--n-font); }
  152. .n-pc-price-sec    { font-family: monospace; font-size: .78rem; color: var(--n-muted); margin-top: 6px; }
  153. .n-pc-billing      { font-size: .78rem; color: var(--n-muted); margin-top: 6px; margin-bottom: 20px; }
  154. /* ── Early-adopter pricing ── */
  155. .n-pc-regular-price {
  156.     font-family: monospace; font-size: .78rem; color: var(--n-muted-2);
  157.     text-decoration: line-through; margin-bottom: 4px; opacity: .75;
  158. }
  159. .n-pc-early-badge {
  160.     display: inline-flex; align-items: center; gap: 5px;
  161.     font-size: .65rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  162.     color: var(--n-amber); background: var(--n-amber-dim);
  163.     border: 1px solid rgba(192,125,42,.25); border-radius: 100px;
  164.     padding: 3px 9px; margin-bottom: 8px;
  165. }
  166. .n-pc-early-badge::before { content: '⚡'; font-size: .7rem; }
  167. .n-pc-lock-note {
  168.     font-size: .72rem; color: var(--n-amber); margin-top: 5px; margin-bottom: 18px;
  169.     display: flex; align-items: flex-start; gap: 5px; line-height: 1.45;
  170. }
  171. .n-pc-lock-note::before { content: '🔒'; font-size: .72rem; flex-shrink: 0; }
  172. /* Dark-card overrides */
  173. .n-plan-featured .n-pc-early-badge { color: var(--n-amber); background: rgba(192,125,42,.18); border-color: rgba(192,125,42,.35); }
  174. .n-plan-featured .n-pc-regular-price { color: rgba(255,255,255,.35); }
  175. .n-plan-featured .n-pc-lock-note { color: rgba(192,125,42,.85); }
  176. .n-pc-divider      { border: none; border-top: 1.5px solid var(--n-border); margin: 4px 0 16px; }
  177. .n-pc-features     { list-style: none; margin: 0 0 24px; padding: 0; flex: 1; }
  178. .n-pc-features li  {
  179.     padding: 9px 0; font-size: .88rem; color: var(--n-muted);
  180.     display: flex; gap: 10px; align-items: flex-start;
  181.     border-bottom: 1px solid var(--n-border); line-height: 1.45;
  182. }
  183. .n-pc-features li:last-child { border-bottom: none; }
  184. .n-pc-features li::before {
  185.     content: ''; width: 13px; height: 13px; border: 1.5px solid var(--n-amber);
  186.     border-radius: 50%; flex-shrink: 0; margin-top: 3px;
  187.     background: radial-gradient(circle, var(--n-amber) 30%, transparent 32%);
  188. }
  189. .n-plan-free .n-pc-features li::before {
  190.     border-color: var(--n-sage);
  191.     background: radial-gradient(circle, var(--n-sage) 30%, transparent 32%);
  192. }
  193. .n-pc-btn {
  194.     margin-top: auto; padding: 12px 20px; width: 100%;
  195.     background: var(--n-dark); color: #fff; border: none;
  196.     border-radius: var(--n-radius-sm); font-family: 'DM Sans', sans-serif;
  197.     font-size: .9rem; font-weight: 700; cursor: pointer; transition: background .2s; text-align: center;
  198. }
  199. .n-pc-btn:hover { background: var(--n-amber); }
  200. .n-plan-free .n-pc-btn { background: var(--n-sage); }
  201. .n-plan-free .n-pc-btn:hover { background: #4f8866; }
  202. /* ── Plan Summary Cart ───────────────────────────────────────────────────────── */
  203. .n-plan-summary-wrap { max-width: 560px; margin: 52px auto 0; padding: 0 24px; display: none; }
  204. .n-plan-summary-wrap.visible { display: block; background: #f7f5f0; padding-bottom: 72px; }
  205. .n-plan-summary {
  206.     border: 1.5px solid var(--n-border-md); border-radius: 16px;
  207.     padding: 28px; background: #fff; box-shadow: var(--n-shadow-md);
  208. }
  209. .n-ps-header {
  210.     display: flex; align-items: center; justify-content: space-between;
  211.     border-bottom: 1.5px solid var(--n-border); padding-bottom: 14px; margin-bottom: 20px;
  212. }
  213. .n-ps-header h4 { font-family: 'Montserrat', sans-serif; font-size: 1.05rem; font-weight: 700; color: var(--n-dark); margin: 0; }
  214. .n-ps-name  { font-size: 1.15rem; font-weight: 700; color: var(--n-dark); }
  215. .n-ps-price { font-size: 1rem; font-weight: 600; color: var(--n-amber); margin-top: 4px; }
  216. .n-ps-row   { display: flex; justify-content: space-between; margin-bottom: 18px; }
  217. .n-ps-col small    { font-size: .78rem; color: var(--n-muted); display: block; margin-bottom: 4px; }
  218. .n-ps-col strong   { font-size: .95rem; color: var(--n-dark); }
  219. .n-ps-label        { font-size: .78rem; color: var(--n-muted); margin: 0 0 8px; }
  220. .n-counter-row     { display: flex; gap: 16px; margin-bottom: 20px; flex-wrap: wrap; }
  221. .n-counter         { flex: 1; min-width: 140px; }
  222. .n-counter label   { display: block; font-size: .78rem; color: var(--n-muted); margin-bottom: 6px; }
  223. .n-counter-ctrl    {
  224.     display: flex; align-items: center; justify-content: space-between;
  225.     border: 1.5px solid var(--n-border-md); border-radius: 8px; padding: 6px 12px; gap: 10px;
  226. }
  227. .n-counter-ctrl button { width: 32px; height: 32px; border: none; background: var(--n-cream); border-radius: 6px; font-size: 1rem; cursor: pointer; transition: background .15s; }
  228. .n-counter-ctrl button:hover    { background: #e6e3db; }
  229. .n-counter-ctrl button:disabled { opacity: .4; cursor: not-allowed; }
  230. .n-counter-ctrl span { font-weight: 700; font-size: 1rem; color: var(--n-dark); flex: 1; text-align: center; }
  231. .n-ps-total-row { display: flex; justify-content: space-between; align-items: center; border-top: 1.5px solid var(--n-border); padding-top: 16px; margin-top: 4px; }
  232. .n-ps-total-label { font-weight: 600; color: var(--n-dark); }
  233. .n-ps-total-val   { font-size: 1.2rem; font-weight: 800; color: var(--n-amber); }
  234. .n-pay-btn { width: 100%; margin-top: 18px; padding: 13px; background: var(--n-amber); color: #fff; border: none; border-radius: 10px; font-family: 'DM Sans', sans-serif; font-size: 1rem; font-weight: 700; cursor: pointer; transition: background .2s, transform .15s; }
  235. .n-pay-btn:hover    { background: #a86d24; transform: translateY(-1px); }
  236. .n-pay-btn:disabled { opacity: .55; cursor: not-allowed; transform: none; }
  237. /* ── Calculator section ──────────────────────────────────────────────────────── */
  238. .n-calc-section { padding: 0     0 0; }
  239. .n-calc-box {
  240.     padding: 48px; background: var(--n-white);
  241.     border: 1px solid var(--n-border-md); border-radius: 16px;
  242. }
  243. .n-calc-head { max-width: 660px; }
  244. .n-calc-head h2 {
  245.     font-family: 'Montserrat', sans-serif;
  246.     font-size: clamp(1.7rem, 3.2vw, 2.6rem); font-weight: 900;
  247.     color: var(--n-dark); letter-spacing: -.03em; line-height: 1.1; margin-bottom: 14px;
  248. }
  249. .n-calc-head h2 em { font-style: italic; color: var(--n-amber); }
  250. .n-calc-head p { color: var(--n-muted); font-size: .95rem; line-height: 1.72; }
  251. .n-calc-toggle-row {
  252.     margin-top: 24px; display: flex; align-items: center; gap: 14px;
  253. }
  254. .n-calc-toggle-btn {
  255.     display: inline-flex; align-items: center; gap: 7px;
  256.     padding: 10px 22px; background: var(--n-dark); color: #fff;
  257.     border: none; border-radius: var(--n-radius-sm);
  258.     font-family: 'DM Sans', sans-serif; font-size: .88rem; font-weight: 600;
  259.     cursor: pointer; transition: background .2s;
  260. }
  261. .n-calc-toggle-btn:hover { background: var(--n-amber); }
  262. .n-calc-toggle-btn .arrow { transition: transform .25s; display: inline-block; }
  263. .n-calc-toggle-btn.open .arrow { transform: rotate(180deg); }
  264. .n-calc-body { display: block !important; overflow: visible !important; opacity: 1 !important; max-height: none !important; visibility: visible !important; }
  265. .n-calc-table-wrap { overflow-x: auto; margin-top: 32px; }
  266. .n-calc-table {
  267.     width: 100%; border-collapse: collapse; min-width: 600px;
  268. }
  269. .n-calc-table thead th {
  270.     text-align: left; padding: 13px 16px;
  271.     background: var(--n-cream-2); font-family: monospace; font-weight: 500;
  272.     font-size: .72rem; letter-spacing: .08em; text-transform: uppercase;
  273.     color: var(--n-muted); border-bottom: 1px solid var(--n-border-md);
  274. }
  275. .n-calc-table thead th:not(:first-child) { text-align: right; }
  276. .n-calc-table tbody tr { border-bottom: 1px solid var(--n-border); }
  277. .n-calc-table tbody tr:last-child { border-bottom: none; }
  278. .n-calc-table td { padding: 14px 16px; font-size: .93rem; color: var(--n-muted); }
  279. .n-calc-table td:not(:first-child) { text-align: right; }
  280. .n-calc-table td.n-total   { font-family: 'Montserrat', sans-serif; font-size: 1.25rem; font-weight: 900; color: var(--n-dark); }
  281. .n-calc-table td.n-blended { font-family: monospace; font-size: .82rem; color: var(--n-amber); font-weight: 700; }
  282. .n-admin-explainer {
  283.     margin-top: 24px; padding: 18px 22px; background: var(--n-cream);
  284.     border: 1px solid var(--n-border-md); border-radius: 10px;
  285.     display: flex; gap: 16px; align-items: flex-start;
  286. }
  287. .n-admin-explainer .marker {
  288.     font-family: monospace; font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
  289.     color: var(--n-amber); flex-shrink: 0; padding-top: 1px; font-weight: 700; white-space: nowrap;
  290. }
  291. .n-admin-explainer p { font-size: .88rem; color: var(--n-muted); margin: 0; line-height: 1.65; }
  292. .n-admin-explainer strong { color: var(--n-dark); font-weight: 600; }
  293. /* ── Comparison table ────────────────────────────────────────────────────────── */
  294. .n-compare-section { padding: 0 0 80px; }
  295. .n-compare-head { margin-bottom: 28px; }
  296. .n-compare-head h2 { font-family: 'Montserrat', sans-serif; font-size: clamp(1.4rem,2.5vw,2rem); font-weight: 800; color: var(--n-dark); letter-spacing: -.02em; }
  297. .n-compare-wrap {
  298.     border: 1px solid var(--n-border-md); border-radius: 14px;
  299.     overflow-x: auto;   /* scroll on small screens, no height collapse */
  300.     background: #ffffff;
  301. }
  302. /* Force table to render — project-wide CSS may override generic `table` selectors */
  303. .n-cmp-table {
  304.     display: table !important;
  305.     width: 100% !important;
  306.     border-collapse: collapse !important;
  307.     table-layout: auto !important;
  308.     visibility: visible !important;
  309.     min-width: 600px;
  310. }
  311. .n-cmp-table thead  { display: table-header-group !important; visibility: visible !important; }
  312. .n-cmp-table tbody  { display: table-row-group    !important; visibility: visible !important; }
  313. .n-cmp-table tr     { display: table-row          !important; visibility: visible !important; border-bottom: 1px solid rgba(26,29,46,.07); }
  314. .n-cmp-table tr:last-child { border-bottom: none !important; }
  315. .n-cmp-table th,
  316. .n-cmp-table td     { display: table-cell         !important; visibility: visible !important; }
  317. /* thead cells */
  318. .n-cmp-table thead th {
  319.     text-align: left !important; padding: 18px 22px !important;
  320.     background: #F0EDE5 !important; font-family: var(--n-font) !important;
  321.     font-weight: 700 !important; font-size: .9rem !important; color: #1A1D2E !important;
  322.     border-bottom: 1px solid rgba(26,29,46,.12) !important;
  323.     white-space: nowrap;
  324. }
  325. .n-cmp-table thead th:not(:first-child) { text-align: center !important; }
  326. /* plan-name+price header row */
  327. .n-cmp-table thead tr.n-plan-row th {
  328.     background: #ffffff !important; padding: 14px 22px 16px !important;
  329.     border-bottom: 2px solid rgba(26,29,46,.12) !important;
  330. }
  331. .n-cmp-table thead tr.n-plan-row th:first-child { background: #F0EDE5 !important; }
  332. /* section header rows in tbody */
  333. .n-cmp-table tbody tr.n-sh td {
  334.     background: #F7F5F0 !important; font-family: monospace !important;
  335.     font-size: .72rem !important; letter-spacing: .1em !important; text-transform: uppercase !important;
  336.     color: #C07D2A !important; padding: 12px 22px !important; font-weight: 700 !important;
  337.     border-bottom: 1px solid rgba(26,29,46,.12) !important;
  338. }
  339. /* regular tbody cells */
  340. .n-cmp-table tbody td {
  341.     padding: 13px 22px !important; font-size: .88rem !important; color: #6B6E7F !important;
  342. }
  343. .n-cmp-table tbody td:not(:first-child) { text-align: center !important; color: #1A1D2E !important; font-size: .9rem !important; }
  344. /* value indicators */
  345. .n-cmp-table .n-check   { color: #3D6B52 !important; font-weight: 700 !important; font-size: 1rem !important; }
  346. .n-cmp-table .n-dash    { color: #9395A5 !important; }
  347. .n-cmp-table .n-partial { color: #C07D2A !important; font-size: .82rem !important; font-weight: 600 !important; }
  348. /* Toggle button */
  349. .n-table-toggle { text-align: center; margin-bottom: 28px; }
  350. .n-table-toggle-btn {
  351.     padding: 11px 30px; background: var(--n-dark); color: #fff;
  352.     border: none; border-radius: 8px; font-family: 'DM Sans', sans-serif;
  353.     font-size: .9rem; font-weight: 600; cursor: pointer; transition: background .2s;
  354. }
  355. .n-table-toggle-btn:hover { background: var(--n-amber); }
  356. /* plan header cells content */
  357. .n-cmp-plan-name  { font-family: 'Montserrat', sans-serif; font-size: .88rem; font-weight: 800; color: #1A1D2E; }
  358. .n-cmp-plan-price { font-family: 'Montserrat', sans-serif; font-size: 1.35rem; font-weight: 900; color: #C07D2A; line-height: 1.1; margin: 3px 0; }
  359. .n-cmp-plan-sub   { font-size: .72rem; color: #9395A5; font-family: monospace; }
  360. /* ── FAQ ─────────────────────────────────────────────────────────────────────── */
  361. .n-faq-section { padding: 72px 0; border-top: 1px solid var(--n-border); }
  362. .n-faq-layout  { display: grid; grid-template-columns: 320px 1fr; gap: 64px; align-items: start; }
  363. .n-faq-sidebar h2 { font-family: 'Montserrat', sans-serif; font-size: clamp(1.4rem,2.2vw,1.9rem); font-weight: 800; color: var(--n-dark); letter-spacing: -.02em; margin-bottom: 12px; }
  364. .n-faq-sidebar h2 em { font-style: italic; color: var(--n-amber); }
  365. .n-faq-sidebar p { font-size: .9rem; color: var(--n-muted); line-height: 1.65; }
  366. .n-faq-sidebar a { color: var(--n-amber); border-bottom: 1px solid rgba(192,125,42,.4); }
  367. .n-faq-list    { display: flex; flex-direction: column; gap: 0; }
  368. .n-faq-item    { border-bottom: 1px solid var(--n-border-md); }
  369. details.n-faq-item summary {
  370.     display: flex; justify-content: space-between; align-items: center;
  371.     padding: 18px 0; cursor: pointer; gap: 16px;
  372.     font-size: .93rem; font-weight: 600; color: var(--n-dark);
  373.     list-style: none; user-select: none;
  374. }
  375. details.n-faq-item summary::-webkit-details-marker { display: none; }
  376. .n-faq-icon {
  377.     width: 22px; height: 22px; border-radius: 50%;
  378.     background: var(--n-amber-dim); border: 1px solid rgba(192,125,42,.2);
  379.     display: flex; align-items: center; justify-content: center;
  380.     font-size: 14px; color: var(--n-amber); flex-shrink: 0;
  381.     transition: transform .2s;
  382. }
  383. details[open].n-faq-item .n-faq-icon { transform: rotate(45deg); }
  384. .n-faq-answer { padding: 0 0 18px; font-size: .88rem; color: var(--n-muted); line-height: 1.7; }
  385. /* ── CTA ─────────────────────────────────────────────────────────────────────── */
  386. .n-cta-final { padding: 96px 0; text-align: center; background: var(--n-cream-2); border-top: 1px solid var(--n-border); }
  387. .n-cta-final h2 { font-family: 'Montserrat', sans-serif; font-size: clamp(1.6rem,3vw,2.4rem); font-weight: 900; color: var(--n-dark); letter-spacing: -.025em; margin-bottom: 16px; }
  388. .n-cta-final h2 em { font-style: italic; color: var(--n-amber); }
  389. .n-cta-final p { max-width: 52ch; margin: 0 auto 32px; color: var(--n-muted); font-size: 1rem; line-height: 1.7; }
  390. .n-btn-primary { display: inline-flex; align-items: center; gap: 7px; padding: 12px 26px; background: var(--n-dark); color: #fff; border: none; border-radius: var(--n-radius-sm); font-size: .92rem; font-weight: 700; cursor: pointer; text-decoration: none; transition: background .18s, transform .18s; font-family: var(--n-font); }
  391. .n-btn-primary:hover { background: var(--n-amber); color: #fff; transform: translateY(-1px); }
  392. .n-btn-secondary { display: inline-flex; align-items: center; gap: 7px; padding: 12px 26px; background: transparent; color: var(--n-dark); border: 1.5px solid var(--n-border-md); border-radius: var(--n-radius-sm); font-size: .92rem; font-weight: 600; cursor: pointer; text-decoration: none; transition: all .18s; font-family: var(--n-font); }
  393. .n-btn-secondary:hover { border-color: var(--n-dark); transform: translateY(-1px); color: var(--n-dark); }
  394. /* ── Billing cycle toggle buttons ───────────────────────────────────────────── */
  395. .n-cycle-btn {
  396.     flex: 1; padding: 8px 10px; border: 1.5px solid var(--n-border-md);
  397.     border-radius: 8px; background: var(--n-cream); color: var(--n-muted);
  398.     font-family: var(--n-font); font-size: .82rem; font-weight: 600;
  399.     cursor: pointer; transition: all .18s; line-height: 1.3;
  400. }
  401. .n-cycle-btn.active {
  402.     background: var(--n-dark); color: #fff; border-color: var(--n-dark);
  403. }
  404. .n-cycle-btn:hover:not(.active) { border-color: var(--n-amber); color: var(--n-amber); }
  405. /* ── Three-column counter row ────────────────────────────────────────────────── */
  406. .n-counter-row { flex-wrap: wrap; }
  407. #mlCounterWrap  { min-width: 100%; margin-top: 8px; }
  408. @media (min-width: 420px) { #mlCounterWrap { min-width: unset; flex: 1; margin-top: 0; } }
  409. /* ── Scroll reveal ───────────────────────────────────────────────────────────── */
  410. .reveal { opacity: 0; transform: translateY(22px); transition: opacity .5s ease, transform .5s ease; }
  411. .reveal.visible { opacity: 1; transform: none; }
  412. /* ── Responsive ──────────────────────────────────────────────────────────────── */
  413. @media (max-width: 980px) {
  414.     .n-calc-box { padding: 28px 20px; }
  415.     .n-faq-layout { grid-template-columns: 1fr; gap: 32px; }
  416. }
  417. @media (max-width: 680px) {
  418.     .n-pricing-grid { grid-template-columns: 1fr; }
  419.     .n-pricing-card.n-plan-featured { margin-top: 0; }
  420. }
  421. </style>
  422. <!-- ── HERO ─────────────────────────────────────────────────────────────────── -->
  423. <section class="n-pricing-hero">
  424.     <div class="n-wrap">
  425.         <span class="n-eyebrow trn" data-trn-key="_PRC_EYEBROW_">Pricing</span>
  426.         <h1><span class="trn" data-trn-key="_H_PRICING_H2_">Software subscription pricing for</span> <em class="n-em trn" data-trn-key="_H_PRICING_H2_EM_">industrial operations.</em></h1>
  427.         <p class="n-lead trn" data-trn-key="_PRC_LEAD_">Start free for up to 50 users. HoneyBee software runs your core business workflows. Edge+, IoT, hardware, and local AI deployments are scoped and quoted separately per project.</p>
  428.         <div style="display:flex;gap:16px;flex-wrap:wrap;margin-top:8px;margin-bottom:28px;">
  429.             <div style="flex:1;min-width:240px;background:rgba(61,107,82,.06);border:1px solid rgba(61,107,82,.2);border-radius:10px;padding:18px 22px;">
  430.                 <div style="font-size:.72rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#3D6B52;margin-bottom:6px">Option 1</div>
  431.                 <div style="font-weight:700;color:#1A1D2E;margin-bottom:4px;font-size:.95rem">Software subscription</div>
  432.                 <div style="font-size:.85rem;color:#6B6E7F;line-height:1.6">Start free — upgrade to Team when you're ready. No hardware required.</div>
  433.             </div>
  434.             <div style="flex:1;min-width:240px;background:rgba(192,125,42,.06);border:1px solid rgba(192,125,42,.2);border-radius:10px;padding:18px 22px;">
  435.                 <div style="font-size:.72rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#C07D2A;margin-bottom:6px">Option 2</div>
  436.                 <div style="font-weight:700;color:#1A1D2E;margin-bottom:4px;font-size:.95rem">Software + Edge+ deployment</div>
  437.                 <div style="font-size:.85rem;color:#6B6E7F;line-height:1.6">Add site data, IoT, and local infrastructure. Scoped and quoted per project separately.</div>
  438.             </div>
  439.         </div>
  440.         <div class="n-trigger-note">
  441.             <span class="n-trigger-icon">ⓘ</span>
  442.             <p>
  443.                 <strong class="trn" data-trn-key="_PRC_TRIGGER_TITLE_">The conversion trigger.</strong>
  444.                 <span class="trn" data-trn-key="_PRC_TRIGGER_DESC_">The first time any single user's storage passes 10 MB, the whole workspace moves to the Team plan. No surprise invoices — we notify you in advance, and you can delete data or upgrade on your terms.</span>
  445.                 <br><br>
  446.                 <strong style="color:var(--n-amber)">⚡ Early-adopter rate:</strong>
  447.                 <span>Any Free workspace that converts — voluntarily or via the storage trigger — is locked in at the launch price of <strong>€7.99/user · €19.99/admin</strong> permanently, even after the launch window closes.</span>
  448.             </p>
  449.         </div>
  450.     </div>
  451. </section>
  452. <!-- ── PRICING CARDS ─────────────────────────────────────────────────────────── -->
  453. <section class="n-pricing-section">
  454.     <div class="n-pricing-grid">
  455.         {% for packageDetail in packageDetails %}{% if packageDetail.hidden is not defined or not packageDetail.hidden %}
  456.             <div class="n-pricing-card pricing-card
  457.                 {% if packageDetail.freeFlag == '1' %}n-plan-free
  458.                 {% elseif packageDetail.featured is defined and packageDetail.featured %}n-plan-featured
  459.                 {% endif %}">
  460.                 {% if packageDetail.freeFlag == '1' %}
  461.                     <span class="n-plan-tag free-tag trn" data-trn-key="_H_PLAN_FREE_TAG_">Free forever*</span>
  462.                 {% elseif packageDetail.tag is defined and packageDetail.tag %}
  463.                     <span class="n-plan-tag popular">{{ packageDetail.tag }}</span>
  464.                 {% endif %}
  465.                 <p class="n-pc-name">{{ packageDetail.packageName }}</p>
  466.                 <p class="n-pc-badge">{{ packageDetail.description is defined and packageDetail.description ? packageDetail.description : packageDetail.badge }}</p>
  467.                 {% if packageDetail.freeFlag == '1' %}
  468.                     <div class="n-pc-price">€0 <small>/ forever</small></div>
  469.                     <div class="n-pc-price-sec">10 MB storage per user · up to {{ packageDetail.maxUser }} users</div>
  470.                     <div class="n-pc-billing">*Upgrades automatically once any user crosses 10 MB</div>
  471.                 {% elseif packageDetail.contactSalesFlag == '1' %}
  472.                     <div class="n-pc-price" style="font-size:2rem;">Custom</div>
  473.                     <div class="n-pc-price-sec">Quote based on scope</div>
  474.                     <div class="n-pc-billing">Includes development hours for your specific workflows</div>
  475.                 {% else %}
  476.                     {% if packageDetail.earlyAdopter is defined and packageDetail.earlyAdopter %}
  477.                         {# ── Early-adopter / launch offer pricing ── #}
  478.                         <div class="n-pc-early-badge">Launch offer</div>
  479.                         <div class="n-pc-regular-price">
  480.                             €{{ packageDetail.euRegularUserPrice.monthlyPrice }}/user · €{{ packageDetail.euRegularAdminPrice.monthlyPrice }}/admin
  481.                         </div>
  482.                         <div class="n-pc-price">
  483.                             €{{ packageDetail.euPerUserPrice.monthlyPrice }}
  484.                             <small>/ user / month</small>
  485.                         </div>
  486.                         <div class="n-pc-price-sec">+ €{{ packageDetail.euPerAdminPrice.monthlyPrice }} / admin / month</div>
  487.                         <div class="n-pc-lock-note">{{ packageDetail.earlyAdopterNote }}</div>
  488.                     {% else %}
  489.                         <div class="n-pc-price">
  490.                             €{{ packageDetail.euPerUserPrice.monthlyPrice }}
  491.                             <small>/ user / month</small>
  492.                         </div>
  493.                         <div class="n-pc-price-sec">+ €{{ packageDetail.euPerAdminPrice.monthlyPrice }} / admin / month</div>
  494.                         <div class="n-pc-billing">Structure: 4 users + 1 admin per block · unlimited storage</div>
  495.                     {% endif %}
  496.                 {% endif %}
  497.                 <hr class="n-pc-divider divider-first">
  498.                 <ul class="n-pc-features">
  499.                     {% for data in packageDetail.features %}
  500.                         <li>{{ data.feature }}</li>
  501.                     {% endfor %}
  502.                 </ul>
  503.                 {% if packageDetail.contactSalesFlag == '1' %}
  504.                     <button class="n-pc-btn btn btn-primary trn" style="background:var(--n-muted-2);color:#fff;" data-trn-key="_H_PLAN_CONTACT_">Contact Sales</button>
  505.                 {% elseif packageDetail.freeFlag == '1' %}
  506.                     <button class="n-pc-btn btn btn-primary trn" data-trn-key="_H_PLAN_START_FREE_">Start free</button>
  507.                 {% else %}
  508.                     <button class="n-pc-btn btn btn-primary">{{ packageDetail.planShortName }}</button>
  509.                 {% endif %}
  510.                 {# ── Hidden data fields (JS hooks — DO NOT REMOVE OR RENAME) ── #}
  511.                 <input type="hidden" class="maxAdminUser"    value="{{ packageDetail.maxAdmin }}">
  512.                 <input type="hidden" class="minAdminUser"    value="{{ packageDetail.minAdmin }}">
  513.                 <input type="hidden" class="maxUser"         value="{{ packageDetail.maxUser }}">
  514.                 <input type="hidden" class="minUser"         value="{{ packageDetail.minUser }}">
  515.                 <input type="hidden" class="perUserPrice"    value="{{ packageDetail.perUserPrice.monthlyPrice }}">
  516.                 <input type="hidden" class="perAdminPrice"   value="{{ packageDetail.perAdminPrice.monthlyPrice }}">
  517.                 <input type="hidden" class="contactSales"    value="{{ packageDetail.contactSalesFlag }}">
  518.                 <input type="hidden" class="packageId"       value="{{ packageDetail.id }}">
  519.                 {# EU price fields used by cart JS #}
  520.                 <input type="hidden" class="euBasePrice"       value="{% if packageDetail.euBasePrice is defined %}{{ packageDetail.euBasePrice.monthlyPrice }}{% else %}{{ packageDetail.basePrice.monthlyPrice }}{% endif %}">
  521.                 <input type="hidden" class="euPerUserPrice"   value="{% if packageDetail.euPerUserPrice is defined %}{{ packageDetail.euPerUserPrice.monthlyPrice }}{% else %}{{ packageDetail.perUserPrice.monthlyPrice }}{% endif %}">
  522.                 <input type="hidden" class="euPerAdminPrice"  value="{% if packageDetail.euPerAdminPrice is defined %}{{ packageDetail.euPerAdminPrice.monthlyPrice }}{% else %}{{ packageDetail.perAdminPrice.monthlyPrice }}{% endif %}">
  523.                 <input type="hidden" class="euPerMlUserPrice" value="{% if packageDetail.euPerMlUserPrice is defined %}{{ packageDetail.euPerMlUserPrice.monthlyPrice }}{% else %}0{% endif %}">
  524.                 <input type="hidden" class="minMlUser"        value="{{ packageDetail.minMlUser is defined ? packageDetail.minMlUser : '0' }}">
  525.                 <input type="hidden" class="maxMlUser"        value="{{ packageDetail.maxMlUser is defined ? packageDetail.maxMlUser : '0' }}">
  526.                 <input type="hidden" class="usersPerAdminBlock" value="{{ packageDetail.usersPerAdminBlock is defined ? packageDetail.usersPerAdminBlock : '0' }}">
  527.                 {% if packageDetail.earlyAdopter is defined and packageDetail.earlyAdopter %}
  528.                 <input type="hidden" class="earlyAdopterFlag" value="1">
  529.                 {% endif %}
  530.             </div>
  531.         {% endif %}{% endfor %}
  532.     </div>
  533.     {# Hardware note — sits inside the white section so no cream seam #}
  534.     <div class="n-wrap" style="padding-top:32px;padding-bottom:0">
  535.         <div style="background:rgba(192,125,42,.08);border:1px solid rgba(192,125,42,.22);border-radius:12px;padding:28px 32px;max-width:900px;margin:0 auto;">
  536.             <div style="font-size:.75rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#C07D2A;margin-bottom:10px">Industrial &amp; Hardware Deployments</div>
  537.             <p style="font-family:'DM Sans',sans-serif;font-size:.95rem;color:#6B6E7F;line-height:1.75;margin:0 0 14px">
  538.                 Edge+, IoT, and infrastructure deployments are scoped and quoted separately per project — not included in the software subscription.
  539.             </p>
  540.             <ul style="font-family:'DM Sans',sans-serif;font-size:.9rem;color:#6B6E7F;line-height:1.75;margin:0;padding-left:20px;">
  541.                 <li>Edge+ data acquisition devices and gateways</li>
  542.                 <li>Industrial meters, sensors, and PLC integrations</li>
  543.                 <li>Local server and GPU infrastructure for on-premise AI/ML</li>
  544.                 <li>Network setup, commissioning, and site deployment</li>
  545.                 <li>Payment by bank transfer via formal quotation and invoice</li>
  546.             </ul>
  547.         </div>
  548.         <div class="n-wrap" style="padding-top:28px;padding-bottom:0">
  549.             <div style="max-width:900px;margin:0 auto;">
  550.                 <div style="font-size:.72rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#C07D2A;margin-bottom:14px">Edge+ Deployment Types</div>
  551.                 <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;">
  552.                     <div style="background:#fff;border:1px solid rgba(26,29,46,.08);border-radius:10px;padding:16px 18px;">
  553.                         <div style="font-weight:700;font-size:.88rem;color:#1A1D2E;margin-bottom:4px">Energy monitoring &amp; billing</div>
  554.                         <div style="font-size:.82rem;color:#6B6E7F;line-height:1.55">Meters and sensors connected to billing, O&amp;M, and reporting workflows.</div>
  555.                     </div>
  556.                     <div style="background:#fff;border:1px solid rgba(26,29,46,.08);border-radius:10px;padding:16px 18px;">
  557.                         <div style="font-weight:700;font-size:.88rem;color:#1A1D2E;margin-bottom:4px">Site operations</div>
  558.                         <div style="font-size:.82rem;color:#6B6E7F;line-height:1.55">PLC, SCADA, and production data connected to the HoneyBee platform.</div>
  559.                     </div>
  560.                     <div style="background:#fff;border:1px solid rgba(26,29,46,.08);border-radius:10px;padding:16px 18px;">
  561.                         <div style="font-weight:700;font-size:.88rem;color:#1A1D2E;margin-bottom:4px">Mobile &amp; remote assets</div>
  562.                         <div style="font-size:.82rem;color:#6B6E7F;line-height:1.55">GPS, cellular, and satellite-connected assets feeding operational data.</div>
  563.                     </div>
  564.                     <div style="background:#fff;border:1px solid rgba(26,29,46,.08);border-radius:10px;padding:16px 18px;">
  565.                         <div style="font-weight:700;font-size:.88rem;color:#1A1D2E;margin-bottom:4px">Multi-site industrial</div>
  566.                         <div style="font-size:.82rem;color:#6B6E7F;line-height:1.55">Warehouse, factory, and process monitoring across distributed locations.</div>
  567.                     </div>
  568.                     <div style="background:#fff;border:1px solid rgba(26,29,46,.08);border-radius:10px;padding:16px 18px;">
  569.                         <div style="font-weight:700;font-size:.88rem;color:#1A1D2E;margin-bottom:4px">Local AI/ML inference</div>
  570.                         <div style="font-size:.82rem;color:#6B6E7F;line-height:1.55">Private GPU server and on-premise LLM — nothing leaves your building.</div>
  571.                     </div>
  572.                 </div>
  573.             </div>
  574.         </div>
  575.     </div>
  576. </section>
  577. <!-- ── PLAN SUMMARY CART ─────────────────────────────────────────────────────── -->
  578. <div id="selectedPlanSummary" class="n-plan-summary-wrap">
  579.     <div class="n-plan-summary">
  580.         <div class="n-ps-header"><h4>Configure Your Plan</h4></div>
  581.         <div id="selectedPlanName"  class="n-ps-name"></div>
  582.         <div id="selectedPlanPrice" class="n-ps-price"></div>
  583.         <div class="n-ps-row" style="margin-top:20px;">
  584.             <div class="n-ps-col"><small>Min Users</small><strong id="selectedPlanMinUser">—</strong></div>
  585.             <div class="n-ps-col"><small>Max Users</small><strong id="selectedPlanMaxUser">—</strong></div>
  586.         </div>
  587.         <div class="n-ps-row">
  588.             <div class="n-ps-col"><small>Min Admins</small><strong id="selectedPlanMinAdminUser">—</strong></div>
  589.             <div class="n-ps-col"><small>Max Admins</small><strong id="selectedPlanAdminUser">—</strong></div>
  590.         </div>
  591.         <p class="n-ps-label">Company</p>
  592.         <div style="margin-bottom:18px;" id="companyFieldWrap">
  593.             {% if session[UserConstants.USER_ID] is defined and companies is defined and companies|length > 0 %}
  594.                 {# ── Logged in with companies: standard selector ── #}
  595.                 <select id="companySelect" class="form-control" style="border-radius:8px;border:1.5px solid var(--n-border-md);font-family:'DM Sans',sans-serif;">
  596.                     <option value="0">Select a company</option>
  597.                     {% for data in companies %}
  598.                     <option value="{{ data.appId }}">{{ data.name }}</option>
  599.                     {% endfor %}
  600.                 </select>
  601.             {% elseif session[UserConstants.USER_ID] is defined %}
  602.                 {# ── Logged in but no companies yet ── #}
  603.                 <input type="text" id="companyNameInput"
  604.                     placeholder="Enter your company name"
  605.                     style="width:100%;padding:10px 14px;border:1.5px solid var(--n-border-md);border-radius:8px;font-size:.9rem;font-family:'DM Sans',sans-serif;background:var(--n-cream);color:var(--n-dark);outline:none;box-sizing:border-box;transition:border-color .2s"
  606.                     oninput="this.style.borderColor='#C07D2A'">
  607.                 <input type="hidden" id="guestMode" value="new_company">
  608.                 <p style="font-size:.78rem;color:var(--n-muted);margin-top:6px;line-height:1.5">
  609.                     No workspace found. Enter a name and we'll set it up as you proceed.
  610.                     <a href="{{ url('create_company') }}" style="color:var(--n-amber);font-weight:600">Create workspace first →</a>
  611.                 </p>
  612.             {% else %}
  613.                 {# ── Guest / not logged in ── #}
  614.                 <input type="text" id="companyNameInput"
  615.                     placeholder="Enter your company name"
  616.                     style="width:100%;padding:10px 14px;border:1.5px solid var(--n-border-md);border-radius:8px;font-size:.9rem;font-family:'DM Sans',sans-serif;background:var(--n-cream);color:var(--n-dark);outline:none;box-sizing:border-box;transition:border-color .2s"
  617.                     oninput="this.style.borderColor='#C07D2A'">
  618.                 <input type="hidden" id="guestMode" value="guest">
  619.                 <p style="font-size:.78rem;color:var(--n-muted);margin-top:6px;line-height:1.5;display:flex;align-items:flex-start;gap:5px">
  620.                     <i class="fa-solid fa-lock" style="color:var(--n-amber);font-size:10px;margin-top:2px;flex-shrink:0"></i>
  621.                     Your plan selection will be saved. You'll be prompted to sign in or create an account to continue.
  622.                 </p>
  623.             {% endif %}
  624.         </div>
  625.         {# ── Billing cycle toggle ── #}
  626.         <div id="billingCycleWrap" style="display:none;margin-bottom:18px;">
  627.             <p class="n-ps-label" style="margin-bottom:8px;">Billing Cycle</p>
  628.             <div style="display:flex;gap:8px;">
  629.                 <button type="button" id="cycleMonthly" class="n-cycle-btn active">Monthly</button>
  630.                 <button type="button" id="cycleYearly"  class="n-cycle-btn">Yearly <span style="font-size:.7rem;color:var(--n-sage);font-weight:700;">−20%</span></button>
  631.             </div>
  632.         </div>
  633.         <div class="n-counter-row">
  634.             <div class="n-counter">
  635.                 <label>Select Users</label>
  636.                 <div class="n-counter-ctrl">
  637.                     <button type="button" id="decreaseUser">−</button>
  638.                     <span id="userCount">1</span>
  639.                     <button type="button" id="increaseUser">+</button>
  640.                 </div>
  641.             </div>
  642.             <div class="n-counter">
  643.                 <label>Select Admins</label>
  644.                 <div class="n-counter-ctrl">
  645.                     <button type="button" id="decreaseAdmin">−</button>
  646.                     <span id="adminCount">1</span>
  647.                     <button type="button" id="increaseAdmin">+</button>
  648.                 </div>
  649.             </div>
  650.             <div class="n-counter" id="mlCounterWrap" style="display:none;">
  651.                 <label>ML / AI Users</label>
  652.                 <div class="n-counter-ctrl">
  653.                     <button type="button" id="decreaseMl">−</button>
  654.                     <span id="mlCount">0</span>
  655.                     <button type="button" id="increaseMl">+</button>
  656.                 </div>
  657.             </div>
  658.         </div>
  659.         {# ── Admin ratio notice ── #}
  660.         <div id="adminRatioNotice" style="display:none;font-size:.78rem;color:var(--n-amber);margin-bottom:12px;padding:8px 12px;background:var(--n-amber-dim);border:1px solid rgba(192,125,42,.2);border-radius:6px;line-height:1.5;">
  661.             <span id="adminRatioText"></span>
  662.         </div>
  663.         <div class="n-ps-total-row">
  664.             <span class="n-ps-total-label" id="totalCycleLabel">Monthly Total</span>
  665.             <span id="cartTotal" class="n-ps-total-val">€0.00</span>
  666.         </div>
  667.         <button id="payButton" class="n-pay-btn">Proceed to SaaS Subscription</button>
  668.     </div>
  669. </div>
  670. <!-- ── CALCULATOR / WORKED EXAMPLES ─────────────────────────────────────────── -->
  671. <section class="n-calc-section">
  672.     <div class="n-wrap">
  673.         <div class="n-calc-box">
  674.             <div class="n-calc-head">
  675.                 <span class="n-eyebrow">Worked examples</span>
  676.                 <h2>What your <em>team</em><br>actually pays.</h2>
  677.                 <p>Because the Team plan uses a user + admin structure, here's what real teams pay once they cross the free-tier threshold. The blended rate is the total divided by headcount — we show it so you can compare with flat per-user competitors.</p>
  678.             </div>
  679.             <div class="n-calc-body open" id="calcBody">
  680.                 <div class="n-calc-table-wrap">
  681.                     <table class="n-calc-table worked_example_table">
  682.                         <thead>
  683.                             <tr>
  684.                                 <th>Team size</th>
  685.                                 <th>Users</th>
  686.                                 <th>Admins</th>
  687.                                 <th>User cost</th>
  688.                                 <th>Admin cost</th>
  689.                                 <th>Monthly total</th>
  690.                                 <th>Blended rate</th>
  691.                             </tr>
  692.                         </thead>
  693.                         <tbody id="workedExampleTbody">
  694.                             {# rows injected by buildWorkedExamples() below #}
  695.                         </tbody>
  696.                     </table>
  697.                 </div>
  698.                 <div class="n-admin-explainer">
  699.                     <span class="marker">Why admins?</span>
  700.                     <p id="adminExplainerText">Admins have write access to configuration, approvals, and cross-entity data — the roles that carry operational risk. The 4-users-to-1-admin ratio reflects what actually works in mid-market businesses: one approver per small team, not a flat hierarchy where everyone is equal. Blended rate at the 10-person tier: <strong id="blendedRateDisplay">€10.39/person</strong>.</p>
  701.                 </div>
  702.                 <script>
  703.                 (function () {
  704.                     /* ── 1. Read user/admin prices from the paid plan card ────────── */
  705.                     var userPrice  = null;
  706.                     var adminPrice = null;
  707.                     document.querySelectorAll('.n-pricing-card').forEach(function (card) {
  708.                         var isSales = card.querySelector('.contactSales');
  709.                         var isFree  = card.querySelector('.euPerUserPrice');
  710.                         if (isSales && isSales.value === '1') return;          // skip enterprise
  711.                         if (!isFree) return;                                    // guard
  712.                         var up = parseFloat(isFree.value);
  713.                         var ap = card.querySelector('.euPerAdminPrice') ? parseFloat(card.querySelector('.euPerAdminPrice').value) : 0;
  714.                         if (up > 0 && userPrice === null) {                    // first paid plan wins
  715.                             userPrice  = up;
  716.                             adminPrice = ap;
  717.                         }
  718.                     });
  719.                     /* If no paid plan found in DOM, table will not render */
  720.                     if (userPrice  === null || adminPrice === null) return;
  721.                     /* ── 2. Define team-size scenarios (4-user : 1-admin ratio) ──── */
  722.                     var scenarios = [
  723.                         { total: 10  },
  724.                         { total: 25  },
  725.                         { total: 50  },
  726.                         { total: 100 },
  727.                         { total: 250 },
  728.                     ];
  729.                     /* ── 3. Helper: format number as €1,234 ──────────────────────── */
  730.                     function eur(n) {
  731.                         return '\u20AC' + n.toLocaleString('en-US');
  732.                     }
  733.                     /* ── 4. Build rows ───────────────────────────────────────────── */
  734.                     var tbody = document.getElementById('workedExampleTbody');
  735.                     if (!tbody) return;
  736.                     tbody.innerHTML = '';
  737.                     var blendedRate = null;
  738.                     scenarios.forEach(function (s) {
  739.                         /* 4 users per 1 admin — round admins, users = remainder */
  740.                         var admins = Math.max(1, Math.round(s.total / 5));
  741.                         var users  = s.total - admins;
  742.                         var uCost  = users  * userPrice;
  743.                         var aCost  = admins * adminPrice;
  744.                         var total  = uCost + aCost;
  745.                         var blend  = (total / s.total).toFixed(2);
  746.                         if (blendedRate === null) blendedRate = blend; /* store first for explainer */
  747.                         var tr = document.createElement('tr');
  748.                         tr.innerHTML =
  749.                             '<td>' + s.total + ' people</td>' +
  750.                             '<td>' + users   + '</td>' +
  751.                             '<td>' + admins  + '</td>' +
  752.                             '<td>' + eur(uCost) + '</td>' +
  753.                             '<td>' + eur(aCost) + '</td>' +
  754.                             '<td class="n-total">'   + eur(total) + '</td>' +
  755.                             '<td class="n-blended">\u20AC' + blend + '/person</td>';
  756.                         tbody.appendChild(tr);
  757.                     });
  758.                     /* ── 5. Update explainer with live blended rate ───────────────── */
  759.                     var blendDisplay = document.getElementById('blendedRateDisplay');
  760.                     if (blendDisplay && blendedRate !== null) {
  761.                         blendDisplay.textContent = '\u20AC' + blendedRate + '/person';
  762.                     }
  763.                 }());
  764.                 </script>
  765.             </div>
  766.             {# ── Comparison table — inside same card, toggle to reveal ── #}
  767.             <div style="border-top:1px solid var(--n-border-md);margin-top:32px;padding-top:32px;">
  768.                 <div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:0;">
  769.                     <div>
  770.                         <span class="n-eyebrow trn" data-trn-key="_PRC_CMP_EYEBROW_">Compare plans</span>
  771.                         <h3 style="font-family:'Montserrat',sans-serif;font-size:1.25rem;font-weight:800;color:var(--n-dark);letter-spacing:-.02em;margin-top:4px;">
  772.                             <span class="trn" data-trn-key="_PRC_CMP_H2_">Exactly what's in</span> <em style="font-style:italic;color:var(--n-amber);" class="trn" data-trn-key="_PRC_CMP_H2_EM_">each plan.</em>
  773.                         </h3>
  774.                     </div>
  775.                     <button id="toggleButtonPrice" class="n-calc-toggle-btn" onclick="toggleDetails()">
  776.                         <span class="trn" data-trn-key="_PRC_CMP_BTN_SHOW_">Show full comparison</span> <span class="arrow">↓</span>
  777.                     </button>
  778.                 </div>
  779.                 {# ── Resolve Team plan prices dynamically from packageDetails ─── #}
  780.                 {% set _teamUserPrice = '' %}
  781.                 {% set _teamRegularUserPrice = '' %}
  782.                 {% set _teamIsEarlyAdopter = false %}
  783.                 {% for pd in packageDetails %}
  784.                     {% if pd.freeFlag is defined and pd.freeFlag != '1' and pd.contactSalesFlag is defined and pd.contactSalesFlag != '1' and pd.euPerUserPrice is defined and pd.euPerUserPrice.monthlyPrice > 0 and _teamUserPrice == '' %}
  785.                         {% set _teamUserPrice = pd.euPerUserPrice.monthlyPrice %}
  786.                         {% if pd.earlyAdopter is defined and pd.earlyAdopter %}
  787.                             {% set _teamIsEarlyAdopter = true %}
  788.                             {% set _teamRegularUserPrice = pd.euRegularUserPrice.monthlyPrice %}
  789.                         {% endif %}
  790.                     {% endif %}
  791.                 {% endfor %}
  792.                 <div id="featureTableWrap" style="display:none;margin-top:24px;">
  793.                     <div class="n-compare-wrap">
  794.                         <table class="n-cmp-table">
  795.                             <thead>
  796.                                 <tr class="n-plan-row">
  797.                                     <th style="width:38%;"></th>
  798.                                     <th>
  799.                                         <div class="n-cmp-plan-name">Free</div>
  800.                                         <div class="n-cmp-plan-price">€0</div>
  801.                                         <div class="n-cmp-plan-sub">free forever</div>
  802.                                     </th>
  803.                                     <th style="background:rgba(26,29,46,.04);">
  804.                                         <div class="n-cmp-plan-name" style="color:var(--n-amber);">Team</div>
  805.                                         {% if _teamIsEarlyAdopter %}
  806.                                             <div style="font-size:.68rem;color:var(--n-muted-2);text-decoration:line-through;line-height:1.2;margin-bottom:2px;">€{{ _teamRegularUserPrice }}/user</div>
  807.                                         {% endif %}
  808.                                         <div class="n-cmp-plan-price">€{{ _teamUserPrice }}</div>
  809.                                         <div class="n-cmp-plan-sub">/ user / month{% if _teamIsEarlyAdopter %} · ⚡ launch{% endif %}</div>
  810.                                     </th>
  811.                                     <th>
  812.                                         <div class="n-cmp-plan-name">Enterprise</div>
  813.                                         <div class="n-cmp-plan-price" style="font-size:1.1rem;">Custom</div>
  814.                                         <div class="n-cmp-plan-sub">contact sales</div>
  815.                                     </th>
  816.                                 </tr>
  817.                                 <tr>
  818.                                     <th style="width:38%;">Feature</th>
  819.                                     <th>Free</th>
  820.                                     <th style="background:rgba(26,29,46,.04);">Team</th>
  821.                                     <th>Enterprise</th>
  822.                                 </tr>
  823.                             </thead>
  824.                             <tbody>
  825.                                 <tr class="n-sh"><td colspan="4">Users &amp; storage</td></tr>
  826.                                 <tr><td>User limit</td><td>Up to 50</td><td>Unlimited</td><td>Unlimited</td></tr>
  827.                                 <tr><td>Storage per user</td><td>10 MB</td><td>Unlimited</td><td>Unlimited</td></tr>
  828.                                 <tr><td>Multi-company entities</td><td class="n-dash">—</td><td>Up to 5</td><td>Unlimited</td></tr>
  829.                                 <tr><td>Data retention</td><td>12 months</td><td>Unlimited</td><td>Unlimited</td></tr>
  830.                                 <tr><td>API access</td><td class="n-dash">—</td><td class="n-partial">Read-only</td><td class="n-check">✓</td></tr>
  831.                                 <tr class="n-sh"><td colspan="4">Six systems (all included)</td></tr>
  832.                                 <tr><td>Sales &amp; CRM with Kanban pipelines</td><td class="n-check">✓</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  833.                                 <tr><td>Projects with WBS, RACI, EVM costing</td><td class="n-check">✓</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  834.                                 <tr><td>Finance with bank rec, loan/EMI, expenses</td><td class="n-check">✓</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  835.                                 <tr><td>Cloud + IoT with InfluxDB dashboards</td><td class="n-dash">—</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  836.                                 <tr><td>Admin Command Center</td><td class="n-dash">—</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  837.                                 <tr><td>AI &amp; Reporting (email classifier, Spotlight)</td><td class="n-partial">Limited</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  838.                                 <tr class="n-sh"><td colspan="4">Germany-specific</td></tr>
  839.                                 <tr><td>DATEV-ready export (SKR03/04 compatible)</td><td class="n-dash">—</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  840.                                 <tr><td>SKR03 / SKR04 chart of accounts</td><td class="n-dash">—</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  841.                                 <tr><td>Berater + Mandant number mapping</td><td class="n-dash">—</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  842.                                 <tr class="n-sh"><td colspan="4">Mobile (Beezeness)</td></tr>
  843.                                 <tr><td>iOS + Android app</td><td class="n-check">✓</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  844.                                 <tr><td>QR-based employee onboarding</td><td class="n-check">✓</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  845.                                 <tr><td>Biometric login + offline clock-in</td><td class="n-check">✓</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  846.                                 <tr class="n-sh"><td colspan="4">Deployment &amp; support</td></tr>
  847.                                 <tr><td>EU cloud hosting</td><td class="n-check">✓</td><td class="n-check">✓</td><td class="n-check">✓</td></tr>
  848.                                 <tr><td>Native LLM Server (on-premise AI)</td><td class="n-dash">—</td><td class="n-partial">Add-on</td><td class="n-check">✓</td></tr>
  849.                                 <tr><td>On-premise deployment</td><td class="n-dash">—</td><td class="n-dash">—</td><td class="n-check">✓</td></tr>
  850.                                 <tr><td>Custom development hours</td><td class="n-dash">—</td><td class="n-dash">—</td><td class="n-check">Included</td></tr>
  851.                                 <tr><td>Migration support</td><td>Self-serve</td><td>3 days included</td><td>Full implementation</td></tr>
  852.                                 <tr><td>Support channel</td><td>Community + email</td><td>Priority chat</td><td>Dedicated manager</td></tr>
  853.                             </tbody>
  854.                         </table>
  855.                     </div>
  856.                     <div style="text-align:center;margin-top:20px;">
  857.                         <button class="n-calc-toggle-btn" onclick="hideTables()">
  858.                             <span class="trn" data-trn-key="_PRC_CMP_BTN_HIDE_">Hide comparison</span> <span class="arrow" style="transform:rotate(180deg);display:inline-block;">↓</span>
  859.                         </button>
  860.                     </div>
  861.                 </div>
  862.             </div>
  863.         </div>
  864.     </div>
  865. </section>
  866. <!-- ── FAQ ───────────────────────────────────────────────────────────────────── -->
  867. <section class="n-faq-section reveal">
  868.     <div class="n-wrap">
  869.         <div class="n-faq-layout">
  870.             <div class="n-faq-sidebar">
  871.                 <span class="n-eyebrow trn" data-trn-key="_PRC_FAQ_EYEBROW_">Common questions</span>
  872.                 <h2 style="margin-top:12px;"><span class="trn" data-trn-key="_PRC_FAQ_H2_">Before you sign up,</span> <em class="trn" data-trn-key="_PRC_FAQ_H2_EM_">the real answers.</em></h2>
  873.                 <p style="margin-top:14px;">Not here? <a href="#">Ask us directly</a>.</p>
  874.             </div>
  875.             <div class="n-faq-list">
  876.                 <details class="n-faq-item">
  877.                     <summary><span class="trn" data-trn-key="_PRC_FAQ_Q1_">What exactly counts toward the 10 MB storage?</span> <span class="n-faq-icon">+</span></summary>
  878.                     <div class="n-faq-answer">File attachments on invoices, expenses, documents, and mobile receipt captures. Transactional database rows (invoices, leads, ledger entries) don't count — you could process thousands of invoices and stay under the limit if nobody uploads heavy PDFs or photos.</div>
  879.                 </details>
  880.                 <details class="n-faq-item">
  881.                     <summary><span class="trn" data-trn-key="_PRC_FAQ_Q2_">What happens on the day a user crosses 10 MB?</span> <span class="n-faq-icon">+</span></summary>
  882.                     <div class="n-faq-answer">You get an email warning before, at, and after the threshold. Nothing breaks. You have a grace window to decide — upgrade to Team, delete old files, or do nothing (in which case your data is kept but writes are paused until you choose).</div>
  883.                 </details>
  884.                 <details class="n-faq-item">
  885.                     <summary><span class="trn" data-trn-key="_PRC_FAQ_Q3_">How does the admin ratio work in practice?</span> <span class="n-faq-icon">+</span></summary>
  886.                     <div class="n-faq-answer">You pick the mix. A 10-person team might be 8 users + 2 admins (€88/month) or 7 users + 3 admins (€102/month) depending on your organization. The 4-to-1 ratio is the most common mid-market pattern, not a hard rule.</div>
  887.                 </details>
  888.                 <details class="n-faq-item">
  889.                     <summary>Can we go back to Free if our storage drops? <span class="n-faq-icon">+</span></summary>
  890.                     <div class="n-faq-answer">Yes. Annual Team subscribers stay on the paid tier for the contract period, but monthly subscribers who get back under 10 MB per user can drop to Free at the next billing cycle.</div>
  891.                 </details>
  892.                 <details class="n-faq-item">
  893.                     <summary>What's included in Enterprise customization? <span class="n-faq-icon">+</span></summary>
  894.                     <div class="n-faq-answer">A fixed allocation of development hours per year for custom modules, workflow automations, integrations (your specific SAP, Salesforce, or legacy system), and branded reporting. Scope is agreed before work starts.</div>
  895.                 </details>
  896.                 <details class="n-faq-item">
  897.                     <summary>Where is my data hosted? <span class="n-faq-icon">+</span></summary>
  898.                     <div class="n-faq-answer">Free and Team customers: EU cloud (Frankfurt region). Enterprise: your choice of private cloud or on-premise deployment. Data never moves between regions without explicit permission. The Native LLM Server option means AI inference runs entirely on your hardware — nothing leaves your building.</div>
  899.                 </details>
  900.                 <details class="n-faq-item">
  901.                     <summary>What happens to my data if I leave? <span class="n-faq-icon">+</span></summary>
  902.                     <div class="n-faq-answer">Full export any time — during or after cancellation. CSV, JSON, or SQL. DATEV customers get a clean DATEV-format export on exit. No retention fees, no lock-in.</div>
  903.                 </details>
  904.             </div>
  905.         </div>
  906.     </div>
  907. </section>
  908. <!-- ── FINAL CTA ─────────────────────────────────────────────────────────────── -->
  909. <section class="n-cta-final">
  910.     <div class="n-wrap">
  911.         <h2>Ready to connect <em>your operations?</em></h2>
  912.         <p>Start free for up to 50 users and use HoneyBee on real work. Edge+, infrastructure, and project deployments are scoped separately — we will match you with the right approach.</p>
  913.         <div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap;">
  914.             <a href="{{ url('honeybee_contact') }}" class="n-btn-primary">Request Project Solution</a>
  915.             <a href="{{ path('quote_request', {plan: 'enterprise'}) }}" class="n-btn-secondary">Contact Sales</a>
  916.         </div>
  917.     </div>
  918. </section>
  919. {% include '@HoneybeeWeb/footer/central_footer.html.twig' %}
  920. <script>
  921.     /* ── Feature table toggle ── */
  922.     function toggleDetails() {
  923.         var wrap = document.getElementById('featureTableWrap');
  924.         var btn  = document.getElementById('toggleButtonPrice');
  925.         wrap.style.display = 'block';
  926.         btn.style.display  = 'none';
  927.         wrap.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
  928.     }
  929.     function hideTables() {
  930.         var wrap = document.getElementById('featureTableWrap');
  931.         var btn  = document.getElementById('toggleButtonPrice');
  932.         wrap.style.display = 'none';
  933.         btn.style.display  = 'inline-flex';
  934.         btn.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
  935.     }
  936.     /* ── Scroll reveal ── */
  937.     (function(){
  938.         var obs = new IntersectionObserver(function(entries){
  939.             entries.forEach(function(e){ if(e.isIntersecting){ e.target.classList.add('visible'); obs.unobserve(e.target); } });
  940.         },{ threshold: 0.10 });
  941.         document.querySelectorAll('.reveal').forEach(function(el){ obs.observe(el); });
  942.     })();
  943.     /* ── Plan selection + cart ── */
  944.     document.addEventListener('DOMContentLoaded', function () {
  945.         const YEARLY_DISCOUNT = 0.20; // 20% off for yearly billing
  946.         let selectedPlan = null;
  947.         let basePrice = 0, perUserPrice = 0, perAdminPrice = 0, perMlUserPrice = 0;
  948.         let minUser = 0, maxUserLimit = 0, minAdmin = 0, maxAdminLimit = 0;
  949.         let minMlUser = 0, maxMlUserLimit = 0, usersPerAdminBlock = 0;
  950.         let currentUsers = 0, currentAdmins = 0, currentMlUsers = 0;
  951.         let billingCycle = 'monthly';
  952.         const cartTotal        = document.getElementById('cartTotal');
  953.         const increaseUserBtn  = document.getElementById('increaseUser');
  954.         const decreaseUserBtn  = document.getElementById('decreaseUser');
  955.         const increaseAdminBtn = document.getElementById('increaseAdmin');
  956.         const decreaseAdminBtn = document.getElementById('decreaseAdmin');
  957.         const increaseMlBtn    = document.getElementById('increaseMl');
  958.         const decreaseMlBtn    = document.getElementById('decreaseMl');
  959.         const payButton        = document.getElementById('payButton');
  960.         const companySelect    = document.getElementById('companySelect');
  961.         const companyNameInput = document.getElementById('companyNameInput');
  962.         const guestModeField   = document.getElementById('guestMode');
  963.         const isOfflineMode    = guestModeField !== null;
  964.         const mlCounterWrap    = document.getElementById('mlCounterWrap');
  965.         const billingCycleWrap = document.getElementById('billingCycleWrap');
  966.         const cycleMonthly     = document.getElementById('cycleMonthly');
  967.         const cycleYearly      = document.getElementById('cycleYearly');
  968.         const adminRatioNotice = document.getElementById('adminRatioNotice');
  969.         const adminRatioText   = document.getElementById('adminRatioText');
  970.         const totalCycleLabel  = document.getElementById('totalCycleLabel');
  971.         /* ── Billing cycle toggle ── */
  972.         if (cycleMonthly) cycleMonthly.addEventListener('click', function() {
  973.             billingCycle = 'monthly';
  974.             cycleMonthly.classList.add('active');
  975.             cycleYearly.classList.remove('active');
  976.             if (totalCycleLabel) totalCycleLabel.innerText = 'Monthly Total';
  977.             calculateTotal(); updateButtons();
  978.         });
  979.         if (cycleYearly) cycleYearly.addEventListener('click', function() {
  980.             billingCycle = 'yearly';
  981.             cycleYearly.classList.add('active');
  982.             cycleMonthly.classList.remove('active');
  983.             if (totalCycleLabel) totalCycleLabel.innerText = 'Yearly Total';
  984.             calculateTotal(); updateButtons();
  985.         });
  986.         function validateCompanySelection() {
  987.             if (!selectedPlan) return;
  988.             if (selectedPlan.contactSales === 1) { payButton.disabled = false; return; }
  989.             if (isOfflineMode) {
  990.                 payButton.disabled = !companyNameInput || companyNameInput.value.trim() === '';
  991.             } else {
  992.                 payButton.disabled = !companySelect || parseInt(companySelect.value || 0) === 0;
  993.             }
  994.         }
  995.         if (companySelect)    companySelect.addEventListener('change', validateCompanySelection);
  996.         if (companyNameInput) companyNameInput.addEventListener('input', validateCompanySelection);
  997.         /* Compute the minimum admin count required for current user count */
  998.         function computeRequiredAdmins() {
  999.             if (!usersPerAdminBlock || usersPerAdminBlock <= 0) return minAdmin;
  1000.             let extraUsers = Math.max(0, currentUsers - minUser);
  1001.             return minAdmin + Math.floor(extraUsers / usersPerAdminBlock);
  1002.         }
  1003.         function updateAdminRatioNotice() {
  1004.             if (!adminRatioNotice || !adminRatioText) return;
  1005.             let required = computeRequiredAdmins();
  1006.             if (required > minAdmin && currentAdmins < required) {
  1007.                 adminRatioText.innerText = 'ⓘ Minimum ' + required + ' admin(s) required for ' + currentUsers + ' users (1 admin per ' + usersPerAdminBlock + ' extra users).';
  1008.                 adminRatioNotice.style.display = 'block';
  1009.             } else {
  1010.                 adminRatioNotice.style.display = 'none';
  1011.             }
  1012.         }
  1013.         function calculateTotal() {
  1014.             if (!selectedPlan || selectedPlan.contactSales === 1) return;
  1015.             let extraUsers  = Math.max(0, currentUsers  - minUser);
  1016.             let extraAdmins = Math.max(0, currentAdmins - minAdmin);
  1017.             let monthlyBase = basePrice
  1018.                 + (extraUsers  * perUserPrice)
  1019.                 + (extraAdmins * perAdminPrice)
  1020.                 + (currentMlUsers * perMlUserPrice);
  1021.             let total = billingCycle === 'yearly'
  1022.                 ? monthlyBase * 12 * (1 - YEARLY_DISCOUNT)
  1023.                 : monthlyBase;
  1024.             cartTotal.innerText    = '€' + total.toFixed(2);
  1025.             selectedPlan.total     = total;
  1026.             selectedPlan.users     = currentUsers;
  1027.             selectedPlan.admins    = currentAdmins;
  1028.             selectedPlan.mlUsers   = currentMlUsers;
  1029.             selectedPlan.billingCycle = billingCycle;
  1030.             validateCompanySelection();
  1031.         }
  1032.         function updateButtons() {
  1033.             if (!selectedPlan) {
  1034.                 increaseUserBtn.disabled = true;  decreaseUserBtn.disabled = true;
  1035.                 increaseAdminBtn.disabled = true; decreaseAdminBtn.disabled = true;
  1036.                 if (increaseMlBtn) { increaseMlBtn.disabled = true; decreaseMlBtn.disabled = true; }
  1037.                 cartTotal.innerText = '';
  1038.                 return;
  1039.             }
  1040.             let requiredAdmins    = computeRequiredAdmins();
  1041.             let effectiveMinAdmin = Math.max(minAdmin, requiredAdmins);
  1042.             /* User counters — enabled for ALL plans including Enterprise */
  1043.             decreaseUserBtn.disabled  = currentUsers <= minUser;
  1044.             increaseUserBtn.disabled  = maxUserLimit !== -1 && currentUsers >= maxUserLimit;
  1045.             /* Admin counters — lower bound is the ratio-required minimum */
  1046.             decreaseAdminBtn.disabled = currentAdmins <= effectiveMinAdmin;
  1047.             increaseAdminBtn.disabled = maxAdminLimit !== -1 && currentAdmins >= maxAdminLimit;
  1048.             /* ML counters */
  1049.             if (increaseMlBtn) {
  1050.                 decreaseMlBtn.disabled = currentMlUsers <= minMlUser;
  1051.                 increaseMlBtn.disabled = maxMlUserLimit === 0
  1052.                     || (maxMlUserLimit !== -1 && currentMlUsers >= maxMlUserLimit);
  1053.             }
  1054.             updateAdminRatioNotice();
  1055.         }
  1056.         document.querySelectorAll('.pricing-card .btn').forEach(function(button) {
  1057.             button.addEventListener('click', function(e) {
  1058.                 e.preventDefault(); e.stopPropagation();
  1059.                 const card = this.closest('.pricing-card');
  1060.                 const planName = card.querySelector('.n-pc-name')?.innerText || '';
  1061.                 /* Read plan config from hidden data fields */
  1062.                 basePrice          = parseFloat(card.querySelector('.euBasePrice')?.value || 0);
  1063.                 perUserPrice       = parseFloat(card.querySelector('.euPerUserPrice')?.value  || card.querySelector('.perUserPrice')?.value  || 0);
  1064.                 perAdminPrice      = parseFloat(card.querySelector('.euPerAdminPrice')?.value || card.querySelector('.perAdminPrice')?.value || 0);
  1065.                 perMlUserPrice     = parseFloat(card.querySelector('.euPerMlUserPrice')?.value || 0);
  1066.                 minUser            = parseInt(card.querySelector('.minUser')?.value       || 0);
  1067.                 maxUserLimit       = parseInt(card.querySelector('.maxUser')?.value       || 0);
  1068.                 minAdmin           = parseInt(card.querySelector('.minAdminUser')?.value  || 0);
  1069.                 maxAdminLimit      = parseInt(card.querySelector('.maxAdminUser')?.value  || 0);
  1070.                 minMlUser          = parseInt(card.querySelector('.minMlUser')?.value     || 0);
  1071.                 maxMlUserLimit     = parseInt(card.querySelector('.maxMlUser')?.value     || 0);
  1072.                 usersPerAdminBlock = parseInt(card.querySelector('.usersPerAdminBlock')?.value || 0);
  1073.                 currentUsers       = minUser;
  1074.                 currentAdmins      = minAdmin;
  1075.                 currentMlUsers     = minMlUser;
  1076.                 const contactSalesFlag = parseInt(card.querySelector('.contactSales')?.value || 0);
  1077.                 const packageId        = card.querySelector('.packageId')?.value || 0;
  1078.                 selectedPlan = { name: planName, currency: 'eur', contactSales: contactSalesFlag, packageId: packageId };
  1079.                 document.getElementById('selectedPlanName').innerText        = planName;
  1080.                 const isEarlyAdopter = !!card.querySelector('.earlyAdopterFlag');
  1081.                 const priceLabel = contactSalesFlag === 1
  1082.                     ? 'Custom quote'
  1083.                     : (perUserPrice > 0
  1084.                         ? '€' + perUserPrice.toFixed(2) + ' / user / month' + (isEarlyAdopter ? '  ⚡ launch offer' : '')
  1085.                         : 'Free');
  1086.                 document.getElementById('selectedPlanPrice').innerText        = priceLabel;
  1087.                 document.getElementById('selectedPlanMinUser').innerText      = minUser;
  1088.                 document.getElementById('selectedPlanMaxUser').innerText      = maxUserLimit  === -1 ? 'Unlimited' : maxUserLimit;
  1089.                 document.getElementById('selectedPlanMinAdminUser').innerText = minAdmin;
  1090.                 document.getElementById('selectedPlanAdminUser').innerText    = maxAdminLimit === -1 ? 'Unlimited' : maxAdminLimit;
  1091.                 document.getElementById('userCount').innerText                = currentUsers;
  1092.                 document.getElementById('adminCount').innerText               = currentAdmins;
  1093.                 if (document.getElementById('mlCount')) document.getElementById('mlCount').innerText = currentMlUsers;
  1094.                 /* Show/hide billing cycle toggle (hide for fully-free plans) */
  1095.                 let isFree = contactSalesFlag !== 1 && parseFloat(perUserPrice) === 0 && parseFloat(perAdminPrice) === 0 && parseFloat(basePrice) === 0;
  1096.                 if (billingCycleWrap) billingCycleWrap.style.display = isFree ? 'none' : 'block';
  1097.                 /* Show/hide ML counter (hide when package has no ML seats) */
  1098.                 if (mlCounterWrap) mlCounterWrap.style.display = (maxMlUserLimit === 0) ? 'none' : 'flex';
  1099.                 /* Reset to monthly on plan change */
  1100.                 billingCycle = 'monthly';
  1101.                 if (cycleMonthly) { cycleMonthly.classList.add('active'); cycleYearly.classList.remove('active'); }
  1102.                 if (totalCycleLabel) totalCycleLabel.innerText = 'Monthly Total';
  1103.                 if (contactSalesFlag === 1) {
  1104.                     payButton.innerText  = 'Contact Sales';
  1105.                     payButton.disabled   = false;
  1106.                     cartTotal.innerText  = '';
  1107.                     payButton.onclick    = function() {
  1108.                         var params = new URLSearchParams({
  1109.                             plan:          'enterprise',
  1110.                             normal_users:  currentUsers,
  1111.                             admin_users:   currentAdmins,
  1112.                             ml_users:      currentMlUsers,
  1113.                             billing_cycle: billingCycle,
  1114.                         });
  1115.                         var company = companyNameInput ? companyNameInput.value.trim() : '';
  1116.                         if (company) params.set('company_name', company);
  1117.                         window.location.href = '{{ path('quote_request') }}?' + params.toString();
  1118.                     };
  1119.                 } else {
  1120.                     payButton.innerText = isOfflineMode ? 'Save & Continue →' : 'Proceed to SaaS Subscription';
  1121.                     payButton.onclick   = function() {
  1122.                         if (!selectedPlan) { alert('Please select a plan first!'); return; }
  1123.                         if (isOfflineMode) {
  1124.                             var companyName = companyNameInput ? companyNameInput.value.trim() : '';
  1125.                             if (!companyName) {
  1126.                                 companyNameInput && (companyNameInput.style.borderColor = '#B04030');
  1127.                                 alert('Please enter your company name to continue.');
  1128.                                 return;
  1129.                             }
  1130.                             localStorage.setItem('checkoutPackage', JSON.stringify({
  1131.                                 name:         selectedPlan.name,
  1132.                                 price:        selectedPlan.total,
  1133.                                 users:        selectedPlan.users,
  1134.                                 admins:       selectedPlan.admins,
  1135.                                 mlUsers:      selectedPlan.mlUsers,
  1136.                                 billingCycle: selectedPlan.billingCycle,
  1137.                                 currency:     selectedPlan.currency,
  1138.                                 packageId:    selectedPlan.packageId,
  1139.                                 companyName:  companyName,
  1140.                                 guestMode:    guestModeField.value
  1141.                             }));
  1142.                             window.location.href = '{{ url('create_company') }}';
  1143.                             return;
  1144.                         }
  1145.                         const selectedAppId = companySelect ? parseInt(companySelect.value || 0) : 0;
  1146.                         if (selectedAppId === 0) { alert('Please select a company first!'); return; }
  1147.                         localStorage.setItem('checkoutPackage', JSON.stringify({
  1148.                             name:         selectedPlan.name,
  1149.                             price:        selectedPlan.total,
  1150.                             users:        selectedPlan.users,
  1151.                             admins:       selectedPlan.admins,
  1152.                             mlUsers:      selectedPlan.mlUsers,
  1153.                             billingCycle: selectedPlan.billingCycle,
  1154.                             currency:     selectedPlan.currency,
  1155.                             appId:        selectedAppId,
  1156.                             packageId:    selectedPlan.packageId
  1157.                         }));
  1158.                         window.location.href = '{{ path('honeybee_payment_method') }}';
  1159.                     };
  1160.                     calculateTotal(); updateButtons();
  1161.                 }
  1162.                 const summaryWrap = document.getElementById('selectedPlanSummary');
  1163.                 summaryWrap.classList.add('visible');
  1164.                 document.querySelectorAll('.pricing-card').forEach(c => c.classList.remove('active-card'));
  1165.                 card.classList.add('active-card');
  1166.                 summaryWrap.scrollIntoView({ behavior: 'smooth', block: 'center' });
  1167.                 updateButtons();
  1168.                 validateCompanySelection();
  1169.             });
  1170.         });
  1171.         /* ── User counter ── */
  1172.         increaseUserBtn.addEventListener('click', function() {
  1173.             if (!selectedPlan) return;
  1174.             if (maxUserLimit === -1 || currentUsers < maxUserLimit) {
  1175.                 currentUsers++;
  1176.                 document.getElementById('userCount').innerText = currentUsers;
  1177.                 /* Auto-bump admins if ratio requires it */
  1178.                 let required = computeRequiredAdmins();
  1179.                 if (currentAdmins < required) {
  1180.                     currentAdmins = required;
  1181.                     document.getElementById('adminCount').innerText = currentAdmins;
  1182.                 }
  1183.                 calculateTotal(); updateButtons();
  1184.             }
  1185.         });
  1186.         decreaseUserBtn.addEventListener('click', function() {
  1187.             if (!selectedPlan || currentUsers <= minUser) return;
  1188.             currentUsers--;
  1189.             document.getElementById('userCount').innerText = currentUsers;
  1190.             calculateTotal(); updateButtons();
  1191.         });
  1192.         /* ── Admin counter ── */
  1193.         increaseAdminBtn.addEventListener('click', function() {
  1194.             if (!selectedPlan) return;
  1195.             if (maxAdminLimit === -1 || currentAdmins < maxAdminLimit) {
  1196.                 currentAdmins++;
  1197.                 document.getElementById('adminCount').innerText = currentAdmins;
  1198.                 calculateTotal(); updateButtons();
  1199.             }
  1200.         });
  1201.         decreaseAdminBtn.addEventListener('click', function() {
  1202.             if (!selectedPlan) return;
  1203.             let effectiveMin = Math.max(minAdmin, computeRequiredAdmins());
  1204.             if (currentAdmins > effectiveMin) {
  1205.                 currentAdmins--;
  1206.                 document.getElementById('adminCount').innerText = currentAdmins;
  1207.                 calculateTotal(); updateButtons();
  1208.             }
  1209.         });
  1210.         /* ── ML / AI User counter ── */
  1211.         if (increaseMlBtn) increaseMlBtn.addEventListener('click', function() {
  1212.             if (!selectedPlan) return;
  1213.             if (maxMlUserLimit === -1 || currentMlUsers < maxMlUserLimit) {
  1214.                 currentMlUsers++;
  1215.                 document.getElementById('mlCount').innerText = currentMlUsers;
  1216.                 calculateTotal(); updateButtons();
  1217.             }
  1218.         });
  1219.         if (decreaseMlBtn) decreaseMlBtn.addEventListener('click', function() {
  1220.             if (!selectedPlan || currentMlUsers <= minMlUser) return;
  1221.             currentMlUsers--;
  1222.             document.getElementById('mlCount').innerText = currentMlUsers;
  1223.             calculateTotal(); updateButtons();
  1224.         });
  1225.     });
  1226. </script>