app/Plugin/NZEstimateSystem42/Resource/template/default/simulator.twig line 1

Open in your IDE?
  1. {% extends 'default_frame.twig' %}
  2. {% block main %}
  3. <style>
  4. /* フォント強制読み込み */
  5. @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&display=swap');
  6. * {
  7.     font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Segoe UI", Arial, "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, "Noto Sans JP", sans-serif !important;
  8. }
  9. /* 見積もりシミュレーター専用スタイル */
  10. .nz-estimate-wrapper {
  11.     max-width: 1200px;
  12.     margin: 60px auto;
  13.     padding: 0 20px;
  14. }
  15. .nz-estimate-card {
  16.     background: #ffffff;
  17.     border-radius: 20px;
  18.     box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
  19.     overflow: hidden;
  20. }
  21. .nz-estimate-header {
  22.     background: linear-gradient(135deg, #a8b3ff 0%, #d4a5ff 100%);
  23.     padding: 60px 40px;
  24.     text-align: center;
  25. }
  26. .nz-estimate-title {
  27.     font-size: 42px;
  28.     font-weight: 800;
  29.     color: #ffffff;
  30.     margin: 0 0 16px 0;
  31.     letter-spacing: -0.5px;
  32. }
  33. .nz-estimate-description {
  34.     font-size: 18px;
  35.     color: rgba(255, 255, 255, 0.95);
  36.     line-height: 1.7;
  37. }
  38. .nz-estimate-description img {
  39.     max-width: 100%;
  40.     height: auto;
  41.     display: block;
  42.     margin: 16px auto;
  43.     border-radius: 8px;
  44. }
  45. .nz-estimate-description p {
  46.     margin: 8px 0;
  47. }
  48. .nz-estimate-description br {
  49.     display: block;
  50.     content: "";
  51.     margin: 4px 0;
  52. }
  53. .nz-estimate-body {
  54.     padding: 50px 40px;
  55. }
  56. /* カテゴリセクション */
  57. .category-section {
  58.     margin-bottom: 20px;
  59.     overflow: hidden;
  60. }
  61. .category-row {
  62.     display: flex;
  63.     align-items: center;
  64.     gap: 20px;
  65.     width: 100%;
  66.     max-width: 100%;
  67. }
  68. .category-section.child-category {
  69.     margin-left: 40px;
  70.     padding-left: 20px;
  71.     border-left: 3px solid #cbd5e0;
  72.     display: none;
  73. }
  74. .category-section.child-category.active {
  75.     display: block;
  76.     animation: slideDown 0.3s ease;
  77. }
  78. @keyframes slideDown {
  79.     from { opacity: 0; transform: translateY(-10px); }
  80.     to { opacity: 1; transform: translateY(0); }
  81. }
  82. .category-header {
  83.     display: flex;
  84.     align-items: center;
  85.     gap: 15px;
  86.     margin-bottom: 0;
  87. }
  88. .category-title {
  89.     font-size: 20px;
  90.     font-weight: 700;
  91.     color: #2d3748;
  92.     margin: 0;
  93.     min-width: 150px;
  94.     flex-shrink: 0;
  95. }
  96. .category-title.child {
  97.     font-size: 18px;
  98.     color: #4a5568;
  99. }
  100. .category-required-badge {
  101.     display: inline-block;
  102.     background: #e53e3e;
  103.     color: white;
  104.     font-size: 12px;
  105.     font-weight: 700;
  106.     padding: 4px 12px;
  107.     border-radius: 12px;
  108. }
  109. .category-optional-badge {
  110.     display: inline-block;
  111.     background: #718096;
  112.     color: white;
  113.     font-size: 12px;
  114.     font-weight: 700;
  115.     padding: 4px 12px;
  116.     border-radius: 12px;
  117. }
  118. .parts-select {
  119.     flex: 1;
  120.     width: 100%;
  121.     max-width: 100%;
  122.     padding: 12px 16px;
  123.     font-size: 16px;
  124.     color: #2d3748 !important;
  125.     background-color: #f7fafc !important;
  126.     border: 2px solid #e2e8f0 !important;
  127.     border-radius: 12px !important;
  128.     transition: all 0.3s ease !important;
  129.     cursor: pointer;
  130.     box-sizing: border-box;
  131. }
  132. .parts-select:focus {
  133.     outline: none !important;
  134.     border-color: #667eea !important;
  135.     background-color: #ffffff !important;
  136.     box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
  137. }
  138. .parts-select option {
  139.     padding: 10px;
  140. }
  141. .parts-select option[value=""] {
  142.     color: #a0aec0 !important;
  143.     font-style: italic;
  144. }
  145. .parts-select option:disabled {
  146.     color: #a0aec0 !important;
  147. }
  148. /* 見積もりサマリー(常時表示) */
  149. .estimate-summary {
  150.     background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%);
  151.     border-radius: 16px;
  152.     padding: 30px;
  153.     margin-top: 40px;
  154. }
  155. .summary-header {
  156.     margin-bottom: 20px;
  157.     padding-bottom: 15px;
  158. }
  159. .summary-note {
  160.     font-size: 14px;
  161.     color: #1a202c;
  162.     font-weight: 600;
  163.     text-align: center;
  164. }
  165. .summary-items {
  166.     margin-bottom: 20px;
  167.     min-height: 100px;
  168. }
  169. .summary-empty {
  170.     text-align: center;
  171.     padding: 40px 20px;
  172.     color: #a0aec0;
  173.     font-size: 15px;
  174. }
  175. .summary-item {
  176.     display: block;
  177.     padding: 12px 0;
  178.     border-bottom: 1px solid #e2e8f0;
  179. }
  180. .summary-item-left {
  181.     width: 100%;
  182. }
  183. .summary-item-category {
  184.     font-size: 13px;
  185.     color: #718096;
  186.     margin-bottom: 4px;
  187. }
  188. .summary-item-name {
  189.     font-size: 15px;
  190.     font-weight: 600;
  191.     color: #2d3748;
  192. }
  193. .summary-total {
  194.     display: flex;
  195.     justify-content: space-between;
  196.     align-items: center;
  197.     padding: 24px 28px;
  198.     margin-top: 20px;
  199.     border-top: none;
  200.     background: linear-gradient(135deg, #a8b3ff 0%, #d4a5ff 100%);
  201.     border-radius: 12px;
  202.     box-shadow: 0 4px 12px rgba(168, 179, 255, 0.3);
  203. }
  204. .summary-total-label {
  205.     font-size: 22px;
  206.     font-weight: 700;
  207.     color: #ffffff;
  208. }
  209. .summary-total-price {
  210.     font-size: 36px;
  211.     font-weight: 800;
  212.     color: #ffffff;
  213.     text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  214. }
  215. .summary-total-price.hidden {
  216.     color: rgba(255, 255, 255, 0.4);
  217.     text-shadow: none;
  218. }
  219. /* 必須項目未選択警告 */
  220. .required-warning {
  221.     background: #fff5f5;
  222.     border: 2px solid #fc8181;
  223.     border-radius: 12px;
  224.     padding: 15px 20px;
  225.     margin-top: 20px;
  226.     text-align: center;
  227.     display: none;
  228. }
  229. .required-warning.active {
  230.     display: block;
  231. }
  232. .required-warning-text {
  233.     font-size: 14px;
  234.     font-weight: 600;
  235.     color: #742a2a;
  236. }
  237. /* お客様情報フォーム */
  238. .customer-form {
  239.     margin-top: 30px;
  240.     padding: 25px;
  241.     background: #f7fafc;
  242.     border-radius: 12px;
  243.     border: 1px solid #e2e8f0;
  244.     display: none;
  245. }
  246. .customer-form.active {
  247.     display: block;
  248.     animation: fadeIn 0.3s ease;
  249. }
  250. @keyframes fadeIn {
  251.     from { opacity: 0; transform: translateY(10px); }
  252.     to { opacity: 1; transform: translateY(0); }
  253. }
  254. .customer-form h3 {
  255.     font-size: 20px;
  256.     font-weight: 700;
  257.     color: #2d3748;
  258.     margin-bottom: 20px;
  259. }
  260. .form-group {
  261.     margin-bottom: 25px;
  262. }
  263. .form-row {
  264.     display: flex;
  265.     gap: 20px;
  266.     margin-bottom: 0;
  267. }
  268. .form-group-half {
  269.     flex: 1;
  270.     margin-bottom: 25px;
  271. }
  272. .form-label {
  273.     display: block;
  274.     font-size: 16px;
  275.     font-weight: 700;
  276.     color: #2d3748;
  277.     margin-bottom: 10px;
  278. }
  279. .form-label .required {
  280.     color: #e53e3e;
  281.     margin-left: 4px;
  282. }
  283. .form-control {
  284.     width: 100%;
  285.     padding: 14px 18px;
  286.     font-size: 16px;
  287.     color: #2d3748 !important;
  288.     background-color: #f7fafc !important;
  289.     border: 2px solid #e2e8f0 !important;
  290.     border-radius: 12px !important;
  291.     transition: all 0.3s ease !important;
  292. }
  293. .form-control:focus {
  294.     outline: none !important;
  295.     border-color: #667eea !important;
  296.     background-color: #ffffff !important;
  297.     box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
  298. }
  299. textarea.form-control {
  300.     min-height: 120px;
  301.     resize: vertical;
  302. }
  303. /* 送信ボタン */
  304. .submit-section {
  305.     margin-top: 40px;
  306.     text-align: center;
  307.     display: none;
  308. }
  309. .submit-section.active {
  310.     display: block;
  311.     animation: fadeIn 0.3s ease;
  312. }
  313. .submit-btn {
  314.     background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  315.     color: #ffffff;
  316.     font-size: 18px;
  317.     font-weight: 700;
  318.     padding: 18px 60px;
  319.     border: none;
  320.     border-radius: 50px;
  321.     cursor: pointer;
  322.     transition: all 0.3s ease;
  323.     box-shadow: 0 8px 24px rgba(102, 126, 234, 0.4);
  324. }
  325. .submit-btn:hover {
  326.     transform: translateY(-2px);
  327.     box-shadow: 0 12px 32px rgba(102, 126, 234, 0.5);
  328. }
  329. .submit-btn:disabled {
  330.     background: #cbd5e0;
  331.     cursor: not-allowed;
  332.     box-shadow: none;
  333. }
  334. /* ローディング */
  335. .loading {
  336.     text-align: center;
  337.     padding: 40px;
  338.     display: none;
  339. }
  340. .loading.active {
  341.     display: block;
  342. }
  343. .loading-spinner {
  344.     border: 4px solid #e2e8f0;
  345.     border-top: 4px solid #667eea;
  346.     border-radius: 50%;
  347.     width: 50px;
  348.     height: 50px;
  349.     animation: spin 1s linear infinite;
  350.     margin: 0 auto 20px;
  351. }
  352. @keyframes spin {
  353.     0% { transform: rotate(0deg); }
  354.     100% { transform: rotate(360deg); }
  355. }
  356. /* 完了メッセージ */
  357. .complete-message {
  358.     text-align: center;
  359.     padding: 60px 40px;
  360.     display: none;
  361. }
  362. .complete-message.active {
  363.     display: block;
  364.     animation: fadeIn 0.5s ease;
  365. }
  366. .complete-icon {
  367.     font-size: 72px;
  368.     color: #48bb78;
  369.     margin-bottom: 20px;
  370. }
  371. .complete-title {
  372.     font-size: 28px;
  373.     font-weight: 700;
  374.     color: #2d3748;
  375.     margin-bottom: 15px;
  376. }
  377. .complete-text {
  378.     font-size: 16px;
  379.     color: #718096;
  380.     line-height: 1.8;
  381. }
  382. /* 注意事項 */
  383. .notice-text {
  384.     margin-top: 20px;
  385.     padding: 15px 20px;
  386.     background-color: #fffbeb;
  387.     border-left: 4px solid #f59e0b;
  388.     border-radius: 4px;
  389.     color: #92400e;
  390.     font-size: 14px;
  391.     line-height: 1.7;
  392. }
  393. /* フリーエリア */
  394. .nz-free-area {
  395.     max-width: 1200px;
  396.     margin: 40px auto;
  397.     padding: 0 20px;
  398.     text-align: center;
  399. }
  400. .nz-free-area h2 {
  401.     font-size: 28px;
  402.     font-weight: 700;
  403.     color: #2d3748;
  404.     margin-bottom: 20px;
  405.     text-align: center;
  406. }
  407. .nz-free-area h3 {
  408.     font-size: 22px;
  409.     font-weight: 600;
  410.     color: #4a5568;
  411.     margin: 30px 0 15px;
  412.     text-align: left;
  413. }
  414. .nz-free-area p {
  415.     font-size: 16px;
  416.     color: #718096;
  417.     line-height: 1.8;
  418.     margin-bottom: 15px;
  419.     text-align: left;
  420. }
  421. .nz-free-area img {
  422.     max-width: 100%;
  423.     height: auto;
  424.     border-radius: 8px;
  425.     margin: 20px 0;
  426. }
  427. .nz-free-area .btn {
  428.     display: inline-block;
  429.     width: 100%;
  430.     max-width: 400px;
  431.     padding: 18px 30px;
  432.     background: linear-gradient(135deg, #10b981 0%, #059669 100%);
  433.     color: white !important;
  434.     text-decoration: none;
  435.     border-radius: 16px;
  436.     font-weight: 700;
  437.     font-size: 18px;
  438.     text-align: center;
  439.     transition: all 0.3s ease;
  440.     border: none;
  441.     cursor: pointer;
  442.     box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
  443. }
  444. .nz-free-area .btn:hover {
  445.     background: linear-gradient(135deg, #059669 0%, #047857 100%);
  446.     transform: translateY(-2px);
  447.     box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
  448. }
  449. /* 購入セクション */
  450. .purchase-section {
  451.     margin-bottom: 40px;
  452. }
  453. .purchase-card {
  454.     background: #ffffff;
  455.     border: none;
  456.     border-radius: 0;
  457.     padding: 20px 0;
  458.     text-align: center;
  459. }
  460. .purchase-btn {
  461.     width: 100%;
  462.     max-width: 400px;
  463.     padding: 18px;
  464.     background: linear-gradient(135deg, #10b981 0%, #059669 100%);
  465.     color: white;
  466.     border: none;
  467.     border-radius: 12px;
  468.     font-size: 18px;
  469.     font-weight: 700;
  470.     cursor: pointer;
  471.     transition: all 0.3s ease;
  472.     box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
  473.     margin-bottom: 15px;
  474. }
  475. .purchase-btn:hover {
  476.     transform: translateY(-2px);
  477.     box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
  478. }
  479. .purchase-btn:active {
  480.     transform: translateY(0);
  481. }
  482. .purchase-notice {
  483.     margin-top: 15px;
  484.     padding: 15px;
  485.     background-color: #f0fdf4;
  486.     border-radius: 8px;
  487.     color: #065f46;
  488.     font-size: 14px;
  489.     line-height: 1.7;
  490. }
  491. /* スマホ用固定サマリー:PC/タブレットでは非表示 */
  492. .estimate-summary-sticky {
  493.     display: none;
  494. }
  495. /* ==================== レスポンシブ対応 ==================== */
  496. @media (max-width: 768px) {
  497.     /* コンテナ */
  498.     .estimate-container {
  499.         padding: 10px;
  500.     }
  501.     
  502.     /* ヘッダー */
  503.     .estimate-header {
  504.         padding: 20px 10px;
  505.         margin-bottom: 15px;
  506.     }
  507.     
  508.     .estimate-header h1 {
  509.         font-size: 20px;
  510.     }
  511.     
  512.     .estimate-header p {
  513.         font-size: 13px;
  514.     }
  515.     
  516.     /* カテゴリセクション */
  517.     .category-section {
  518.         padding: 15px;
  519.         margin-bottom: 12px;
  520.         width: 100%;
  521.         box-sizing: border-box;
  522.     }
  523.     
  524.     .category-row {
  525.         display: flex;
  526.         flex-direction: column;
  527.         gap: 10px;
  528.         width: 100%;
  529.         max-width: 100%;
  530.         box-sizing: border-box;
  531.     }
  532.     
  533.     .category-header {
  534.         flex-direction: row;
  535.         align-items: center;
  536.         gap: 10px;
  537.         margin-bottom: 0;
  538.         width: 100%;
  539.     }
  540.     
  541.     .category-name {
  542.         font-size: 16px;
  543.     }
  544.     
  545.     .required-badge {
  546.         font-size: 11px;
  547.         padding: 3px 8px;
  548.     }
  549.     
  550.     /* セレクトボックス */
  551.     .parts-select {
  552.         font-size: 15px;
  553.         padding: 12px 35px 12px 12px;
  554.         margin-top: 0;
  555.         width: 100% !important;
  556.         max-width: 100% !important;
  557.         flex: none;
  558.     }
  559.     
  560.     /* 見積もりサマリー */
  561.     .estimate-summary {
  562.         padding: 15px;
  563.         margin-bottom: 15px;
  564.     }
  565.     
  566.     .summary-note {
  567.         font-size: 12px;
  568.     }
  569.     
  570.     .summary-total {
  571.         flex-direction: row;
  572.         align-items: center;
  573.         gap: 15px;
  574.         padding: 12px 0;
  575.     }
  576.     
  577.     .summary-total-label {
  578.         font-size: 15px;
  579.     }
  580.     
  581.     .summary-total-price {
  582.         font-size: 24px;
  583.         text-align: right;
  584.     }
  585.     
  586.     /* 購入セクション */
  587.     .purchase-section {
  588.         margin-bottom: 15px;
  589.     }
  590.     
  591.     .purchase-card {
  592.         padding: 15px;
  593.     }
  594.     
  595.     .purchase-btn {
  596.         max-width: 100%;
  597.         font-size: 15px;
  598.         padding: 14px;
  599.     }
  600.     
  601.     .purchase-notice {
  602.         font-size: 12px;
  603.         padding: 10px;
  604.         margin-top: 10px;
  605.     }
  606.     
  607.     /* お客様情報フォーム */
  608.     .customer-form {
  609.         padding: 15px;
  610.         margin-top: 15px;
  611.     }
  612.     
  613.     .customer-form h3 {
  614.         font-size: 16px;
  615.         margin-bottom: 12px;
  616.     }
  617.     
  618.     .form-row {
  619.         flex-direction: column;
  620.         gap: 0;
  621.     }
  622.     
  623.     .form-group {
  624.         margin-bottom: 15px;
  625.     }
  626.     
  627.     .form-label {
  628.         font-size: 14px;
  629.         margin-bottom: 6px;
  630.     }
  631.     
  632.     .form-control {
  633.         padding: 10px 12px;
  634.         font-size: 15px;
  635.     }
  636.     
  637.     textarea.form-control {
  638.         min-height: 80px;
  639.     }
  640.     
  641.     /* 送信ボタン */
  642.     .submit-section {
  643.         padding: 15px;
  644.         margin-top: 15px;
  645.     }
  646.     
  647.     .submit-btn {
  648.         padding: 14px;
  649.         font-size: 15px;
  650.     }
  651.     
  652.     /* 必須警告 */
  653.     .required-warning {
  654.         padding: 10px;
  655.         font-size: 12px;
  656.         margin: 10px 0;
  657.     }
  658.     
  659.     /* フリーエリア */
  660.     .nz-free-area {
  661.         padding: 20px 15px;
  662.         font-size: 14px;
  663.     }
  664.     
  665.     .nz-free-area h2 {
  666.         font-size: 20px;
  667.     }
  668.     
  669.     .nz-free-area h3 {
  670.         font-size: 18px;
  671.     }
  672. }
  673. @media (max-width: 480px) {
  674.     /* さらに小さい画面用 */
  675.     .estimate-container {
  676.         padding: 8px;
  677.     }
  678.     
  679.     .estimate-header {
  680.         padding: 15px 8px;
  681.     }
  682.     
  683.     .estimate-header h1 {
  684.         font-size: 18px;
  685.     }
  686.     
  687.     .estimate-header p {
  688.         font-size: 12px;
  689.     }
  690.     
  691.     .category-section {
  692.         padding: 12px;
  693.         margin-bottom: 10px;
  694.     }
  695.     
  696.     .category-row {
  697.         display: flex;
  698.         flex-direction: column;
  699.         gap: 8px;
  700.     }
  701.     
  702.     .category-name {
  703.         font-size: 15px;
  704.     }
  705.     
  706.     .parts-select {
  707.         font-size: 14px;
  708.         padding: 10px 30px 10px 10px;
  709.         width: 100% !important;
  710.         max-width: 100% !important;
  711.         flex: none;
  712.     }
  713.     
  714.     .estimate-summary {
  715.         padding: 12px;
  716.     }
  717.     
  718.     .summary-title {
  719.         font-size: 15px;
  720.     }
  721.     
  722.     .summary-total {
  723.         padding: 16px 20px !important;
  724.         border-radius: 12px;
  725.     }
  726.     
  727.     .summary-total-label {
  728.         font-size: 18px !important;
  729.     }
  730.     
  731.     .summary-total-price {
  732.         font-size: 26px !important;
  733.     }
  734.     
  735.     /* スマホ用:下部固定サマリー(クローン) */
  736.     .estimate-summary-sticky {
  737.         display: none; /* 初期状態では非表示 */
  738.         position: fixed;
  739.         bottom: 0;
  740.         left: 0;
  741.         right: 0;
  742.         background: linear-gradient(135deg, #a8b3ff 0%, #d4a5ff 100%);
  743.         box-shadow: 0 -4px 12px rgba(168, 179, 255, 0.4);
  744.         z-index: 9999;
  745.         padding: 8px 16px;
  746.     }
  747.     
  748.     .estimate-summary-sticky.visible {
  749.         display: block !important; /* スクロールで表示 */
  750.     }
  751.     
  752.     .estimate-summary-sticky .summary-total {
  753.         margin: 0;
  754.         padding: 0;
  755.         border-top: none;
  756.         display: flex;
  757.         justify-content: space-between;
  758.         align-items: center;
  759.         background: transparent;
  760.         box-shadow: none;
  761.         border-radius: 0;
  762.     }
  763.     
  764.     .estimate-summary-sticky .summary-total-label {
  765.         font-size: 14px;
  766.         font-weight: 700;
  767.         color: #ffffff;
  768.     }
  769.     
  770.     .estimate-summary-sticky .summary-total-price {
  771.         font-size: 20px;
  772.         font-weight: 800;
  773.         color: #ffffff;
  774.         text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  775.     }
  776.     
  777.     /* コンテンツが固定サマリーに隠れないよう余白 */
  778.     body.sticky-summary-active {
  779.         padding-bottom: 50px;
  780.     }
  781.     
  782.     .purchase-card {
  783.         padding: 12px;
  784.     }
  785.     
  786.     .purchase-btn {
  787.         font-size: 14px;
  788.         padding: 12px;
  789.     }
  790.     
  791.     .customer-form {
  792.         padding: 12px;
  793.     }
  794.     
  795.     .form-group {
  796.         margin-bottom: 12px;
  797.     }
  798.     
  799.     .submit-section {
  800.         padding: 12px;
  801.     }
  802. }
  803. </style>
  804. <div class="nz-estimate-wrapper">
  805.     <div class="nz-estimate-card">
  806.         <div class="nz-estimate-header">
  807.             <h1 class="nz-estimate-title">
  808.                 {% if config.pageTitle %}
  809.                     {{ config.pageTitle|replace({'&lt;': '<', '&gt;': '>', '&quot;': '"', '&#039;': "'", '&amp;': '&'})|raw }}
  810.                 {% else %}
  811.                     見積もりシミュレーター
  812.                 {% endif %}
  813.             </h1>
  814.             <div class="nz-estimate-description">
  815.                 {% if config.pageDescription %}
  816.                     {{ config.pageDescription|replace({'&lt;': '<', '&gt;': '>', '&quot;': '"', '&#039;': "'", '&amp;': '&'})|raw }}
  817.                 {% else %}
  818.                     パーツを選択して、お見積もりを作成いただけます。
  819.                 {% endif %}
  820.             </div>
  821.         </div>
  822.         <div class="nz-estimate-body">
  823.             {% for item in categoriesWithParts %}
  824.                 <div class="category-section" data-category-id="{{ item.category.id }}">
  825.                     <div class="category-row">
  826.                         <div class="category-header">
  827.                             <h2 class="category-title">{{ item.category.name }}</h2>
  828.                             {% if item.category.isRequired %}
  829.                                 <span class="category-required-badge">必須</span>
  830.                             {% else %}
  831.                                 <span class="category-optional-badge">任意</span>
  832.                             {% endif %}
  833.                         </div>
  834.                         
  835.                         {# パーツを選択 #}
  836.                         <select class="parts-select" 
  837.                                 data-category-id="{{ item.category.id }}" 
  838.                                 data-required="{{ item.category.isRequired ? '1' : '0' }}"
  839.                                 data-category-name="{{ item.category.name }}">
  840.                             <option value="">選択してください</option>
  841.                             {% for parts in item.parts %}
  842.                                 <option value="{{ parts.id }}" 
  843.                                         data-price="{{ parts.price }}"
  844.                                         data-name="{{ parts.productName }}"
  845.                                         data-child-category-id="{{ parts.childCategoryId ? parts.childCategoryId : '' }}"
  846.                                         data-child-filter-keywords="{{ parts.childFilterKeywords ? parts.childFilterKeywords : '' }}">
  847.                                     {{ parts.productName }}
  848.                                 </option>
  849.                             {% endfor %}
  850.                         </select>
  851.                     </div>
  852.                 </div>
  853.             {% endfor %}
  854.             
  855.             {# 全パーツデータをJavaScript用に埋め込み #}
  856.             <script type="application/json" id="allPartsData">
  857.             [
  858.                 {% for item in categoriesWithParts %}
  859.                     {% for parts in item.parts %}
  860.                     {
  861.                         "id": {{ parts.id }},
  862.                         "category_id": {{ item.category.id }},
  863.                         "category_name": {{ item.category.name|json_encode|raw }},
  864.                         "name": {{ parts.productName|json_encode|raw }},
  865.                         "model_number": {{ parts.modelNumber|default('')|json_encode|raw }},
  866.                         "price": {{ parts.price }},
  867.                         "child_category_id": {{ parts.childCategoryId ? parts.childCategoryId|json_encode|raw : 'null' }},
  868.                         "child_filter_keywords": {{ parts.childFilterKeywords ? parts.childFilterKeywords|json_encode|raw : 'null' }}
  869.                     }{% if not loop.parent.loop.last or not loop.last %},{% endif %}
  870.                     {% endfor %}
  871.                 {% endfor %}
  872.             ]
  873.             </script>
  874.             
  875.             {# カテゴリデータをJavaScriptに埋め込み(子カテゴリを含むすべて) #}
  876.             <script type="application/json" id="allCategoriesData">
  877.             [
  878.                 {% for item in allCategoriesWithParts %}
  879.                 {
  880.                     "id": {{ item.category.id }},
  881.                     "name": {{ item.category.name|json_encode|raw }},
  882.                     "is_required": {{ item.category.isRequired ? 'true' : 'false' }},
  883.                     "parts": [
  884.                         {% for parts in item.parts %}
  885.                         {
  886.                             "id": {{ parts.id }},
  887.                             "name": {{ parts.productName|json_encode|raw }},
  888.                             "model_number": {{ parts.modelNumber|default('')|json_encode|raw }},
  889.                             "price": {{ parts.price }},
  890.                             "child_category_id": {{ parts.childCategoryId ? parts.childCategoryId|json_encode|raw : 'null' }},
  891.                             "child_filter_keywords": {{ parts.childFilterKeywords ? parts.childFilterKeywords|json_encode|raw : 'null' }}
  892.                         }{% if not loop.last %},{% endif %}
  893.                         {% endfor %}
  894.                     ]
  895.                 }{% if not loop.last %},{% endif %}
  896.                 {% endfor %}
  897.             ]
  898.             </script>
  899.             <!-- 見積もりサマリー(常時表示) -->
  900.             <div class="estimate-summary">
  901.                 <div class="summary-header">
  902.                     <div class="summary-note">※ 必須項目をすべて選択すると金額が表示されます</div>
  903.                 </div>
  904.                 
  905.                 <!-- 必須項目未選択警告 -->
  906.                 <div class="required-warning" id="requiredWarning">
  907.                     <div class="required-warning-text">
  908.                         ⚠️ 必須カテゴリをすべて選択してください
  909.                     </div>
  910.                 </div>
  911.                 
  912.                 <div class="summary-total">
  913.                     <div class="summary-total-label">合計金額</div>
  914.                     <div class="summary-total-price" id="totalPriceArea">
  915.                         ¥<span id="totalPrice">---</span>
  916.                     </div>
  917.                 </div>
  918.             </div>
  919.             <!-- スマホ用:下部固定サマリー -->
  920.             <div class="estimate-summary-sticky" id="stickySummary">
  921.                 <div class="summary-total">
  922.                     <div class="summary-total-label">合計金額</div>
  923.                     <div class="summary-total-price">
  924.                         ¥<span id="stickyTotalPrice">---</span>
  925.                     </div>
  926.                 </div>
  927.             </div>
  928.             <!-- 今すぐ購入セクション -->
  929.             <div class="purchase-section" id="purchaseSection" style="display: none;">
  930.                 <div class="purchase-card">
  931.                     <button type="button" class="purchase-btn" id="purchaseBtn">
  932.                         🛒 すぐに購入
  933.                     </button>
  934.                     
  935.                     {% if config.purchaseNoticeText %}
  936.                     <div class="purchase-notice">
  937.                         {{ config.purchaseNoticeText|nl2br }}
  938.                     </div>
  939.                     {% endif %}
  940.                 </div>
  941.             </div>
  942.             <!-- お客様情報フォーム -->
  943.             <div class="customer-form" id="customerForm">
  944.                 <h3>お客様情報</h3>
  945.                 <form id="estimateForm">
  946.                     <div class="form-row">
  947.                         <div class="form-group form-group-half">
  948.                             <label class="form-label">
  949.                                 お名前<span class="required">*</span>
  950.                             </label>
  951.                             <input type="text" name="name" class="form-control" required>
  952.                         </div>
  953.                         
  954.                         <div class="form-group form-group-half">
  955.                             <label class="form-label">
  956.                                 電話番号<span class="required">*</span>
  957.                             </label>
  958.                             <input type="tel" name="tel" class="form-control" required>
  959.                         </div>
  960.                     </div>
  961.                     
  962.                     <div class="form-group">
  963.                         <label class="form-label">
  964.                             メールアドレス<span class="required">*</span>
  965.                         </label>
  966.                         <input type="email" name="email" class="form-control" required>
  967.                     </div>
  968.                     
  969.                     <div class="form-group">
  970.                         <label class="form-label">ご要望・備考</label>
  971.                         <textarea name="message" class="form-control" rows="3"></textarea>
  972.                     </div>
  973.                 </form>
  974.             </div>
  975.             <!-- 送信ボタン -->
  976.             <div class="submit-section" id="submitSection">
  977.                 <button type="button" class="submit-btn" id="submitBtn">
  978.                     見積もり依頼を送信
  979.                 </button>
  980.                 
  981.                 {% if config.noticeText %}
  982.                 <div class="notice-text">
  983.                     {{ config.noticeText|nl2br }}
  984.                 </div>
  985.                 {% endif %}
  986.             </div>
  987.             <!-- ローディング -->
  988.             <div class="loading" id="loading">
  989.                 <div class="loading-spinner"></div>
  990.                 <p>送信中...</p>
  991.             </div>
  992.             <!-- 完了メッセージ -->
  993.             <div class="complete-message" id="completeMessage">
  994.                 <div class="complete-icon">✓</div>
  995.                 <div class="complete-title">送信完了</div>
  996.                 <div class="complete-text">
  997.                     お見積もり依頼を受け付けました。<br>
  998.                     担当者より折り返しご連絡いたします。
  999.                 </div>
  1000.             </div>
  1001.         </div>
  1002.     </div>
  1003. </div>
  1004. <!-- フリーエリア -->
  1005. {% if config.freeAreaHtml %}
  1006. <div class="nz-free-area">
  1007.     {{ config.freeAreaHtml|raw }}
  1008. </div>
  1009. {% endif %}
  1010. <script>
  1011. $(function() {
  1012.     let selectedParts = [];
  1013.     
  1014.     // 内部料金設定(顧客には非表示)
  1015.     const additionalCharge = {{ config.additionalCharge|default(0) }};
  1016.     const discountRate = {{ config.discountRate|default(0) }};
  1017.     
  1018.     // データ読み込み
  1019.     const allParts = JSON.parse($('#allPartsData').text());
  1020.     const allCategories = JSON.parse($('#allCategoriesData').text());
  1021.     
  1022.     // 必須カテゴリを取得
  1023.     const requiredCategories = [];
  1024.     allCategories.forEach(function(cat) {
  1025.         if (cat.is_required) {
  1026.             requiredCategories.push(cat.id);
  1027.         }
  1028.     });
  1029.     
  1030.     // パーツ選択時の処理
  1031.     $(document).on('change', '.parts-select', function() {
  1032.         const $select = $(this);
  1033.         const partsId = $select.val();
  1034.         const categoryId = $select.data('category-id');
  1035.         const $currentSection = $select.closest('.category-section');
  1036.         
  1037.         // 現在のセクションの後続の子カテゴリを削除(アニメーション付き)
  1038.         $currentSection.nextAll('.category-section.child-category').each(function() {
  1039.             const $childSection = $(this);
  1040.             const parentId = $childSection.data('parent-id');
  1041.             
  1042.             // 現在のカテゴリの子カテゴリのみ削除
  1043.             if (parentId == categoryId) {
  1044.                 $childSection.removeClass('active').fadeOut(200, function() {
  1045.                     $(this).remove();
  1046.                 });
  1047.             }
  1048.         });
  1049.         
  1050.         if (!partsId) {
  1051.             updateSummary();
  1052.             return;
  1053.         }
  1054.         
  1055.         // 選択したパーツ情報を取得
  1056.         const parts = allParts.find(p => p.id == partsId);
  1057.         
  1058.         if (!parts) {
  1059.             updateSummary();
  1060.             return;
  1061.         }
  1062.         
  1063.         // 子カテゴリがあるか確認(カンマ区切りで複数対応)
  1064.         if (parts.child_category_id) {
  1065.             const childCategoryIds = parts.child_category_id.toString().split(',').map(id => parseInt(id.trim())).filter(id => id > 0);
  1066.             
  1067.             console.log('========================================');
  1068.             console.log('選択されたパーツ:', parts.name);
  1069.             console.log('子カテゴリID:', childCategoryIds);
  1070.             console.log('フィルタキーワード:', parts.child_filter_keywords);
  1071.             
  1072.             childCategoryIds.forEach(function(childCatId) {
  1073.                 const childCategory = allCategories.find(c => c.id == childCatId);
  1074.                 
  1075.                 console.log('--- 子カテゴリID:', childCatId, '---');
  1076.                 console.log('子カテゴリ名:', childCategory ? childCategory.name : 'NOT FOUND');
  1077.                 
  1078.                 if (childCategory && childCategory.parts.length > 0) {
  1079.                     let filteredParts = childCategory.parts;
  1080.                     
  1081.                     // フィルタキーワードがある場合は絞り込み
  1082.                     if (parts.child_filter_keywords) {
  1083.                         const keywords = parts.child_filter_keywords.split(',').map(k => k.trim().toLowerCase());
  1084.                         
  1085.                         console.log('=== フィルタ実行 ===');
  1086.                         console.log('子カテゴリ:', childCategory.name);
  1087.                         console.log('フィルタキーワード:', keywords);
  1088.                         console.log('全パーツ数:', childCategory.parts.length);
  1089.                         
  1090.                         filteredParts = childCategory.parts.filter(p => {
  1091.                             const searchText = (p.name + ' ' + (p.model_number || '')).toLowerCase();
  1092.                             const matched = keywords.some(kw => searchText.includes(kw));
  1093.                             console.log(`  - ${p.name}: ${matched ? 'マッチ' : '除外'} (検索文字: ${searchText})`);
  1094.                             return matched;
  1095.                         });
  1096.                         
  1097.                         console.log('フィルタ後パーツ数:', filteredParts.length);
  1098.                     }
  1099.                     
  1100.                     // フィルタ後のパーツがある場合のみ子カテゴリを表示
  1101.                     if (filteredParts.length > 0) {
  1102.                         // 子カテゴリセクションを作成
  1103.                         createChildCategorySection({
  1104.                             ...childCategory,
  1105.                             parts: filteredParts
  1106.                         }, categoryId);
  1107.                     }
  1108.                 }
  1109.             });
  1110.         }
  1111.         
  1112.         updateSummary();
  1113.     });
  1114.     
  1115.     // 子カテゴリセクション作成(セレクトボックス形式)
  1116.     function createChildCategorySection(category, parentCategoryId) {
  1117.         const $parentSection = $(`.category-section[data-category-id="${parentCategoryId}"]`);
  1118.         
  1119.         const childHtml = `
  1120.             <div class="category-section child-category" data-category-id="${category.id}" data-parent-id="${parentCategoryId}">
  1121.                 <div class="category-row">
  1122.                     <div class="category-header">
  1123.                         <h2 class="category-title child">└ ${category.name}</h2>
  1124.                         ${category.is_required ? '<span class="category-required-badge">必須</span>' : '<span class="category-optional-badge">任意</span>'}
  1125.                     </div>
  1126.                     
  1127.                     <select class="parts-select" 
  1128.                             data-category-id="${category.id}" 
  1129.                             data-parent-category-id="${parentCategoryId}"
  1130.                             data-required="${category.is_required ? '1' : '0'}"
  1131.                             data-category-name="${category.name}">
  1132.                         ${category.parts.map((p, index) => `
  1133.                             <option value="${p.id}" 
  1134.                                     data-price="${p.price}"
  1135.                                     data-name="${p.name}"
  1136.                                     data-child-category-id="${p.child_category_id || ''}"
  1137.                                     data-child-filter-keywords="${p.child_filter_keywords || ''}"
  1138.                                     ${index === 0 ? 'selected' : ''}>
  1139.                                 ${p.name}
  1140.                             </option>
  1141.                         `).join('')}
  1142.                     </select>
  1143.                 </div>
  1144.             </div>
  1145.         `;
  1146.         
  1147.         $parentSection.after(childHtml);
  1148.         
  1149.         // 挿入後、activeクラスを追加してスライド表示
  1150.         setTimeout(function() {
  1151.             $(`.child-category[data-parent-id="${parentCategoryId}"]`).addClass('active');
  1152.         }, 10);
  1153.     }
  1154.     
  1155.     // サマリー更新
  1156.     function updateSummary() {
  1157.         const items = [];
  1158.         const selectedCategories = new Set();
  1159.         let total = 0;
  1160.         
  1161.         // すべてのセレクトボックスを取得
  1162.         $('.parts-select').each(function() {
  1163.             const $select = $(this);
  1164.             const value = $select.val();
  1165.             
  1166.             if (value && value !== '') {
  1167.                 const selectedOption = $select.find('option:selected');
  1168.                 const categoryId = $select.data('category-id');
  1169.                 const parentCategoryId = $select.data('parent-category-id');
  1170.                 const categoryName = $select.data('category-name');
  1171.                 const price = Math.floor(parseFloat(selectedOption.data('price'))); // 小数点以下切り捨て
  1172.                 const name = selectedOption.data('name');
  1173.                 
  1174.                 items.push({
  1175.                     parts_id: value,
  1176.                     category_name: categoryName,
  1177.                     name: name,
  1178.                     price: price
  1179.                 });
  1180.                 
  1181.                 // 必須カテゴリの判定: 子カテゴリの場合は親カテゴリIDを、そうでない場合は自身のIDを使用
  1182.                 const checkCategoryId = parentCategoryId || categoryId;
  1183.                 if (checkCategoryId) {
  1184.                     selectedCategories.add(parseInt(checkCategoryId));
  1185.                 }
  1186.                 total += price;
  1187.             }
  1188.         });
  1189.         
  1190.         selectedParts = items;
  1191.         
  1192.         // 必須カテゴリがすべて選択されているかチェック
  1193.         const allRequiredSelected = requiredCategories.every(catId => selectedCategories.has(catId));
  1194.         
  1195.         if (items.length > 0) {
  1196.             // 合計金額の表示
  1197.             if (allRequiredSelected) {
  1198.                 // 内部料金計算: 合計 = パーツ合計 × (1 - 割引率/100) + 追加料金
  1199.                 let finalTotal = total * (1 - discountRate / 100) + additionalCharge;
  1200.                 finalTotal = Math.round(finalTotal); // 四捨五入
  1201.                 
  1202.                 $('#totalPrice').text(finalTotal.toLocaleString());
  1203.                 $('#stickyTotalPrice').text(finalTotal.toLocaleString()); // 固定サマリーも更新
  1204.                 $('#totalPriceArea').removeClass('hidden');
  1205.                 $('#requiredWarning').removeClass('active');
  1206.                 $('#customerForm').addClass('active');
  1207.                 $('#submitSection').addClass('active');
  1208.                 $('#purchaseSection').show();
  1209.             } else {
  1210.                 $('#totalPrice').text('---');
  1211.                 $('#stickyTotalPrice').text('---'); // 固定サマリーもクリア
  1212.                 $('#totalPriceArea').addClass('hidden');
  1213.                 $('#requiredWarning').addClass('active');
  1214.                 $('#customerForm').removeClass('active');
  1215.                 $('#submitSection').removeClass('active');
  1216.                 $('#purchaseSection').hide();
  1217.             }
  1218.         } else {
  1219.             $('#totalPrice').text('---');
  1220.             $('#stickyTotalPrice').text('---'); // 固定サマリーもクリア
  1221.             $('#totalPriceArea').addClass('hidden');
  1222.             $('#requiredWarning').removeClass('active');
  1223.             $('#customerForm').removeClass('active');
  1224.             $('#submitSection').removeClass('active');
  1225.             $('#purchaseSection').hide();
  1226.         }
  1227.     }
  1228.     
  1229.     // 送信
  1230.     $('#submitBtn').on('click', function() {
  1231.         if (selectedParts.length === 0) {
  1232.             alert('パーツを選択してください。');
  1233.             return;
  1234.         }
  1235.         
  1236.         const form = $('#estimateForm')[0];
  1237.         if (!form.checkValidity()) {
  1238.             form.reportValidity();
  1239.             return;
  1240.         }
  1241.         
  1242.         const formData = new FormData(form);
  1243.         formData.append('items', JSON.stringify(selectedParts));
  1244.         
  1245.         $('#submitBtn').prop('disabled', true);
  1246.         $('#loading').addClass('active');
  1247.         
  1248.         $.ajax({
  1249.             url: '{{ url('nz_estimate_simulator_send') }}',
  1250.             type: 'POST',
  1251.             data: formData,
  1252.             processData: false,
  1253.             contentType: false,
  1254.             headers: {
  1255.                 'x-csrf-token': $('meta[name="x-csrf-token"]').attr('content')
  1256.             },
  1257.             success: function(response) {
  1258.                 $('#loading').removeClass('active');
  1259.                 
  1260.                 if (response.success) {
  1261.                     $('.category-section, .estimate-summary, .customer-form, .submit-section').hide();
  1262.                     $('#completeMessage').addClass('active');
  1263.                 } else {
  1264.                     alert(response.message || '送信に失敗しました。');
  1265.                     $('#submitBtn').prop('disabled', false);
  1266.                 }
  1267.             },
  1268.             error: function(xhr, status, error) {
  1269.                 $('#loading').removeClass('active');
  1270.                 
  1271.                 let errorMessage = '送信中にエラーが発生しました。';
  1272.                 
  1273.                 if (xhr.responseJSON && xhr.responseJSON.message) {
  1274.                     errorMessage = xhr.responseJSON.message;
  1275.                 } else if (xhr.status === 0) {
  1276.                     errorMessage = 'ネットワークエラーが発生しました。インターネット接続を確認してください。';
  1277.                 } else if (xhr.status === 500) {
  1278.                     errorMessage = 'サーバーエラーが発生しました。しばらく時間をおいて再度お試しください。';
  1279.                 } else if (xhr.status === 403) {
  1280.                     errorMessage = 'セキュリティエラーが発生しました。ページを再読み込みして再度お試しください。';
  1281.                 }
  1282.                 
  1283.                 alert(errorMessage);
  1284.                 console.error('Error details:', {
  1285.                     status: xhr.status,
  1286.                     statusText: xhr.statusText,
  1287.                     responseText: xhr.responseText,
  1288.                     error: error
  1289.                 });
  1290.                 
  1291.                 $('#submitBtn').prop('disabled', false);
  1292.             }
  1293.         });
  1294.     });
  1295.     // 今すぐ購入ボタン
  1296.     $('#purchaseBtn').on('click', function(e) {
  1297.         e.preventDefault();
  1298.         
  1299.         console.log('購入ボタンがクリックされました');
  1300.         
  1301.         if (selectedParts.length === 0) {
  1302.             alert('パーツが選択されていません');
  1303.             return;
  1304.         }
  1305.         // パーツ合計を計算
  1306.         const partsTotal = Math.floor(selectedParts.reduce((sum, item) => sum + item.price, 0));
  1307.         
  1308.         // 内部料金調整を適用: 合計 = パーツ合計 × (1 - 割引率/100) + 追加料金
  1309.         let finalTotal = partsTotal * (1 - discountRate / 100) + additionalCharge;
  1310.         finalTotal = Math.floor(finalTotal); // 小数点以下切り捨て
  1311.         console.log('カート追加:', {
  1312.             partsTotal: partsTotal,
  1313.             discountRate: discountRate,
  1314.             additionalCharge: additionalCharge,
  1315.             finalTotal: finalTotal,
  1316.             selectedParts: selectedParts
  1317.         });
  1318.         // ボタンを無効化
  1319.         $(this).prop('disabled', true).text('商品作成中...');
  1320.         // カートに追加リクエスト
  1321.         const ajaxUrl = '/estimate_simulator/add_to_cart';
  1322.         console.log('AJAX URL:', ajaxUrl);
  1323.         
  1324.         $.ajax({
  1325.             url: ajaxUrl,
  1326.             type: 'POST',
  1327.             contentType: 'application/json',
  1328.             data: JSON.stringify({
  1329.                 selected_parts: selectedParts,
  1330.                 total_price: finalTotal
  1331.             }),
  1332.             beforeSend: function() {
  1333.                 console.log('AJAXリクエスト送信開始');
  1334.             },
  1335.             success: function(response) {
  1336.                 console.log('AJAX成功:', response);
  1337.                 if (response.success) {
  1338.                     console.log('リダイレクト開始:', response.product_url);
  1339.                     
  1340.                     // 複数の方法でリダイレクトを試行
  1341.                     try {
  1342.                         window.location.href = response.product_url;
  1343.                     } catch(e) {
  1344.                         console.error('location.href失敗:', e);
  1345.                         try {
  1346.                             window.location.assign(response.product_url);
  1347.                         } catch(e2) {
  1348.                             console.error('location.assign失敗:', e2);
  1349.                             window.location.replace(response.product_url);
  1350.                         }
  1351.                     }
  1352.                 } else {
  1353.                     alert(response.message || '商品作成に失敗しました');
  1354.                     $('#purchaseBtn').prop('disabled', false).text('🛒 すぐに購入');
  1355.                 }
  1356.             },
  1357.             error: function(xhr, status, error) {
  1358.                 console.log('AJAXエラー:', {status: xhr.status, error: error, responseText: xhr.responseText});
  1359.                 let errorMessage = '商品作成中にエラーが発生しました';
  1360.                 if (xhr.responseJSON && xhr.responseJSON.message) {
  1361.                     errorMessage = xhr.responseJSON.message;
  1362.                 }
  1363.                 alert(errorMessage);
  1364.                 $('#purchaseBtn').prop('disabled', false).text('🛒 すぐに購入');
  1365.             }
  1366.         });
  1367.     });
  1368.     
  1369.     // スマホ用:スクロール検知で固定サマリーを表示/非表示
  1370.     function initStickySummary() {
  1371.         if (window.innerWidth > 768) {
  1372.             return; // タブレット以上は無効
  1373.         }
  1374.         
  1375.         const $originalSummary = $('.estimate-summary').first();
  1376.         const $stickySummary = $('#stickySummary');
  1377.         
  1378.         if ($originalSummary.length === 0 || $stickySummary.length === 0) {
  1379.             return;
  1380.         }
  1381.         
  1382.         function checkSummaryPosition() {
  1383.             const summaryTop = $originalSummary.offset().top;
  1384.             const summaryBottom = summaryTop + $originalSummary.outerHeight();
  1385.             const scrollTop = $(window).scrollTop();
  1386.             const windowBottom = scrollTop + $(window).height();
  1387.             
  1388.             // 元のサマリーが画面外に出たら固定サマリーを表示
  1389.             if (summaryBottom < scrollTop || summaryTop > windowBottom) {
  1390.                 $stickySummary.addClass('visible');
  1391.                 $('body').addClass('sticky-summary-active');
  1392.             } else {
  1393.                 $stickySummary.removeClass('visible');
  1394.                 $('body').removeClass('sticky-summary-active');
  1395.             }
  1396.         }
  1397.         
  1398.         $(window).on('scroll', checkSummaryPosition);
  1399.         $(window).on('resize', checkSummaryPosition);
  1400.         
  1401.         // 初回チェック(少し遅延させる)
  1402.         setTimeout(checkSummaryPosition, 500);
  1403.     }
  1404.     
  1405.     // DOMが完全に読み込まれた後に初期化
  1406.     initStickySummary();
  1407. });
  1408. </script>
  1409. {% endblock %}