1<!DOCTYPE html> 2<html lang="ja"> 3<head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <meta name="robots" content="noindex, nofollow" /> 7 <title>ã¨ã¼ã¸ã§ã³ã診æ & 䏿¬ç»é²ãã¼ã«</title> 8 <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>ð</text></svg>" /> 9
9<script src="https://cdn.tailwindcss.com/3.4.17"></script>
9 10
10<script src="https://unpkg.com/[email protected]/umd/react.production.min.js" crossorigin></script>
10 11
11<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js" crossorigin></script>
11 12
12<script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
12 13
13<script src="https://cdn.jsdelivr.net/npm/@supabase/[email protected]/dist/umd/supabase.min.js"></script>
13 14
14<script> 15 const _supabaseUrl = 'https://cugaikmapidrwqmwlxzp.supabase.co'; 16 const _supabaseKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImN1Z2Fpa21hcGlkcndxbXdseHpwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzM0NzAxNTMsImV4cCI6MjA4OTA0NjE1M30.znDqwz2J4uGUcaHC5F2HoPIMOuHo4r7iCprqWGl7PMY'; 17 const _supabase = supabase.createClient(_supabaseUrl, _supabaseKey); 18 </script>
18 19
19<script> 20 tailwind.config = { 21 theme: { 22 extend: { 23 colors: { 24 brand: { 25 gold: '#D4AF37', 26 'gold-dark': '#B8960C', 27 orange: '#E8735A', 28 'orange-dark': '#D4573E', 29 } 30 }, 31 animation: { 32 'fade-in': 'fadeIn 0.5s ease-out', 33 'fade-in-up': 'fadeInUp 0.5s ease-out', 34 'slide-in': 'slideIn 0.4s ease-out', 35 'pulse-once': 'pulseOnce 0.3s ease-out', 36 'pop-in': 'popIn 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55)', 37 'bounce-light': 'bounceLight 0.4s ease-out', 38 }, 39 keyframes: { 40 fadeIn: { '0%': { opacity: '0' }, '100%': { opacity: '1' } }, 41 fadeInUp: { '0%': { opacity: '0', transform: 'translateY(20px)' }, '100%': { opacity: '1', transform: 'translateY(0)' } }, 42 slideIn: { '0%': { opacity: '0', transform: 'translateX(30px)' }, '100%': { opacity: '1', transform: 'translateX(0)' } }, 43 pulseOnce: { '0%': { transform: 'scale(1)' }, '50%': { transform: 'scale(1.05)' }, '100%': { transform: 'scale(1)' } }, 44 popIn: { '0%': { transform: 'scale(0)' }, '100%': { transform: 'scale(1)' } }, 45 bounceLight: { '0%': { transform: 'scale(1)' }, '40%': { transform: 'scale(1.08)' }, '100%': { transform: 'scale(1)' } }, 46 } 47 } 48 } 49 } 50 </script>
50 51 <link rel="preconnect" href="https://fonts.googleapis.com"> 52 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 53 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Noto+Sans+JP:wght@400;500;600;700;800&display=swap" rel="stylesheet"> 54 <style> 55 * { box-sizing: border-box; } 56 body { margin: 0; padding: 0; font-family: 'Inter', 'Noto Sans JP', -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", sans-serif; background: transparent; } 57 .stagger-1 { animation-delay: 0.1s; } 58 .stagger-2 { animation-delay: 0.2s; } 59 .stagger-3 { animation-delay: 0.3s; } 60 .stagger-4 { animation-delay: 0.4s; } 61 .stagger-5 { animation-delay: 0.5s; } 62 /* ===== Mode Select Cards ===== */ 63 </style> 64</head> 65<body> 66 <div id="root"></div> 67 68
68<script src="/data/agents-master.js"></script>
68 69
69<script type="text/babel" data-presets="react"> 70 /* @jsxRuntime classic */ 71 const { useState, useReducer, useCallback, useMemo, useEffect, useRef } = React; 72 73 // ============================================================ 74 // DATA: ã¨ã¼ã¸ã§ã³ããã¹ã¿ã¼ãã¼ã¿ï¼å¤é¨ãã¡ã¤ã«ããèªè¾¼ï¼ 75 // ============================================================ 76 const AGENTS_MASTER = window.AGENTS_MASTER; 77 78 // ============================================================ 79 // DATA: è¨äºå¥è¨å®ï¼URLãã©ã¡ã¼ã¿ã§åæ¿ï¼ 80 // ============================================================ 81 const ARTICLE_CONFIGS = { 82 strategy: { 83 articleId: "strategy-consul-agent", title: "æ¦ç¥ã³ã³ãµã«æ¡ä»¶", 84 agents: [ 85 { id: "senjin", priority: 1 }, 86 { id: "highperfo", priority: 2 }, 87 { id: "lancers_pro", priority: 3 }, 88 { id: "freeconsultant_jp", priority: 10 }, 89 { id: "proconnect", priority: 5 }, 90 { id: "freeconsul_biz", priority: 6 }, 91 { id: "strategy_bank", priority: 7 }, 92 { id: "forpro", priority: 8 }, 93 { id: "pro_hub", priority: 9 } 94 ] 95 }, 96 biz_consul: { 97 articleId: "biz-consul-agent", title: "æ¥åã³ã³ãµã«æ¡ä»¶", 98 agents: [ 99 { id: "senjin", priority: 1 }, 100 { id: "highperfo", priority: 2 }, 101 { id: "lancers_pro", priority: 3 }, 102 { id: "freeconsultant_jp", priority: 11 }, 103 { id: "pmo_jobs", priority: 5 }, 104 { id: "proconnect", priority: 6 }, 105 { id: "freeconsul_biz", priority: 7 }, 106 { id: "strategy_bank", priority: 8 }, 107 { id: "forpro", priority: 9 }, 108 { id: "pro_hub", priority: 10 } 109 ] 110 }, 111 it_consul: { 112 articleId: "it-consul-agent", title: "ITã³ã³ãµã«æ¡ä»¶", 113 agents: [ 114 { id: "senjin_it", priority: 1 }, 115 { id: "highperfo", priority: 2 }, 116 { id: "freeconsultant_jp", priority: 8 }, 117 { id: "lancers_pro", priority: 4 }, 118 { id: "pmo_jobs", priority: 5 }, 119 { id: "proconnect", priority: 6 }, 120 { id: "pro_hub", priority: 7 } 121 ] 122 }, 123 engineering: { 124 articleId: "engineer-agent", title: "ã¨ã³ã¸ãã¢ãªã³ã°æ¡ä»¶", 125 agents: [ 126 { id: "levatech", priority: 1 }, 127 { id: "midworks", priority: 2 }, 128 { id: "techstock", priority: 3 }, 129 { id: "techfree", priority: 4 }, 130 { id: "techadapt", priority: 5 }, 131 { id: "itpro_partners", priority: 6 }, 132 { id: "bigdata_navi", priority: 7 } 133 ] 134 }, 135 sap: { 136 articleId: "sap-agent", title: "SAPæ¡ä»¶", 137 agents: [ 138 { id: "senjin_it", priority: 1 }, 139 { id: "quickflow", priority: 2 }, 140 { id: "highperfo", priority: 3 }, 141 { id: "levatech", priority: 4 }, 142 { id: "lancers_pro", priority: 5 }, 143 { id: "freeconsultant_jp", priority: 8 }, 144 { id: "proconnect", priority: 7 }, 145 { id: "techstock", priority: 8 }, 146 { id: "forpro", priority: 9 } 147 ] 148 }, 149 security: { 150 articleId: "security-agent", title: "ã»ãã¥ãªãã£æ¡ä»¶", 151 agents: [ 152 { id: "senjin_security", priority: 1 }, 153 { id: "levatech", priority: 2 }, 154 { id: "highperfo", priority: 3 }, 155 { id: "freeconsultant_jp", priority: 7 }, 156 { id: "lancers_pro", priority: 5 }, 157 { id: "proconnect", priority: 6 }, 158 { id: "techstock", priority: 7 }, 159 { id: "bigdata_navi", priority: 8 } 160 ] 161 }, 162 tenshoku_strategy_consul: { 163 articleId: "tenshoku-consul-agent", title: "æ¦ç¥ã»çµå¶ã³ã³ãµã«è»¢è·", 164 agents: [ 165 { id: "myvision", priority: 1 }, 166 { id: "yhc", priority: 2 }, 167 { id: "axis", priority: 3 }, 168 { id: "concord", priority: 4 }, 169 { id: "assign", priority: 5 }, 170 { id: "samurai", priority: 6 }, 171 { id: "kotora", priority: 7 }, 172 { id: "laughlogic", priority: 8 } 173 ] 174 }, 175 tenshoku_it_consul: { 176 articleId: "tenshoku-it-consul-agent", title: "ITã»DXã³ã³ãµã«è»¢è·", 177 agents: [ 178 { id: "myvision", priority: 1 }, 179 { id: "axis", priority: 2 }, 180 { id: "yhc", priority: 3 }, 181 { id: "assign", priority: 4 }, 182 { id: "kotora", priority: 5 }, 183 { id: "samurai", priority: 6 }, 184 { id: "sap_tenshoku", priority: 7 } 185 ] 186 }, 187 tenshoku_startup: { 188 articleId: "tenshoku-startup-agent", title: "ã¹ã¿ã¼ãã¢ãã転è·", 189 agents: [ 190 { id: "myvision", priority: 1 }, 191 { id: "yhc", priority: 2 }, 192 { id: "axis", priority: 3 }, 193 { id: "assign", priority: 4 }, 194 { id: "samurai", priority: 5 }, 195 { id: "miracareer", priority: 6 }, 196 { id: "owen_career", priority: 7 }, 197 { id: "neocareer", priority: 8 } 198 ] 199 }, 200 tenshoku_enterprise: { 201 articleId: "tenshoku-enterprise-agent", title: "大伿¥ã»äºæ¥ä¼ç¤¾è»¢è·", 202 agents: [ 203 { id: "myvision", priority: 1 }, 204 { id: "yhc", priority: 2 }, 205 { id: "axis", priority: 3 }, 206 { id: "assign", priority: 4 }, 207 { id: "samurai", priority: 5 }, 208 { id: "kotora", priority: 6 }, 209 { id: "hr_career_agent", priority: 7 }, 210 { id: "newma", priority: 8 }, 211 { id: "twinpro", priority: 9 }, 212 { id: "miracareer", priority: 10 }, 213 { id: "owen_career", priority: 11 }, 214 { id: "neocareer", priority: 12 } 215 ] 216 }, 217 tenshoku_engineer: { 218 articleId: "tenshoku-engineer-agent", title: "ã¨ã³ã¸ãã¢è»¢è·", 219 agents: [ 220 { id: "techgo", priority: 1 }, 221 { id: "techgate_expert", priority: 2 }, 222 { id: "meiko_career", priority: 3 }, 223 { id: "shanai_se", priority: 4 }, 224 { id: "techclips", priority: 5 }, 225 { id: "sap_tenshoku", priority: 6 }, 226 { id: "raku_partners", priority: 7 } 227 ] 228 }, 229 tenshoku_ma: { 230 articleId: "tenshoku-ma-agent", title: "M&Aã»è²¡åã¢ããã¤ã¶ãªã¼è»¢è·", 231 agents: [ 232 { id: "newma", priority: 1 }, 233 { id: "myvision", priority: 2 }, 234 { id: "yhc", priority: 3 }, 235 { id: "concord", priority: 4 }, 236 { id: "ma_beginners", priority: 5 }, 237 { id: "kotora", priority: 6 } 238 ] 239 }, 240 tenshoku_ibd_pe: { 241 articleId: "tenshoku-ibd-pe-agent", title: "æè³éè¡ã»PEãã¡ã³ã転è·", 242 agents: [ 243 { id: "yhc", priority: 1 }, 244 { id: "newma", priority: 2 }, 245 { id: "myvision", priority: 3 }, 246 { id: "concord", priority: 4 }, 247 { id: "kotora", priority: 5 }, 248 { id: "ma_beginners", priority: 6 } 249 ] 250 }, 251 tenshoku_accounting: { 252 articleId: "tenshoku-accounting-agent", title: "ä¼è¨ã»ç¨å転è·", 253 agents: [ 254 { id: "twinpro", priority: 1 }, 255 { id: "myvision", priority: 2 }, 256 { id: "kotora", priority: 3 }, 257 { id: "yhc", priority: 4 } 258 ] 259 }, 260 tenshoku_20s: { 261 articleId: "tenshoku-20s-agent", title: "20代転è·", 262 agents: [ 263 { id: "miracareer", priority: 1 }, 264 { id: "owen_career", priority: 2 }, 265 { id: "neocareer", priority: 3 }, 266 { id: "assign", priority: 4 }, 267 { id: "myvision", priority: 5 }, 268 { id: "raku_partners", priority: 6 } 269 ] 270 }, 271 tenshoku_sales: { 272 articleId: "tenshoku-sales-agent", title: "å¶æ¥ã»ã»ã¼ã«ã¹è»¢è·", 273 agents: [ 274 { id: "myvision", priority: 1 }, 275 { id: "assign", priority: 2 }, 276 { id: "yhc", priority: 3 }, 277 { id: "samurai", priority: 4 }, 278 { id: "newma", priority: 5 }, 279 { id: "miracareer", priority: 6 }, 280 { id: "owen_career", priority: 7 }, 281 { id: "neocareer", priority: 8 } 282 ] 283 }, 284 tenshoku_marketing: { 285 articleId: "tenshoku-marketing-agent", title: "ãã¼ã±ãã£ã³ã°è»¢è·", 286 agents: [ 287 { id: "myvision", priority: 1 }, 288 { id: "axis", priority: 2 }, 289 { id: "assign", priority: 3 }, 290 { id: "yhc", priority: 4 }, 291 { id: "samurai", priority: 5 }, 292 { id: "kotora", priority: 6 } 293 ] 294 }, 295 tenshoku_other: {
296 articleId: "tenshoku-other-agent", title: "ãã®ä»è»¢è·", 297 agents: [ 298 { id: "myvision", priority: 1 }, 299 { id: "axis", priority: 2 }, 300 { id: "assign", priority: 3 }, 301 { id: "yhc", priority: 4 }, 302 { id: "samurai", priority: 5 }, 303 { id: "kotora", priority: 6 }, 304 { id: "miracareer", priority: 7 }, 305 { id: "owen_career", priority: 8 }, 306 { id: "neocareer", priority: 9 } 307 ] 308 }, 309 tenshoku_consul_firm: { 310 articleId: "tenshoku-consul-firm", title: "ã³ã³ãµã«ãã¡ã¼ã 転è·", 311 agents: [ 312 { id: "myvision", priority: 1 }, 313 { id: "yhc", priority: 2 }, 314 { id: "axis", priority: 3 }, 315 { id: "concord", priority: 4 }, 316 { id: "assign", priority: 5 }, 317 { id: "samurai", priority: 6 }, 318 { id: "kotora", priority: 7 }, 319 { id: "laughlogic", priority: 8 } 320 ] 321 }, 322 tenshoku_foreign: { 323 articleId: "tenshoku-foreign", title: "å¤è³ç³»è»¢è·", 324 agents: [ 325 { id: "myvision", priority: 1 }, 326 { id: "yhc", priority: 2 }, 327 { id: "axis", priority: 3 }, 328 { id: "concord", priority: 4 }, 329 { id: "assign", priority: 5 }, 330 { id: "samurai", priority: 6 }, 331 { id: "kotora", priority: 7 } 332 ] 333 }, 334 tenshoku_any: { 335 articleId: "tenshoku-any", title: "ç·å転è·", 336 agents: [ 337 { id: "myvision", priority: 1 }, 338 { id: "assign", priority: 2 }, 339 { id: "yhc", priority: 3 }, 340 { id: "samurai", priority: 4 } 341 ] 342 } 343 }; 344 345 // localStorageã«ä¿åæ¸ã¿ã®è¨äºå¥è¨å®ãããã°ä¸æ¸ãï¼ç®¡çç»é¢ã®å¤æ´ãå³åæ ï¼ 346 // 注: Supabase APIããã®èªã¿è¾¼ã¿ã¯å¾è¿°ã® _applyRemoteSettings() ã§éåæã«è¡ã 347 const _savedConfigs = (() => { 348 try { return JSON.parse(localStorage.getItem('af_configs')); } catch(e) { return null; } 349 })(); 350 if (_savedConfigs) { 351 Object.keys(_savedConfigs).forEach(key => { 352 if (ARTICLE_CONFIGS[key]) { 353 ARTICLE_CONFIGS[key] = { ...ARTICLE_CONFIGS[key], ..._savedConfigs[key] }; 354 } 355 }); 356 } 357 358 // ============================================================ 359 // DATA: 診æè³ªåï¼localStorageããèªã¿è¾¼ã¿ããã©ã¼ã«ããã¯ã¯ãã¼ãã³ã¼ãï¼ 360 // ============================================================ 361 // Q3ãã¼ã¹ãã®æ§å½¢å¼ï¼q3val ã®ã¿ï¼â æ°å½¢å¼ï¼q2val:q3val è¤åãã¼ï¼ã«ç§»è¡ 362 function _migrateQ3Boosts(savedQuestions) { 363 if (!savedQuestions || !savedQuestions._Q3_AGENT_BOOSTS) return savedQuestions; 364 const optsByQ2 = savedQuestions._Q3_OPTIONS_BY_Q2 || {}; 365 const oldBoosts = savedQuestions._Q3_AGENT_BOOSTS; 366 const newBoosts = {}; 367 Object.keys(oldBoosts).forEach(key => { 368 if (key.includes(':')) newBoosts[key] = [...(oldBoosts[key] || [])]; 369 }); 370 Object.keys(oldBoosts).forEach(key => { 371 if (key.includes(':')) return; 372 if (key === 'any') return; 373 const q3val = key; 374 const boostList = oldBoosts[key] || []; 375 Object.keys(optsByQ2).forEach(q2val => { 376 const opts = optsByQ2[q2val] || []; 377 if (opts.some(o => o.value === q3val)) { 378 const composite = `${q2val}:${q3val}`; 379 if (!newBoosts[composite]) newBoosts[composite] = [...boostList]; 380 } 381 }); 382 }); 383 return { ...savedQuestions, _Q3_AGENT_BOOSTS: newBoosts }; 384 } 385 386 const _savedQuestions = (() => { 387 try { 388 const raw = JSON.parse(localStorage.getItem('af_questions')); 389 return _migrateQ3Boosts(raw); 390 } catch(e) { return null; } 391 })(); 392 393 // Q1: modeå¥ã«ç¶æ³ãèã 394 const Q1_FREELANCE = _savedQuestions?.Q1_FREELANCE || { 395 id: "q1", title: "ä»ã®ç¶æ³ã¯ï¼", 396 subtitle: "æãè¿ããã®ããé¸ã³ãã ãã", 397 type: "single", 398 options: [ 399 { value: "searching", label: "ããªã¼ã©ã³ã¹ã¨ãã¦æ¡ä»¶ãæ¢ãã¦ãã" },
400 { value: "considering", label: "ç¬ç«ãæ¤è¨ä¸ï¼ã¾ã ä¼ç¤¾å¡ï¼" }, 401 { value: "ending_soon", label: "ä»ã®æ¡ä»¶ãçµãããã / æ¬¡ãæ¢ããã" }, 402 { value: "want_better", label: "å価ãä¸ããã / ã¨ã¼ã¸ã§ã³ããå¤ããã" }, 403 ] 404 }; 405 406 const Q1_TENSHOKU = _savedQuestions?.Q1_TENSHOKU || { 407 id: "q1", title: "ããªãã®çµé¨ã¬ãã«ã¯ï¼", 408 subtitle: "æãè¿ããã®ããé¸ã³ãã ãã", 409 type: "single", 410 options: [ 411 { value: "tenshoku_20s", label: "20代ã»ç¬¬äºæ°åï¼ç¤¾ä¼äººçµé¨3å¹´æªæºï¼" }, 412 { value: "young_mid", label: "è¥æãä¸å ï¼çµé¨3ã10å¹´ï¼" }, 413 { value: "senior", label: "ããã©ã³ã»ç®¡çè·ï¼çµé¨10年以ä¸ï¼" }, 414 { value: "career_change", label: "ãã£ãªã¢ãã§ã³ã¸ï¼ç°æ¥ç¨®ããã®è»¢è·ï¼" }, 415 ] 416 }; 417 418 // Q2: Q1ã®åçã«å¿ãã¦é¸æè¢ãå¤ãã 419 const Q2_FREELANCE = _savedQuestions?.Q2_FREELANCE || { 420 id: "q2", title: "ã©ããªæ¡ä»¶ãããããï¼", 421 subtitle: "2ã¤ã¾ã§é¸æã§ãã¾ã", 422 type: "multi", maxSelect: 2, 423 options: [ 424 { value: "strategy", label: "æ¦ç¥ã³ã³ãµã«ï¼æ¦ç¥çå®ã»æ°è¦äºæ¥ã»M&Aãªã©ï¼" }, 425 { value: "biz_consul", label: "æ¥åã³ã³ãµã«ï¼æ¥åæ¹åã»BPRãªã©ï¼" }, 426 { value: "it_consul", label: "ITã³ã³ãµã«ï¼ã·ã¹ãã å°å ¥ã»PMOãªã©ï¼" }, 427 { value: "engineering", label: "ã¨ã³ã¸ãã¢ãªã³ã°ï¼éçº/ã¤ã³ãã©ãªã©ï¼" }, 428 { value: "sap", label: "SAPæ¡ä»¶" }, 429 { value: "security", label: "ã»ãã¥ãªãã£æ¡ä»¶" }, 430 ] 431 }; 432 433 const Q2_TENSHOKU = _savedQuestions?.Q2_TENSHOKU || { 434 id: "q2", title: "叿ã®è·ç¨®ã¯ï¼", 435 subtitle: "2ã¤ã¾ã§é¸æã§ãã¾ã", 436 type: "multi", maxSelect: 2, 437 options: [ 438 { value: "tenshoku_strategy_consul", label: "æ¦ç¥ã»çµå¶ã³ã³ãµã«ã¿ã³ã" }, 439 { value: "tenshoku_it_consul", label: "ITã»DXã³ã³ãµã«ã¿ã³ã" }, 440 { value: "tenshoku_ma", label: "M&Aã»è²¡åã¢ããã¤ã¶ãªã¼ï¼FAS/IBD/PEï¼" }, 441 { value: "tenshoku_engineer", label: "ã¨ã³ã¸ãã¢ã»ITæè¡è·" }, 442 { value: "tenshoku_sales", label: "å¶æ¥ã»ã»ã¼ã«ã¹" }, 443 { value: "tenshoku_marketing", label: "ãã¼ã±ãã£ã³ã°" }, 444 { value: "tenshoku_accounting", label: "ä¼è¨ã»ç¨åã»çµç" }, 445 { value: "tenshoku_other", label: "ãã®ä»" }, 446 ] 447 }; 448 449 // Q3: ããªã¼ã©ã³ã¹ã¨è»¢è·ã§å¾®å¦ã«ã©ãã«ãå¤ãã 450 const Q3_FREELANCE = _savedQuestions?.Q3_FREELANCE || { 451 id: "q3", title: "ä½ãä¸çªéè¦ããï¼", 452 subtitle: "1ã¤ã ããé¸ã³ãã ãã", 453 type: "single", 454 options: [ 455 { value: "high_rate", label: "ã¨ã«ããé«åä¾¡ï¼æ150ä¸ãï¼" }, 456 { value: "stability", label: "æ¡ä»¶ãéåããªãå®å®æ" }, 457 { value: "speed", label: "ããã«åç»ã§ããã¹ãã¼ã" }, 458 { value: "career_up", label: "ãã£ãªã¢ã¢ããã»ã¹ãã«ã¢ãã" }, 459 ] 460 }; 461 462 // Q3ã¯Q2ã®é¸æã«å¿ãã¦åçã«é¸æè¢ãå¤ããï¼Q3_OPTIONS_BY_Q2åç §ï¼ 463 const Q3_TENSHOKU = _savedQuestions?.Q3_TENSHOKU || { 464 id: "q3", title: "叿ã®ä¼æ¥ã¿ã¤ãã¯ï¼", 465 subtitle: "1ã¤ãé¸ã³ãã ãã", 466 type: "single", 467 options: [ 468 { value: "any", label: "ãã ãããªã" }, 469 ] 470 }; 471 472 // Q2鏿âQ3鏿è¢ã®åçãããã³ã°ï¼ç®¡çç»é¢ã§ä¸æ¸ãå¯è½ï¼ 473 const Q3_OPTIONS_BY_Q2 = _savedQuestions?._Q3_OPTIONS_BY_Q2 || { 474 tenshoku_strategy_consul: [ 475 { value: "mbb", label: "MBBã»æ¦ç¥ãã¡ã¼ã " }, 476 { value: "big4", label: "Big4ã»ç·åãã¡ã¼ã " }, 477 { value: "boutique", label: "ããã£ãã¯ç³»" }, 478 { value: "enterprise_strategy", label: "äºæ¥ä¼ç¤¾ï¼æ¦ç¥ãã¸ã·ã§ã³ï¼" }, 479 { value: "any", label: "ãã ãããªã" }, 480 ], 481 tenshoku_it_consul: [ 482 { value: "accenture_big4_dx", label: "ã¢ã¯ã»ã³ãã¥ã¢ã»Big4 DX" }, 483 { value: "foreign_it", label: "å¤è³ITç³»ã³ã³ãµã«" }, 484 { value: "japan_sier", label: "æ¥ç³»SIer䏿µ" }, 485 { value: "enterprise_dx", label: "äºæ¥ä¼ç¤¾ï¼DXæ¨é²ï¼" }, 486 { value: "any", label: "ãã ãããªã" }, 487 ], 488 tenshoku_ma: [ 489 { value: "foreign_ibd", label: "å¤è³æè³éè¡ï¼IBDï¼" }, 490 { value: "japan_ibd", label: "æ¥ç³»è¨¼å¸IBD" }, 491 { value: "fas", label: "FASï¼Big4ã¢ããã¤ã¶ãªã¼ï¼" }, 492 { value: "pe_fund", label: "PEãã¡ã³ã" }, 493 { value: "enterprise_ma", label: "äºæ¥ä¼ç¤¾ï¼M&A室ï¼" }, 494 { value: "any", label: "ãã ãããªã" }, 495 ], 496 tenshoku_engineer: [ 497 { value: "mega_venture", label: "ã¡ã¬ãã³ãã£ã¼" }, 498 { value: "startup", label: "ã¹ã¿ã¼ãã¢ãã" }, 499 { value: "foreign_it_big", label: "å¤è³ITï¼GAFAç³»ï¼" }, 500 { value: "japan_large", label: "æ¥ç³»å¤§æ" }, 501 { value: "sier", label: "SIer" }, 502 { value: "any", label: "ãã ãããªã" }, 503 ], 504 tenshoku_sales: [ 505 { value: "foreign", label: "å¤è³ç³»" }, 506 { value: "japan_large", label: "æ¥ç³»å¤§æ" }, 507 { value: "startup", label: "ã¹ã¿ã¼ãã¢ããã»ãã³ãã£ã¼" }, 508 { value: "any", label: "ãã ãããªã" }, 509 ], 510 tenshoku_marketing: [ 511 { value: "foreign", label: "å¤è³ç³»" }, 512 { value: "japan_large", label: "æ¥ç³»å¤§æ" }, 513 { value: "startup", label: "ã¹ã¿ã¼ãã¢ããã»ãã³ãã£ã¼" }, 514 { value: "any", label: "ãã ãããªã" }, 515 ], 516 tenshoku_accounting: [ 517 { value: "big4_audit", label: "Big4ã»ç£æ»æ³äºº" }, 518 { value: "fas", label: "FAS" }, 519 { value: "enterprise_finance", label: "äºæ¥ä¼ç¤¾ï¼çµç財åï¼" }, 520 { value: "tax_firm", label: "ç¨ç士æ³äºº" }, 521 { value: "any", label: "ãã ãããªã" }, 522 ], 523 tenshoku_other: [ 524 { value: "foreign", label: "å¤è³ç³»" }, 525 { value: "japan_large", label: "æ¥ç³»å¤§æ" }, 526 { value: "startup", label: "ã¹ã¿ã¼ãã¢ãã" }, 527 { value: "any", label: "ãã ãããªã" }, 528 ], 529 }; 530 531 // Q3ãã¼ã¹ã: ãã¼ã¯ãq2val:q3valãã®è¤åãã¼ï¼Q2ã«ãã´ãªãã¨ã«ç¬ç«ãã¦è¨å®å¯è½ï¼ 532 // 管çç»é¢ã§ä¸æ¸ãå¯è½ 533 const Q3_AGENT_BOOSTS = _savedQuestions?._Q3_AGENT_BOOSTS || { 534 // æ¦ç¥ã³ã³ãµã« 535 "tenshoku_strategy_consul:mbb": ["myvision", "concord", "axis"], 536 "tenshoku_strategy_consul:big4": ["myvision", "axis", "assign"], 537 "tenshoku_strategy_consul:boutique": ["laughlogic", "kotora"], 538 "tenshoku_strategy_consul:enterprise_strategy": ["kotora", "axis", "samurai"], 539 // IT/DXã³ã³ãµã« 540 "tenshoku_it_consul:accenture_big4_dx": ["myvision", "axis", "assign"], 541 "tenshoku_it_consul:foreign_it": ["concord", "samurai", "myvision"], 542 "tenshoku_it_consul:japan_sier": ["kotora"], 543 "tenshoku_it_consul:enterprise_dx": ["kotora", "samurai", "axis"], 544 // M&A 545 "tenshoku_ma:foreign_ibd": ["yhc", "concord", "newma"], 546 "tenshoku_ma:japan_ibd": ["yhc", "newma", "kotora"], 547 "tenshoku_ma:fas": ["myvision", "yhc", "ma_beginners"], 548 "tenshoku_ma:pe_fund": ["yhc", "concord", "kotora"], 549 "tenshoku_ma:enterprise_ma": ["kotora", "newma", "ma_beginners"], 550 // ã¨ã³ã¸ã㢠551 "tenshoku_engineer:mega_venture": ["techgo", "techgate_expert"], 552 "tenshoku_engineer:startup": ["techgo", "miracareer", "owen_career", "neocareer"], 553 "tenshoku_engineer:foreign_it_big": ["techgate_expert", "s
553amurai"], 554 "tenshoku_engineer:japan_large": ["meiko_career", "kotora"], 555 "tenshoku_engineer:sier": ["meiko_career"], 556 // å¶æ¥ 557 "tenshoku_sales:foreign": ["samurai", "concord", "myvision"], 558 "tenshoku_sales:japan_large": ["meiko_career", "kotora"], 559 "tenshoku_sales:startup": ["assign", "miracareer", "myvision", "owen_career", "neocareer"], 560 // ãã¼ã±ãã£ã³ã° 561 "tenshoku_marketing:foreign": ["samurai", "concord", "myvision"], 562 "tenshoku_marketing:japan_large": ["meiko_career", "kotora"], 563 "tenshoku_marketing:startup": ["assign", "miracareer", "myvision", "owen_career", "neocareer"], 564 // ä¼è¨ 565 "tenshoku_accounting:big4_audit": ["twinpro", "myvision", "kotora"], 566 "tenshoku_accounting:fas": ["twinpro", "yhc", "ma_beginners"], 567 "tenshoku_accounting:enterprise_finance": ["twinpro", "kotora"], 568 "tenshoku_accounting:tax_firm": ["twinpro", "kotora"], 569 // ãã®ä» 570 "tenshoku_other:foreign": ["samurai", "myvision"], 571 "tenshoku_other:japan_large": ["meiko_career", "kotora"], 572 "tenshoku_other:startup": ["miracareer", "owen_career", "neocareer"], 573 }; 574 575 const Q4_TENSHOKU = _savedQuestions?.Q4_TENSHOKU || { 576 id: "q4", title: "転è·ã§ä½ãä¸çªéè¦ããï¼", 577 subtitle: "1ã¤ã ããé¸ã³ãã ãã", 578 type: "single", 579 options: [ 580 { value: "high_rate", label: "å¹´åã¢ããï¼800ä¸ãï¼" }, 581 { value: "stability", label: "å®å®ããç°å¢ã»ç¦å©åç" }, 582 { value: "speed", label: "ããã«è»¢è·ã§ããã¹ãã¼ã" }, 583 { value: "career_up", label: "ãã£ãªã¢ã¢ããã»ã¹ãã«ã¢ãã" }, 584 ] 585 }; 586 587 // modeå¤å®: URLãã©ã¡ã¼ã¿ or ãã¹ã§äºåæå®ãããã°ããã使ãããªããã°nullï¼é¸æç»é¢ãè¡¨ç¤ºï¼ 588 function getInitialMode() { 589 const params = new URLSearchParams(window.location.search); 590 if (params.get('mode') === 'tenshoku') return 'tenshoku'; 591 if (params.get('mode') === 'freelance') return 'freelance'; 592 if (params.get('mode') === 'tekisei') return 'tekisei'; 593 if (window.location.pathname.includes('tenshoku')) return 'tenshoku'; 594 if (window.location.pathname.includes('tekisei')) return 'tekisei'; 595 return null; // æªé¸æ â 鏿ç»é¢ã表示 596 } 597 // 徿¹äºæ: getMode()ã¯å»æ¢ããªããAppå ã§stateã使ã 598 function getMode() { 599 return getInitialMode() || 'freelance'; 600 } 601 602 // modeã«å¿ãã¦QUESTIONSãçæï¼3åæ§æï¼ 603 function getQuestions(mode) { 604 const isTenshoku = mode === 'tenshoku'; 605 if (isTenshoku) { 606 return [Q1_TENSHOKU, Q2_TENSHOKU, Q3_TENSHOKU, Q4_TENSHOKU]; 607 } 608 return [Q1_FREELANCE, Q2_FREELANCE, Q3_FREELANCE]; 609 } 610 611 // ============================================================ 612 // DATA: ãã¸ãã¯è¨å®ï¼localStorageããèªã¿è¾¼ã¿ããã©ã¼ã«ããã¯ã¯ãã¼ãã³ã¼ãï¼ 613 // ============================================================ 614 const _savedLogic = (() => { 615 try { return JSON.parse(localStorage.getItem('af_logic')); } catch(e) { return null; } 616 })(); 617 618 const Q2_REQUIRED_AGENTS = _savedLogic?.requiredAgents || { 619 strategy: ["senjin", "highperfo", "lancers_pro", "freeconsultant_jp", "proconnect", "freeconsul_biz", "strategy_bank", "forpro", "pro_hub"], 620 biz_consul: ["senjin", "highperfo", "lancers_pro", "freeconsultant_jp", "pmo_jobs", "proconnect", "freeconsul_biz", "strategy_bank", "forpro", "pro_hub"], 621 it_consul: ["senjin_it", "highperfo", "freeconsultant_jp", "lancers_pro", "pmo_jobs", "proconnect", "pro_hub"], 622 engineering: ["levatech", "midworks", "techstock", "techfree", "techadapt", "itpro_partners", "bigdata_navi"], 623 sap: ["senjin_it", "highperfo", "levatech", "lancers_pro", "freeconsultant_jp", "proconnect", "techstock", "forpro"], 624 security: ["senjin_security", "levatech", "highperfo", "freeconsultant_jp", "lancers_pro", "proconnect", "techstock", "bigdata_navi"], 625 tenshoku_strategy_consul: ["myvision", "yhc", "axis", "concord", "assign", "samurai", "kotora", "laughlogic"], 626 tenshoku_it_consul: ["myvision", "axis", "yhc", "assign", "kotora", "samurai", "sap_tenshoku"], 627 tenshoku_engineer: ["techgo", "techgate_expert", "meiko_career", "shanai_se", "techclips", "sap_tenshoku", "raku_partners"], 628 tenshoku_ma: ["newma", "myvision", "yhc", "concord", "ma_beginners", "kotora"], 629 tenshoku_accounting: ["twinpro", "myvision", "kotora", "yhc"], 630 tenshoku_20s: ["miracareer", "owen_career", "neocareer", "assign", "myvision", "raku_partners"], 631 young_mid: ["myvision", "yhc", "axis", "assign", "samurai", "kotora", "techgo", "techgate_expert"], 632 senior: ["myvision", "yhc", "axis", "concord", "kotora", "samurai", "hr_career_agent"], 633 career_change: ["miracareer", "owen_career", "neocareer", "myvision", "assign", "raku_partners"], 634 tenshoku_sales: ["myvision", "assign", "yhc", "samurai", "newma", "miracareer", "owen_career", "neocareer"], 635 tenshoku_marketing: ["myvision", "axis", "assign", "yhc", "samurai", "kotora"], 636 tenshoku_other: ["myvision", "axis", "assign", "yhc", "samurai", "kotora", "miracareer", "owen_career", "neocareer"], 637 tenshoku_consul_firm: ["myvision", "yhc", "axis", "concord", "assign", "samurai", "kotora", "laughlogic"], 638 tenshoku_ibd_pe: ["yhc", "newma", "myvision", "concord", "kotora", "ma_beginners"], 639 tenshoku_startup: ["myvision", "yhc", "axis", "assign", "samurai", "miracareer", "owen_career", "neocareer"], 640 tenshoku_enterprise: ["myvision", "yhc", "axis", "assign", "samurai", "kotora", "hr_career_agent", "newma", "twinpro", "miracareer", "owen_career", "neocareer"], 641 tenshoku_foreign: ["myvision", "yhc", "axis", "concord", "assign", "samurai", "kotora"], 642 tenshoku_any: ["myvision", "assign", "yhc", "samurai"], 643 }; 644 645 const _domainMap = _savedLogic?.domainMap || { 646 strategy: ["strategy", "bizdev", "consulting"], 647 biz_consul: ["bpr", "consulting", "pmo"], 648 it_consul: ["it", "dx", "pmo", "consulting"], 649 engineering: ["it", "engineer", "dx"], 650 sap: ["sap", "erp"], 651 security: ["security", "it"], 652 tenshoku_strategy_consul: ["tenshoku", "consulting", "strategy"], 653 tenshoku_it_consul: ["tenshoku", "consulting", "it", "dx"], 654 tenshoku_engineer: ["tenshoku", "engineer", "it"], 655 tenshoku_ma: ["tenshoku", "finance", "consulting"], 656 tenshoku_accounting: ["tenshoku", "finance", "accounting"], 657 tenshoku_20s: ["tenshoku", "office", "career_change"], 658 young_mid: ["tenshoku", "consulting", "it"], 659 senior: ["tenshoku", "consulting", "strategy"], 660 career_change: ["tenshoku", "office", "career_change"], 661 tenshoku_sales: ["tenshoku", "sales", "bizdev"], 662 tenshoku_marketing: ["tenshoku", "marketing", "digital"], 663 tenshoku_consul_firm: ["tenshoku", "consulting", "strategy"], 664 tenshoku_ibd_pe: ["tenshoku", "finance", "consulting"], 665 tenshoku_startup: ["tenshoku", "consulting", "it"], 666 tenshoku_enterprise: ["tenshoku", "consulting", "finance"], 667 tenshoku_foreign: ["tenshoku", "consulting", "strategy"], 668 tenshoku_any: ["tenshoku"], 669 tenshoku_other: ["tenshoku"], 670 // ããªã¼ã©ã³ã¹Q1ï¼ã¹ã³ã¢ãªã³ã°ã«ã¯å½±é¿ããªãããããã®æ´åæ§ã®ããï¼ 671 searching: ["consulting", "it", "engineer"], 672 considering: ["consulting", "it"], 673 ending_soon: ["consulting", "it", "engineer"], 674 want_better: ["consulting", "it", "engineer"], 675 }; 676 677 const _goalMap = _savedLogic?.goalMap || { 678 high_rate: "high_rate", 679 stability: "stability", 680 speed: "speed", 681 career_up: "career_up", 682 }; 683 684 const _weights = _savedLogic?.weights || { domain: 50, goal: 35, base: 15 }; 685 686 // ============================================================ 687 // UTILS: ã¹ã³ã¢ãªã³ã°ãã¸ã㯠688 // ============================================================ 689 // Q1: ç¶æ³ â ã¹ã³ã¢ãªã³ã°ã«ã¯ç´æ¥å½±é¿ããªãï¼å ¨å¡åãæ±ãï¼ 690 // ãã ãå°æ¥ã®æ¡å¼µç¨ã«ãããã³ã°ã¯æ®ã 691 function mapSituation(val) { 692 return val || "searching"; 693 } 694 695 // Q2: ããããæ¡ä»¶ â ãã¡ã¤ã³ããã 696 function mapDomain(val) { 697 return _domainMap[val] || []; 698 } 699 700 // Q3: éè¦ãã¤ã³ã â ã´ã¼ã«ããã 701 function mapGoal(val) { 702 return _goalMap[val] || "stability"; 703 } 704 705 function calculateScore(agent, answers) { 706 let score = 0; 707 708 // Q2: æ¡ä»¶é åãããï¼è¤æ°é¸æå¯¾å¿ï¼- æéè¦ 709 if (answers.q2 && answers.q2.length > 0) { 710 let domainMatchCount = 0; 711 answers.q2.forEach(selectedDomain => { 712 const mappedDomains = mapDomain(selectedDomain); 713 const hasMatch = mappedDomains.some(d => 714 agent.targetDomain.includes(d) || agent.categories.includes(d) 715 ); 716 if (hasMatch) domainMatchCount++; 717 }); 718 score += _weights.domain * (domainMatchCount / answers.q2.length); 719 } 720 721 // Q3/Q4: éè¦ãã¤ã³ããããï¼è»¢è·ã¢ã¼ãã§ã¯Q4ãgoalãããªã¼ã©ã³ã¹ã§ã¯Q3ãgoalï¼ 722 const goalAnswer = answers.q4 || (typeof answers.q3 === 'string' ? answers.q3 : null); 723 if (goalAnswer) { 724 const goal = mapGoal(goalAnswer); 725 if (agent.targetGoal.includes(goal)) { 726 score += _weights.goal; 727 } else { 728 score += _weights.goal * 0.2; 729 } 730 } 731 732 // ãã¼ã¹ã¹ã³ã¢ï¼åªå 度ã«ããï¼ 733 score += _weights.base * ((10 - agent.priority) / 10); 734 735 return Math.min(100, Math.round(score)); 736 } 737 738 // ============================================================ 739 // UTILS: è¨å®è§£æ±º 740 // ============================================================ 741 function getConfig() { 742 const params = new URLSearchParams(window.location.search); 743 const mode = getMode(); // getMode()ã使ããã¨ã§ãã¹åå¤å®ãå«ã 744 const configKey = params.get('config') || (mode === 'tenshoku' ? 'tenshoku_strategy_consul' : 'strategy'); 745 return ARTICLE_CONFIGS[configKey] || ARTICLE_CONFIGS.strategy; 746 } 747 748 function resolveAgents(config) { 749 return config.agents.map(cfgAgent => { 750 const master = AGENTS_MASTER.find(a => a.id === cfgAgent.id); 751 if (!master) { 752 // ãã¹ã¿ã¼ã«æªç»é²ã®ã¨ã¼ã¸ã§ã³ã â configãã¼ã¿ã ãã§çæï¼ç®¡çç»é¢ã§è¿½å ãããã±ã¼ã¹ï¼ 753 return { 754 id: cfgAgent.id, 755 name: cfgAgent.nameOverride || cfgAgent.name || cfgAgent.id,
756 shortName: cfgAgent.shortName || cfgAgent.nameOverride || cfgAgent.name || cfgAgent.id, 757 rank: cfgAgent.rank || 'B', 758 rating: cfgAgent.rating || 4.0, 759 affiliateUrl: cfgAgent.affiliateUrl || '', 760 tagline: cfgAgent.tagline || '', 761 monthlyRate: cfgAgent.monthlyRate || '', 762 strengths: cfgAgent.strengths || [], 763 categories: [], 764 targetExperience: ['senior', 'mid'], 765 targetWorkStyle: ['onsite', 'hybrid'], 766 targetDomain: [], 767 targetGoal: ['career_up'], 768 priority: cfgAgent.priority ?? 99, 769 badge: cfgAgent.badge || '', 770 ctaText: cfgAgent.ctaText || 'ç¡æç»é²', 771 }; 772 } 773 return { 774 ...master, 775 // ãã¹ã¿ã¼ãã¼ã¿ã常ã«åªå ï¼rank, rating, strengths, targetDomainçï¼ 776 rank: master.rank, 777 rating: master.rating, 778 targetDomain: master.targetDomain, 779 targetGoal: master.targetGoal, 780 categories: master.categories, 781 strengths: master.strengths, 782 monthlyRate: master.monthlyRate, 783 tagline: master.tagline, 784 // ãã¹ã¿ã¼ãã¼ã¿ã®URLãåªå ï¼é åå¥è¨å®ã®URLã¯ä½¿ããªãï¼ 785 affiliateUrl: master.affiliateUrl, 786 // é åå¥è¨å®ã§ä¸æ¸ãå¯è½ãªé ç®ï¼é çªã®ã¿ï¼ 787 priority: cfgAgent.priority ?? master.priority, 788 badge: master.badge || '', 789 ctaText: master.ctaText || 'ç¡æç»é²', 790 }; 791 }).filter(Boolean).sort((a, b) => a.priority - b.priority); 792 } 793 794 // ============================================================ 795 // UTILS: iframeé«ãèªåéç¥ 796 // ============================================================ 797 function notifyHeight() { 798 try { 799 const height = document.getElementById('root').scrollHeight; 800 window.parent.postMessage({ type: 'resize', height: height + 40 }, '*'); 801 } catch (e) { /* ignore */ } 802 } 803 804 // ============================================================ 805 // COMPONENT: 鲿ããã 806 // ============================================================ 807 function ProgressDots({ total, current, completedCount }) { 808 return ( 809 <div className="flex items-center justify-center gap-2 my-4"> 810 {Array.from({ length: total }, (_, i) => ( 811 <div 812 key={i} 813 className={`w-3 h-3 rounded-full transition-all duration-300 ${ 814 i < completedCount ? 'bg-green-500' : 815 i === current ? 'bg-brand-gold w-4 h-4' : 816 'bg-gray-300' 817 }`} 818 /> 819 ))} 820 </div> 821 ); 822 } 823 824 // ============================================================ 825 // COMPONENT: 質åã«ã¼ã 826 // ============================================================ 827 function QuestionCard({ question, questionIndex, totalQuestions, answer, onAnswer, onSkip }) { 828 const [selectedValues, setSelectedValues] = useState( 829 question.type === 'multi' ? (answer || []) : [] 830 ); 831 const [animating, setAnimating] = useState(null); 832 833 const handleSingleSelect = (value) => { 834 setAnimating(value); 835 setTimeout(() => { 836 onAnswer(question.id, value); 837 }, 300); 838 }; 839 840 const handleMultiToggle = (value) => { 841 setSelectedValues(prev => { 842 if (prev.includes(value)) { 843 return prev.filter(v => v !== value); 844 } 845 if (prev.length >= (question.maxSelect || 2)) { 846 return prev; 847 } 848 return [...prev, value]; 849 }); 850 }; 851 852 const handleMultiSubmit = () => { 853 if (selectedValues.length > 0) { 854 onAnswer(question.id, selectedValues); 855 } 856 }; 857 858 return ( 859 <div className="animate-fade-in-up"> 860 <div className="text-center mb-4"> 861 <div className="text-[11px] text-gray-500 mb-1"> 862 質å {questionIndex + 1} / {totalQuestions} 863 {questionIndex < totalQuestions - 1 && ` â ãã¨${totalQuestions - questionIndex - 1}å`} 864 </div>
865 <h2 className="text-base font-bold text-gray-800 mb-1 leading-snug">{question.title}</h2> 866 <p className="text-[11px] text-gray-500">{question.subtitle}</p> 867 </div> 868 869 <div className="space-y-2"> 870 {question.options.map((opt, optIdx) => ( 871 <React.Fragment key={opt.value}> 872 {opt.sectionLabel && ( 873 <div className="flex items-center gap-2 py-0.5"> 874 <div className="flex-1 border-t border-gray-200"></div> 875 <span className="text-[10px] text-gray-400 whitespace-nowrap">{opt.sectionLabel}</span> 876 <div className="flex-1 border-t border-gray-200"></div> 877 </div> 878 )} 879 <button 880 onClick={() => question.type === 'single' ? handleSingleSelect(opt.value) : handleMultiToggle(opt.value)} 881 className={`w-full text-left px-3.5 py-3 rounded-xl border-2 transition-all duration-200 ${ 882 question.type === 'single' && animating === opt.value 883 ? 'border-brand-gold bg-yellow-50 animate-bounce-light' 884 : question.type === 'multi' && selectedValues.includes(opt.value) 885 ? 'border-brand-gold bg-yellow-50' 886 : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' 887 }`} 888 > 889 <div className="flex items-center gap-2.5"> 890 {question.type === 'multi' && ( 891 <div className={`w-4 h-4 rounded border-2 flex items-center justify-center transition-all shrink-0 ${ 892 selectedValues.includes(opt.value) 893 ? 'border-brand-gold bg-brand-gold' 894 : 'border-gray-300' 895 }`}> 896 {selectedValues.includes(opt.value) && ( 897 <svg className="w-2.5 h-2.5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}> 898 <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" /> 899 </svg> 900 )} 901 </div> 902 )} 903 <span className="text-gray-700 font-medium text-[13px] leading-snug">{opt.label}</span> 904 </div> 905 </button> 906 </React.Fragment> 907 ))} 908 </div> 909 910 {question.type === 'multi' && ( 911 <button 912 onClick={handleMultiSubmit} 913 disabled={selectedValues.length === 0} 914 className={`w-full mt-3 py-3 rounded-xl font-bold text-white text-sm transition-all ${ 915 selectedValues.length > 0 916 ? 'bg-brand-gold hover:bg-brand-gold-dark' 917 : 'bg-gray-300 cursor-not-allowed' 918 }`} 919 > 920 {selectedValues.length > 0 ? `${selectedValues.length}ã¤é¸æãã¦æ¬¡ã¸` : '1ã¤ä»¥ä¸é¸æãã¦ãã ãã'} 921 </button> 922 )} 923 924 {question.optional && ( 925 <button 926 onClick={() => onSkip(question.id)} 927 className="w-full mt-2 py-1.5 text-[11px] text-gray-500 hover:text-gray-700 transition-colors" 928 > 929 ã¹ããããã¦æ¬¡ã¸ â 930 </button> 931 )} 932 </div> 933 ); 934 } 935 936 // ============================================================ 937 // COMPONENT: ã©ã³ã¯ãã㸠938 // ============================================================ 939 function RankBadge({ rank }) { 940 const colors = { 941 S: 'bg-gradient-to-r from-yellow-400 to-yellow-600 text-white', 942 A: 'bg-gradient-to-r from-blue-400 to-blue-600 text-white', 943 B: 'bg-gradient-to-r from-gray-400 to-gray-600 text-white', 944 }; 945 return ( 946 <span className={`inline-flex items-center justify-center w-6 h-6 rounded-md text-xs font-bold shrink-0 whitespace-nowrap shadow-sm ${colors[rank] || colors.B}`}> 947 {rank} 948 </span> 949 ); 950 } 951 952 // ============================================================ 953 // COMPONENT: ã¨ã¼ã¸ã§ã³ãã«ã¼ãï¼1社ãã¤ææ¡ï¼ 954 // ============================================================ 955 function AgentCard({ agent, matchScore, status, order, totalCount, onCtaClick, onRegistered, onSkip }) { 956 const [ctaAnimating, setCtaAnimating] = useState(false); 957 958 const handleCtaClick = () => { 959 setCtaAnimating(true); 960 setTimeout(() => { 961 setCtaAnimating(false); 962 onCtaClick(agent); 963 }, 200); 964 }; 965 966 return ( 967 <div className="animate-fade-in-up"> 968 <div className="bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden"> 969 {/* ãããã¼ */} 970 <div className="bg-gradient-to-r from-gray-50 to-gray-100 px-5 py-3 flex items-center justify-between"> 971 <div className="flex items-center gap-2">
972 <span className="text-lg font-bold text-gray-700">#{order}</span> 973 <RankBadge rank={agent.rank} /> 974 </div> 975 <div className="text-right"> 976 <span className="text-sm text-gray-500">ãããã度</span> 977 <span className={`ml-1 text-lg font-bold ${ 978 matchScore >= 80 ? 'text-green-600' : matchScore >= 60 ? 'text-brand-gold' : 'text-gray-600' 979 }`}>{matchScore}%</span> 980 </div> 981 </div> 982 983 {/* ãã㸠*/} 984 {agent.badge && ( 985 <div className="px-5 pt-3"> 986 <span className="inline-block bg-yellow-50 border border-yellow-200 text-yellow-800 text-xs font-bold px-3 py-1 rounded-full"> 987 {agent.badge} 988 </span> 989 </div> 990 )} 991 992 {/* ã³ã³ãã³ã */} 993 <div className="p-5"> 994 <h3 className="text-lg font-bold text-gray-800 mb-1">{agent.name}</h3> 995 <p className="text-sm text-gray-500 mb-3">{agent.tagline}</p> 996 997 <div className="flex items-center gap-2 mb-4"> 998 <span className="text-sm text-gray-500">ð°</span> 999 <span className="text-sm font-bold text-gray-700">{agent.monthlyRate}</span> 1000 </div> 1001 1002 {/* å¼·ã¿ */} 1003 <div className="flex flex-wrap gap-2 mb-5"> 1004 {agent.strengths.map((s, i) => ( 1005 <span key={i} className="inline-flex items-center text-xs bg-green-50 text-green-700 px-2 py-1 rounded-lg"> 1006 â {s} 1007 </span> 1008 ))} 1009 </div> 1010 1011 {/* CTAãã¿ã³ */} 1012 {status === 'registered' ? ( 1013 <div className="w-full py-3 rounded-xl bg-green-100 text-green-700 font-bold text-center animate-pop-in"> 1014 â ç»é²æ¸ã¿ 1015 </div> 1016 ) : ( 1017 <> 1018 <button 1019 onClick={handleCtaClick} 1020 className={`w-full py-3.5 rounded-xl font-bold text-white transition-all text-base ${ 1021 ctaAnimating 1022 ? 'bg-brand-orange-dark animate-pulse-once' 1023 : 'bg-brand-orange hover:bg-brand-orange-dark hover:shadow-lg' 1024 }`} 1025 > 1026 â¶ {agent.ctaText} 1027 </button> 1028 1029 <div className="flex gap-3 mt-3"> 1030 <button 1031 onClick={() => onRegistered(agent.id)} 1032 className={`flex-1 py-2 rounded-lg text-sm font-medium transition-all ${ 1033 status === 'clicked' 1034 ? 'bg-green-50 text-green-700 border-2 border-green-300 hover:bg-green-100' 1035 : 'bg-gray-50 text-gray-500 border border-gray-200 hover:bg-gray-100' 1036 }`} 1037 > 1038 â ç»é²ãã 1039 </button> 1040 <button 1041 onClick={() => onSkip(agent.id)} 1042 className="flex-1 py-2 rounded-lg text-sm text-gray-400 hover:text-gray-600 hover:bg-gray-50 border border-gray-200 transition-all" 1043 > 1044 ã¹ããã â æ¬¡ã¸ 1045 </button> 1046 </div> 1047 </> 1048 )} 1049 </div> 1050 </div> 1051 1052 {/* 鲿 */} 1053 <ProgressDots 1054 total={totalCount} 1055 current={order - 1} 1056 completedCount={order - 1} 1057 /> 1058 1059 <p className="text-center text-xs text-gray-400 mt-1"> 1060 ð¡ è¤æ°ç¤¾ã«ç»é²ããã¨æ¡ä»¶ã®é¸æè¢ãåºããã¾ã 1061 </p> 1062 </div> 1063 ); 1064 } 1065 1066 // ============================================================ 1067 // COMPONENT: ã¹ãã¼ã¿ã¹ãµããªã¼ 1068 // ============================================================ 1069 function StatusSummary({ agents, statuses, onBulkOpen }) { 1070 const registeredCount = Object.values(statuses).filter(s => s.status === 'registered').length; 1071 const clickedNotRegistered = agents.filter(a => { 1072 const s = statuses[a.id]; 1073 return s && s.status === 'clicked'; 1074 }); 1075 const pending = agents.filter(a => { 1076 const s = statuses[a.id]; 1077 return !s || s.status === 'pending'; 1078 }); 1079 const notDone = [...clickedNotRegistered, ...pending]; 1080 1081 return ( 1082 <div className="bg-white rounded-2xl shadow-lg border border-gray-100 p-5 animate-fade-in">
1083 <h3 className="text-base font-bold text-gray-800 mb-4">ð ããªãã®ç»é²ã¹ãã¼ã¿ã¹</h3> 1084 1085 <div className="space-y-2 mb-5"> 1086 {agents.map(agent => { 1087 const s = statuses[agent.id]; 1088 const status = s ? s.status : 'pending'; 1089 const icon = status === 'registered' ? 'â ' : status === 'clicked' ? 'ðµ' : status === 'skipped' ? 'âï¸' : 'â¬'; 1090 const label = status === 'registered' ? 'ç»é²æ¸ã¿' : status === 'clicked' ? '確èªä¸' : status === 'skipped' ? 'ã¹ããã' : 'ã¾ã '; 1091 const textColor = status === 'registered' ? 'text-green-600' : status === 'clicked' ? 'text-blue-600' : 'text-gray-400'; 1092 return ( 1093 <div key={agent.id} className="flex items-center justify-between py-1.5"> 1094 <span className="text-sm text-gray-700">{icon} {agent.name}</span> 1095 <span className={`text-xs font-medium ${textColor}`}>{label}</span> 1096 </div> 1097 ); 1098 })} 1099 </div> 1100 1101 {notDone.length > 0 && ( 1102 <button 1103 onClick={() => onBulkOpen(notDone)} 1104 className="w-full py-3 rounded-xl bg-brand-gold hover:bg-brand-gold-dark text-white font-bold transition-all text-sm" 1105 > 1106 â æªç»é²ã®{notDone.length}社ã䏿°ã«éã 1107 </button> 1108 )} 1109 1110 {registeredCount > 0 && registeredCount < agents.length && ( 1111 <p className="text-center text-xs text-gray-500 mt-3"> 1112 ð¡ {registeredCount}社ç»é²å®äºï¼ãã¨{agents.length - registeredCount}社ç»é²ããã¨ãæé©ãªæ¡ä»¶ã«åºä¼ã確çãããã«UP 1113 </p> 1114 )} 1115 1116 {registeredCount >= 3 && ( 1117 <p className="text-center text-xs text-green-600 font-medium mt-3"> 1118 ð {registeredCount}社ç»é²å®äºï¼æ¡ä»¶ã®æ¯è¼æ¤è¨ãå¯è½ã§ã 1119 </p> 1120 )} 1121 </div> 1122 ); 1123 } 1124 1125 // ============================================================ 1126 // COMPONENT: 䏿¬ç»é²ããã«ï¼Module Bï¼ 1127 // ============================================================ 1128 function BulkRegistrationPanel({ agents, statuses, onBulkOpen }) { 1129 const handleClick = (agent) => { 1130 if (agent.affiliateUrl) { 1131 window.open(agent.affiliateUrl, '_blank'); 1132 onBulkOpen([agent]); 1133 } 1134 }; 1135 1136 return ( 1137 <div className="bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden"> 1138 <div className="bg-gradient-to-r from-brand-gold/10 to-brand-orange/10 px-5 py-4"> 1139 <h3 className="text-lg font-bold text-gray-800">ð ãã®ä»ã®ããããã¨ã¼ã¸ã§ã³ã</h3> 1140 <p className="text-xs text-gray-500 mt-1">æ¯è¼æ¤è¨ã®ããã«ãã¡ããåããã¦ç»é²ãããããã§ã</p> 1141 </div> 1142 1143 <div className="p-4 space-y-2"> 1144 {agents.map((agent) => { 1145 const s = statuses[agent.id]; 1146 const isClicked = s && (s.status === 'clicked' || s.status === 'registered'); 1147 const hasUrl = agent.affiliateUrl && agent.affiliateUrl.length > 0; 1148 return ( 1149 <div 1150 key={agent.id} 1151 className={`flex items-center gap-3 p-3 rounded-xl border transition-all ${ 1152 isClicked ? 'border-green-200 bg-green-50' : 'border-gray-200 bg-white' 1153 }`} 1154 > 1155 <div className="flex-1 min-w-0"> 1156 <div className="flex items-center gap-2"> 1157 <span className="text-sm font-bold text-gray-700">{agent.name}</span> 1158 <RankBadge rank={agent.rank} /> 1159 {isClicked && <span className="text-xs text-green-600">â </span>} 1160 </div> 1161 <div className="text-xs text-gray-500">ð° {agent.monthlyRate}</div> 1162 </div> 1163 {hasUrl ? ( 1164 <button 1165 onClick={() => handleClick(agent)} 1166 className={`shrink-0 px-3 py-1.5 rounded-lg text-xs font-bold transition-all ${ 1167 isClicked 1168 ? 'bg-green-100 text-green-600 border border-green-200' 1169 : 'bg-brand-orange text-white hover:bg-brand-orange-dark shadow-sm' 1170 }`} 1171 > 1172 {isClicked ? 'ç»é²æ¸' : 'ç»é² â'} 1173 </button> 1174 ) : (
1175 <span className="shrink-0 px-3 py-1.5 rounded-lg text-xs text-gray-400 bg-gray-100">æºåä¸</span> 1176 )} 1177 </div> 1178 ); 1179 })} 1180 </div> 1181 </div> 1182 ); 1183 } 1184 1185 // ============================================================ 1186 // COMPONENT: çµæã«ã¼ãï¼ä¸è¦§ç¨ã³ã³ãã¯ãçï¼ 1187 // ============================================================ 1188 function AgentResultCard({ agent, matchScore, order, status, onCtaClick }) { 1189 return ( 1190 <div className={`bg-white rounded-2xl shadow-md border border-gray-100 overflow-hidden animate-fade-in-up stagger-${Math.min(order, 5)}`}> 1191 {/* ãããã¼ */} 1192 <div className="bg-gradient-to-r from-gray-50 to-gray-100 px-4 py-2.5 flex items-center justify-between gap-2"> 1193 <div className="flex items-center gap-2 min-w-0 flex-1"> 1194 <span className={`text-lg font-bold shrink-0 ${order <= 3 ? 'text-brand-gold' : 'text-gray-500'}`}>#{order}</span> 1195 <RankBadge rank={agent.rank} /> 1196 {agent.badge && ( 1197 <span className="text-[11px] bg-yellow-50 border border-yellow-200 text-yellow-800 font-bold px-2 py-0.5 rounded-full truncate"> 1198 {agent.badge} 1199 </span> 1200 )} 1201 </div> 1202 <div className="text-right shrink-0"> 1203 <span className="text-xs text-gray-500">ãããã度</span> 1204 <span className={`ml-1 text-base font-bold ${ 1205 matchScore >= 80 ? 'text-green-600' : matchScore >= 60 ? 'text-brand-gold' : 'text-gray-600' 1206 }`}>{matchScore}%</span> 1207 </div> 1208 </div> 1209 1210 {/* ã³ã³ãã³ã */} 1211 <div className="p-4"> 1212 <h3 className="text-base font-bold text-gray-800 mb-0.5">{agent.name}</h3> 1213 <p className="text-xs text-gray-500 mb-2">{agent.tagline}</p> 1214 1215 <div className="flex items-center gap-2 mb-3"> 1216 <span className="text-xs text-gray-500">ð°</span> 1217 <span className="text-xs font-bold text-gray-700">{agent.monthlyRate}</span> 1218 </div> 1219 1220 {/* å¼·ã¿ */} 1221 <div className="flex flex-wrap gap-1.5 mb-4"> 1222 {agent.strengths.map((s, i) => ( 1223 <span key={i} className="inline-flex items-center text-xs bg-green-50 text-green-700 px-2 py-0.5 rounded-lg"> 1224 â {s} 1225 </span> 1226 ))} 1227 </div> 1228 1229 {/* CTAãã¿ã³ */} 1230 {status === 'registered' ? ( 1231 <div className="w-full py-2.5 rounded-xl bg-green-100 text-green-700 font-bold text-center text-sm"> 1232 â ç»é²æ¸ã¿ 1233 </div> 1234 ) : ( 1235 <button 1236 onClick={() => onCtaClick(agent)} 1237 className="w-full py-2.5 rounded-xl bg-brand-orange hover:bg-brand-orange-dark text-white font-bold text-sm transition-all hover:shadow-lg" 1238 > 1239 â¶ {agent.ctaText} 1240 </button> 1241 )} 1242 </div> 1243 </div> 1244 ); 1245 } 1246 1247 // ============================================================ 1248 // COMPONENT: 䏿¬ç»é²ç»é¢ï¼ãã§ãã¯ä»ãï¼ 1249 // ============================================================ 1250 function BulkOnlyView({ categories, statuses, updateStatus, onBack }) { 1251 // ã«ãã´ãªãã¨ã®ãã§ãã¯ç¶æ : { catKey: { agentId: boolean } } 1252 const [checked, setChecked] = useState(() => { 1253 const init = {}; 1254 categories.forEach(cat => { 1255 init[cat.key] = {}; 1256 cat.agents.forEach((a, i) => { init[cat.key][a.id] = i < 3 && !!a.affiliateUrl; }); 1257 }); 1258 return init; 1259 }); 1260 const [fallbacks, setFallbacks] = useState([]); 1261 1262 const toggleAgent = (catKey, agentId) => { 1263 setChecked(prev => ({ 1264 ...prev, 1265 [catKey]: { ...prev[catKey], [agentId]: !prev[catKey]?.[agentId] } 1266 })); 1267 }; 1268 1269 const selectAllCat = (catKey, agents) => { 1270 setChecked(prev => { 1271 const next = { ...prev, [catKey]: {} };
1272 agents.forEach(a => { next[catKey][a.id] = !!a.affiliateUrl; }); 1273 return next; 1274 }); 1275 }; 1276 1277 const deselectAllCat = (catKey, agents) => { 1278 setChecked(prev => { 1279 const next = { ...prev, [catKey]: {} }; 1280 agents.forEach(a => { next[catKey][a.id] = false; }); 1281 return next; 1282 }); 1283 }; 1284 1285 const getCheckedAgents = (catKey, agents) => agents.filter(a => checked[catKey]?.[a.id] && a.affiliateUrl); 1286 1287 const bulkOpenCat = (catKey, agents) => { 1288 const selected = getCheckedAgents(catKey, agents); 1289 if (selected.length === 0) return; 1290 selected.forEach(agent => { 1291 const a = document.createElement('a'); 1292 a.href = agent.affiliateUrl; 1293 a.target = '_blank'; 1294 a.rel = 'noopener noreferrer'; 1295 a.style.display = 'none'; 1296 document.body.appendChild(a); 1297 try { a.click(); } catch(e) {} 1298 document.body.removeChild(a); 1299 updateStatus(agent.id, 'clicked'); 1300 }); 1301 }; 1302 1303 return ( 1304 <div className="max-w-lg mx-auto p-4 space-y-4"> 1305 <div className="text-right"><span className="text-xs text-gray-400">PR</span></div> 1306 <div className="flex items-center justify-between"> 1307 <button onClick={onBack} className="text-sm text-gray-400 hover:text-gray-700 transition-colors"> 1308 â 診æã«æ»ã 1309 </button> 1310 <h2 className="text-base font-bold text-gray-800">é åå¥ããããã¨ã¼ã¸ã§ã³ã</h2> 1311 </div> 1312 1313 {/* ç»é²ãªã³ã¯ãªã¹ã */} 1314 {fallbacks.length > 0 && ( 1315 <div className="bg-yellow-50 border border-yellow-200 rounded-xl p-4"> 1316 <p className="text-sm font-bold text-yellow-800 mb-1">ð {fallbacks.length}社ã®ç»é²ãªã³ã¯</p> 1317 <p className="text-xs text-yellow-700 mb-3">ãããã¢ããããããã¯ããã¾ããã以ä¸ãã¿ãããã¦éãã¦ãã ããï¼</p> 1318 <div className="space-y-2"> 1319 {fallbacks.map((a, idx) => ( 1320 <a key={a.id} href={a.affiliateUrl} target="_blank" rel="noopener noreferrer" 1321 onClick={() => updateStatus(a.id, 'clicked')} 1322 className="flex items-center gap-2 p-3 bg-white border border-yellow-300 rounded-xl text-sm font-bold text-gray-700 hover:bg-yellow-100 transition-all shadow-sm"> 1323 <span className={`shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-[11px] font-bold ${ 1324 idx === 0 ? 'bg-brand-gold text-white' : 'bg-gray-100 text-gray-500' 1325 }`}>{idx + 1}</span> 1326 <span className="flex-1">{a.name}</span> 1327 <span className="text-xs text-green-600 font-bold whitespace-nowrap">ç¡æç»é² â</span> 1328 </a> 1329 ))} 1330 </div> 1331 <button onClick={() => setFallbacks([])} className="w-full mt-3 text-xs text-gray-400 hover:text-gray-600 hover:underline">éãã</button> 1332 </div> 1333 )} 1334 1335 {categories.map(cat => { 1336 const checkedCount = getCheckedAgents(cat.key, cat.agents).length; 1337 return ( 1338 <div key={cat.key} className="bg-white rounded-2xl shadow border border-gray-100 overflow-hidden"> 1339 <div className="px-5 py-3 bg-gray-50 border-b border-gray-100 flex items-center justify-between"> 1340 <div> 1341 <div className="flex items-center gap-2"> 1342 <span className="text-lg">{cat.icon}</span> 1343 <h3 className="text-sm font-bold text-gray-800">{cat.label}</h3> 1344 </div> 1345 <p className="text-[11px] text-gray-400 mt-0.5">{cat.desc}</p> 1346 </div> 1347 <span className="text-[11px] text-gray-400 bg-gray-100 px-2 py-0.5 rounded">{cat.agents.length}社</span> 1348 </div> 1349 <div className="divide-y divide-gray-50"> 1350 {cat.agents.map((agent, idx) => {
1351 const st = statuses[agent.id]; 1352 const isClicked = st?.status === 'clicked'; 1353 const isChecked = !!checked[cat.key]?.[agent.id]; 1354 return ( 1355 <div key={agent.id} className="px-4 py-3 flex items-center gap-2.5"> 1356 <label className="shrink-0 flex items-center cursor-pointer"> 1357 <input 1358 type="checkbox" 1359 checked={isChecked} 1360 onChange={() => toggleAgent(cat.key, agent.id)} 1361 disabled={!agent.affiliateUrl} 1362 className="w-4 h-4 rounded border-gray-300 text-brand-orange focus:ring-brand-orange/50 cursor-pointer" 1363 /> 1364 </label> 1365 <span className={`shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-[11px] font-bold ${ 1366 idx === 0 ? 'bg-brand-gold text-white' : 'bg-gray-100 text-gray-500' 1367 }`}>{idx + 1}</span> 1368 <div className="flex-1 min-w-0" onClick={() => agent.affiliateUrl && toggleAgent(cat.key, agent.id)} style={{cursor:'pointer'}}> 1369 <div className="flex items-center gap-2"> 1370 <span className="text-[13px] font-semibold text-gray-800">{agent.name}</span> 1371 {agent.badge && <span className="text-[9px] bg-brand-gold/10 text-brand-gold-dark px-1.5 py-0.5 rounded-full whitespace-nowrap">{agent.badge}</span>} 1372 </div> 1373 <div className="text-[11px] text-gray-400 mt-0.5">{agent.monthlyRate || agent.tagline}</div> 1374 </div> 1375 {isClicked && <span className="shrink-0 text-[10px] text-green-500 font-bold">æ¸</span>} 1376 </div> 1377 ); 1378 })} 1379 </div> 1380 {/* ã«ãã´ãªå¥ 䏿¬ãã¿ã³ */} 1381 <div className="px-4 py-3 bg-gray-50 border-t border-gray-100 flex items-center justify-between gap-2"> 1382 <div className="flex gap-2 items-center"> 1383 <button onClick={() => selectAllCat(cat.key, cat.agents)} className="text-[11px] text-brand-gold hover:underline">å ¨é¸æ</button> 1384 <span className="text-[11px] text-gray-300">|</span> 1385 <button onClick={() => deselectAllCat(cat.key, cat.agents)} className="text-[11px] text-gray-400 hover:underline">å ¨è§£é¤</button> 1386 </div> 1387 <button 1388 onClick={() => bulkOpenCat(cat.key, cat.agents)} 1389 disabled={checkedCount === 0} 1390 className={`px-4 py-2 rounded-xl text-[12px] font-bold transition-all ${ 1391 checkedCount > 0 1392 ? 'bg-brand-orange text-white hover:bg-brand-orange-dark shadow-sm hover:shadow' 1393 : 'bg-gray-200 text-gray-400 cursor-not-allowed' 1394 }`} 1395 > 1396 {checkedCount > 0 ? `${checkedCount}社ã¾ã¨ãã¦ç¡æç»é²` : '鏿ãã¦ãã ãã'} 1397 </button> 1398 </div> 1399 </div> 1400 ); 1401 })} 1402 1403 <p className="text-[10px] text-gray-400 text-center pt-2"> 1404 è¤æ°ç¤¾ã«ç»é²ããã¨ãæ¡ä»¶ã®é¸æè¢ã3å以ä¸ã«åºããã¾ã 1405 </p> 1406 </div> 1407 ); 1408 } 1409 1410 // ============================================================ 1411 // COMPONENT: 診æå®äº â çµæç»é¢ï¼5社ä¸è¦§è¡¨ç¤ºï¼ 1412 // ============================================================ 1413 function ResultView({ rankedAgents, statuses, updateStatus, onBulkOpen, onRestart }) { 1414 1415 const handleCtaClick = (agent) => { 1416 if (agent.affiliateUrl) { 1417 window.open(agent.affiliateUrl, '_blank'); 1418 updateStatus(agent.id, 'clicked'); 1419 } 1420 }; 1421 1422 return ( 1423 <div className="space-y-5"> 1424 <div className="text-center animate-fade-in">
1425 <h2 className="text-xl font-bold text-gray-800 mb-2"> 1426 ð¯ ããªãã«ãããããã¨ã¼ã¸ã§ã³ãTOP5 1427 </h2> 1428 <p className="text-sm text-gray-500"> 1429 è¤æ°ç¤¾ã«ç»é²ããã¨ãæ¡ä»¶ã®é¸æè¢ã3å以ä¸ã«åºããã¾ãð¡ 1430 </p> 1431 </div> 1432 1433 {/* TOP5ã¨ã¼ã¸ã§ã³ãä¸è¦§è¡¨ç¤º */} 1434 <div className="space-y-4"> 1435 {rankedAgents.slice(0, 5).map((r, i) => ( 1436 <AgentResultCard 1437 key={r.agent.id} 1438 agent={r.agent} 1439 matchScore={r.score} 1440 order={i + 1} 1441 status={statuses[r.agent.id]?.status || 'pending'} 1442 onCtaClick={handleCtaClick} 1443 /> 1444 ))} 1445 </div> 1446 1447 {/* ããä¸åº¦è¨ºæãããã¿ã³ */} 1448 <div className="text-center pt-2"> 1449 <button 1450 onClick={onRestart} 1451 className="text-sm text-brand-gold hover:text-brand-gold-dark hover:underline transition-colors" 1452 > 1453 ð ããä¸åº¦è¨ºæãã 1454 </button> 1455 </div> 1456 </div> 1457 ); 1458 } 1459 1460 // ============================================================ 1461 // COMPONENT: 診æããã¼ï¼Module Aï¼ 1462 // ============================================================ 1463 function DiagnosticFlow({ agents, mode, onComplete }) { 1464 const [currentQ, setCurrentQ] = useState(0); 1465 const [answers, setAnswers] = useState({}); 1466 const [isComplete, setIsComplete] = useState(false); 1467 1468 // modeã«å¿ãã¦è³ªåãªã¹ããçæï¼Q2ã®åçã«å¿ãã¦Q3ãåçã«å¤æ´ï¼ 1469 const questions = useMemo(() => { 1470 const base = getQuestions(mode); 1471 if (mode !== 'tenshoku') return base; 1472 // 転è·ã¢ã¼ã: Q2ã®é¸æã«åºã¥ãQ3ã®é¸æè¢ãåççæ 1473 const q2vals = answers.q2 || []; 1474 if (q2vals.length === 0) return base; 1475 // Q2ã§é¸ã°ããåã«ãã´ãªã®Q3ãªãã·ã§ã³ãçµ±åï¼éè¤é¤å»ï¼ 1476 const seen = new Set(); 1477 const mergedOptions = []; 1478 q2vals.forEach(q2val => { 1479 const opts = Q3_OPTIONS_BY_Q2[q2val] || []; 1480 opts.forEach(opt => { 1481 if (!seen.has(opt.value)) { 1482 seen.add(opt.value); 1483 mergedOptions.push(opt); 1484 } 1485 }); 1486 }); 1487 // anyãæ«å°¾ã«ç§»å 1488 const anyIdx = mergedOptions.findIndex(o => o.value === 'any'); 1489 if (anyIdx >= 0) { 1490 const [anyOpt] = mergedOptions.splice(anyIdx, 1); 1491 mergedOptions.push(anyOpt); 1492 } else { 1493 mergedOptions.push({ value: 'any', label: 'ãã ãããªã' }); 1494 } 1495 // æ°æ§é ãå¼·å¶ï¼å¤ãä¿åãã¼ã¿ã type=multi çãæã£ã¦ãã¦ã䏿¸ãï¼ 1496 const dynamicQ3 = { 1497 id: 'q3', 1498 title: Q3_TENSHOKU.title || '叿ã®ä¼æ¥ã¿ã¤ãã¯ï¼', 1499 subtitle: Q3_TENSHOKU.subtitle || '1ã¤ãé¸ã³ãã ãã', 1500 type: 'single', 1501 options: mergedOptions, 1502 }; 1503 return [base[0], base[1], dynamicQ3, base[3]]; 1504 }, [mode, answers.q2]); 1505 1506 const handleAnswer = (questionId, value) => { 1507 const newAnswers = { ...answers, [questionId]: value }; 1508 setAnswers(newAnswers); 1509 1510 if (currentQ < questions.length - 1) { 1511 setTimeout(() => setCurrentQ(prev => prev + 1), 100); 1512 } else { 1513 finishDiagnostic(newAnswers); 1514 } 1515 }; 1516 1517 const handleBack = () => { 1518 if (currentQ > 0) { 1519 setCurrentQ(prev => prev - 1); 1520 } 1521 }; 1522 1523 const handleSkip = (questionId) => { 1524 if (currentQ < questions.length - 1) { 1525 setCurrentQ(prev => prev + 1); 1526 } else { 1527 finishDiagnostic(answers); 1528 } 1529 }; 1530 1531 const finishDiagnostic = (finalAnswers) => { 1532 // å ±é颿° computeRankedAgents ã«å§è²ï¼SwipeDiagnosticã¨å ±æï¼ 1533 const results = computeRankedAgents(finalAnswers, mode, agents); 1534 setIsComplete(true); 1535 onComplete(results); 1536 }; 1537 1538 if (isComplete) return null; 1539 1540 return ( 1541 <div className="max-w-lg mx-auto"> 1542 <div className="text-right mb-0.5"><span className="text-[10px] text-gray-400">PR</span></div> 1543 {/* æ»ããã¿ã³ + 鲿ãã¼ */} 1544 <div className="flex items-center gap-3 mb-4"> 1545 {currentQ > 0 ? ( 1546 <button 1547 onClick={handleBack} 1548 className="text-xs text-gray-400 hover:text-gray-600 transition-colors flex items-center gap-1 shrink-0" 1549 > 1550 â æ»ã 1551 </button> 1552 ) : <div className="w-10" />} 1553 <div className="flex-1 bg-gray-200 rounded-full h-1.5"> 1554 <div 1555 className="bg-brand-gold h-1.5 rounded-full transition-all duration-500" 1556 style={{ width: `${((currentQ + 1) / questions.length) * 100}%` }} 1557 /> 1558 </div> 1559 </div> 1560 1561 <QuestionCard 1562 key={questions[currentQ].id + '-' + currentQ} 1563 question={questions[currentQ]} 1564 questionIndex={currentQ} 1565 totalQuestions={questions.length} 1566 answer={answers[questions[currentQ].id]} 1567 onAnswer={handleAnswer} 1568 onSkip={handleSkip} 1569 /> 1570 </div> 1571 ); 1572 } 1573 1574 // ============================================================ 1575 // SHARED: 診æçµæè¨ç®ï¼DiagnosticFlow/SwipeDiagnostic å ±éï¼ 1576 // ============================================================ 1577 function computeRankedAgents(finalAnswers, mode, fallbackAgents) { 1578 const q2vals = finalAnswers.q2 || []; 1579 const configAgentsMap = new Map(); 1580 const configPriorityMap = new Map(); 1581
1582 q2vals.forEach(q2val => { 1583 const cfg = ARTICLE_CONFIGS[q2val]; 1584 if (cfg && cfg.agents) { 1585 const resolved = resolveAgents(cfg); 1586 resolved.forEach(a => { 1587 if (!configAgentsMap.has(a.id)) configAgentsMap.set(a.id, a); 1588 }); 1589 cfg.agents.forEach(ca => { 1590 if (!configPriorityMap.has(ca.id)) configPriorityMap.set(ca.id, ca.priority); 1591 }); 1592 } 1593 }); 1594 1595 if (configAgentsMap.size === 0 && fallbackAgents) { 1596 fallbackAgents.forEach(a => configAgentsMap.set(a.id, a)); 1597 } 1598 1599 let allAgents = [...configAgentsMap.values()]; 1600 const results = []; 1601 const seen = new Set(); 1602 1603 const q3val = typeof finalAnswers.q3 === 'string' ? finalAnswers.q3 : (Array.isArray(finalAnswers.q3) ? finalAnswers.q3[0] : null); 1604 // Q3ãã¼ã¹ãããè¤æ°Q2ã対å¿ã«ããï¼q2valãã¨ã«è¤åãã¼ã§åæãã¦ãã¼ã¹ãä½ç½®ãä¿æ 1605 // { agent_id: boost_rank (0=1ä½, 1=2ä½, ...) } 1606 const q3BoostRank = {}; 1607 if (q3val && q3val !== 'any') { 1608 q2vals.forEach(q2val => { 1609 const compositeKey = `${q2val}:${q3val}`; 1610 const list = Q3_AGENT_BOOSTS[compositeKey] || []; 1611 list.forEach((aid, idx) => { 1612 // åãã¨ã¼ã¸ã§ã³ããè¤æ°Q2ã§ãã¼ã¹ãããããæå°ã®rankï¼ä¸ä½ï¼ãæ¡ç¨ 1613 if (q3BoostRank[aid] === undefined || q3BoostRank[aid] > idx) { 1614 q3BoostRank[aid] = idx; 1615 } 1616 }); 1617 }); 1618 // ãã©ã¼ã«ããã¯: è¤åãã¼ã1ã¤ãç¡ããã°q3valåç¬ãã¼ 1619 if (Object.keys(q3BoostRank).length === 0) { 1620 const list = Q3_AGENT_BOOSTS[q3val] || []; 1621 list.forEach((aid, idx) => { q3BoostRank[aid] = idx; }); 1622 } 1623 } 1624 1625 // â ãã¼ããã£ã«ã¿ã¯ãããï¼1社ããåºãªãåé¡ã®åå ï¼ 1626 // 代ããã«ã¹ã³ã¢ãã¼ãã¹ã§ä¸ä½ã«æ¼ãä¸ããï¼1ä½=+20, 2ä½=+15, 3ä½=+10, 4ä½=+7, 5ä½ä»¥é=+5
1627 const boostBonusByRank = [20, 15, 10, 7, 5, 4, 3, 2, 1]; 1628 1629 const rankBonus = { S: 5, A: 0, B: -5 }; 1630 const scored = allAgents.map(agent => { 1631 const a = agent; 1632 const priority = configPriorityMap.get(a.id) ?? 999; 1633 const score = calculateScore(a, finalAnswers); 1634 const rb = rankBonus[a.rank] || 0; 1635 // Q3ãã¼ã¹ã: boost listã«å ¥ã£ã¦ãã°é ä½ã«å¿ãã¦ãã¼ãã¹å ç® 1636 let q3Bonus = 0; 1637 if (q3BoostRank[a.id] !== undefined) { 1638 const rank = q3BoostRank[a.id]; 1639 q3Bonus = boostBonusByRank[rank] !== undefined ? boostBonusByRank[rank] : 1; 1640 } 1641 const finalScore = (1 - priority / 20) * 40 + rb + score * 0.05 + q3Bonus; 1642 const recScore = Math.min(100, Math.max(0, Math.round(finalScore / 68 * 100))); 1643 return { agent: a, score: recScore, priority, finalScore }; 1644 }); 1645 1646 scored.sort((a, b) => b.finalScore - a.finalScore); 1647 1648 for (const item of scored) { 1649 if (seen.has(item.agent.id)) continue; 1650 seen.add(item.agent.id); 1651 results.push({ agent: item.agent, score: item.score }); 1652 } 1653 return results; 1654 } 1655 1656 // ============================================================ 1657 // COMPONENT: åçã¢ã¼ãåæ¿ãã°ã«ï¼ç°¡å / ãã£ããï¼ 1658 // ============================================================ 1659 // COMPONENT: Tinder風ã¹ã¯ã¤ã診æï¼ç°¡ååçã¢ã¼ãï¼ 1660 // ============================================================ 1661 const SWIPE_CARD_META = { 1662 // === Freelance Q2 === 1663 strategy: { emoji: 'ð¯', short: 'æ¦ç¥ã³ã³ãµã«', desc: 'æ°è¦äºæ¥ã»M&Aã»æ¦ç¥ç«æ¡' }, 1664 biz_consul: { emoji: 'ð¢', short: 'æ¥åã³ã³ãµã«', desc: 'æ¥åæ¹åã»BPR' }, 1665 it_consul: { emoji: 'ð»', short: 'ITã³ã³ãµã«', desc: 'ã·ã¹ãã å°å ¥ã»PMO' }, 1666 engineering: { emoji: 'âï¸', short: 'ã¨ã³ã¸ãã¢ãªã³ã°', desc: 'éçºã»ã¤ã³ãã©' }, 1667 sap: { emoji: 'ð', short: 'SAPæ¡ä»¶', desc: 'FICOã»SDã»MMã»ABAP' }, 1668 security: { emoji: 'ð', short: 'ã»ãã¥ãªãã£', desc: 'SOCã»èå¼±æ§è¨ºæ' }, 1669 // === Freelance Q1 (ä»ã®ç¶æ³) === 1670 searching: { emoji: 'ð', short: 'æ¡ä»¶æ¢ãä¸', desc: 'ããªã¼ã©ã³ã¹ã§æ¢ãã¦ã' }, 1671 considering: { emoji: 'ð¤', short: 'ç¬ç«æ¤è¨ä¸', desc: 'ã¾ã ä¼ç¤¾å¡ã§æ¤è¨æ®µé' }, 1672 ending_soon: { emoji: 'â°', short: 'æ¡ä»¶çµäºäºå®', desc: 'æ¬¡ã®æ¡ä»¶ãæ¢ããã' }, 1673 want_better: { emoji: 'ð°', short: 'å価ã¢ããçã', desc: 'ã¨ã¼ã¸ã§ã³ããå¤ããã' }, 1674 // === Freelance Q3 / Tenshoku Q4 (éè¦ãããã®ã»åä¸value) === 1675 high_rate: { emoji: 'ð°', short: 'é«å価ã»å¹´å', desc: 'ã¨ã«ãã稼ããã' }, 1676 stability: { emoji: 'ð¡ï¸', short: 'å®å®æ', desc: 'éåããªãã»ç¦å©åç' }, 1677 speed: { emoji: 'â¡', short: 'ã¹ãã¼ã', desc: 'ããåç»ã»ãã転è·' }, 1678 career_up: { emoji: 'ð', short: 'ãã£ãªã¢ã¢ãã', desc: 'ã¹ãã«ã»æé·' }, 1679 // === Tenshoku Q1 (çµé¨ã¬ãã«) === 1680 tenshoku_20s: { emoji: 'ð±', short: '20代ã»ç¬¬äºæ°å', desc: 'çµé¨3å¹´æªæº' }, 1681 young_mid: { emoji: 'ð¿', short: 'è¥æãä¸å ', desc: 'çµé¨3ã10å¹´' }, 1682 senior: { emoji: 'ð³', short: 'ããã©ã³ã»ç®¡çè·', desc: 'çµé¨10年以ä¸' }, 1683 career_change: { emoji: 'ð', short: 'ãã£ãªã¢ãã§ã³ã¸', desc: 'ç°æ¥ç¨®ããã®è»¢è·' }, 1684 // === Tenshoku Q2 === 1685 tenshoku_strategy_consul: { emoji: 'ð¯', short: 'æ¦ç¥ã»çµå¶ã³ã³ãµã«', desc: 'MBBã»Big4ã»ããã£ãã¯' }, 1686 tenshoku_it_consul: { emoji: 'ð»', short: 'IT/DXã³ã³ãµã«', desc: 'Accentureã»Big4 DX' }, 1687 tenshoku_ma: { emoji: 'ð¼', short: 'M&Aã»FAS', desc: 'IBDã»FASã»PEãã¡ã³ã' }, 1688 tenshoku_engineer: { emoji: 'âï¸', short: 'ã¨ã³ã¸ãã¢', desc: 'ã¡ã¬ãã³ãã£ã¼ã»å¤è³IT' }, 1689 tenshoku_sales: { emoji: 'ð¤', short: 'å¶æ¥ã»ã»ã¼ã«ã¹', desc: 'å¤è³ã»æ¥ç³»ã»ãã³ãã£ã¼' }, 1690 tenshoku_marketing: { emoji: 'ð£', short: 'ãã¼ã±ãã£ã³ã°', desc: 'ãã©ã³ãã»ãã¸ã' }, 1691 tenshoku_accounting: { emoji: 'ð', short: 'ä¼è¨ã»ç¨åã»çµç', desc: 'Big4ã»FASã»äºæ¥ä¼ç¤¾' }, 1692 tenshoku_other: { emoji: 'â¨', short: 'ãã®ä»ã»å¹ åºã', desc: 'ããããç¸è«ããã' }, 1693 // === Tenshoku Q3 dynamic (伿¥ã¿ã¤ã) === 1694 mbb: { emoji: 'ð', short: 'MBBã»æ¦ç¥ãã¡ã¼ã ', desc: 'æä¸ä½ã®æ¦ç¥ãã¡ã¼ã ' }, 1695 big4: { emoji: 'ðï¸', short: 'Big4ã»ç·åãã¡ã¼ã ', desc: 'å¹ åºãç·åãã¡ã¼ã ' }, 1696 boutique: { emoji: 'ð', short: 'ããã£ãã¯ç³»', desc: 'å°éç¹åãã¡ã¼ã ' }, 1697 enterprise_strategy: { emoji: 'ð¬', short: 'äºæ¥ä¼ç¤¾ï¼æ¦ç¥ï¼', desc: 'æ¦ç¥ãã¸ã·ã§ã³' }, 1698 accenture_big4_dx: { emoji: 'â¡', short: 'Accentureã»Big4 DX', desc: 'DXå¤§è¦æ¨¡ããã¸ã§ã¯ã' }, 1699 foreign_it: { emoji: 'ð', short: 'å¤è³ITç³»ã³ã³ãµã«', desc: 'ã°ãã¼ãã«å±é' }, 1700 japan_sier: { emoji: 'ð¯', short: 'æ¥ç³»SIer䏿µ', desc: 'å¤§è¦æ¨¡ã·ã¹ãã ' }, 1701 enterprise_dx: { emoji: 'ð¢', short: 'äºæ¥ä¼ç¤¾ï¼DXï¼', desc: 'DXæ¨é²ãã¸ã·ã§ã³' }, 1702 foreign_ibd: { emoji: 'ðµ', short: 'å¤è³IBD', desc: 'æè³éè¡' }, 1703 japan_ibd: { emoji: 'ð¦', short: 'æ¥ç³»è¨¼å¸IBD', desc: 'å½
1703å æè³éè¡' }, 1704 fas: { emoji: 'ð', short: 'FAS', desc: 'Big4ã¢ããã¤ã¶ãªã¼' }, 1705 pe_fund: { emoji: 'ð¼', short: 'PEãã¡ã³ã', desc: 'ãã©ã¤ãã¼ãã¨ã¯ã¤ãã£' }, 1706 enterprise_ma: { emoji: 'ð¤', short: 'äºæ¥ä¼ç¤¾M&A室', desc: 'è²·ãæå´M&A' }, 1707 mega_venture: { emoji: 'ð', short: 'ã¡ã¬ãã³ãã£ã¼', desc: 'æé·ä¼æ¥' }, 1708 startup: { emoji: 'ð±', short: 'ã¹ã¿ã¼ãã¢ãã', desc: 'ã¢ã¼ãªã¼/ããã«' }, 1709 foreign_it_big: { emoji: 'ð', short: 'å¤è³ITï¼GAFAç³»ï¼', desc: 'ããã°ããã¯' }, 1710 japan_large: { emoji: 'ð¢', short: 'æ¥ç³»å¤§æ', desc: 'å®å®ã»å¤§ä¼æ¥' }, 1711 sier: { emoji: 'ð»', short: 'SIer', desc: 'ã·ã¹ãã éçº' }, 1712 foreign: { emoji: 'ð', short: 'å¤è³ç³»', desc: 'ã°ãã¼ãã«ä¼æ¥' }, 1713 big4_audit: { emoji: 'ð', short: 'Big4ç£æ»æ³äºº', desc: 'ç£æ»ã»ã¢ããã¤ã¶ãªã¼' }, 1714 enterprise_finance: { emoji: 'ð´', short: 'äºæ¥ä¼ç¤¾ï¼çµç財åï¼', desc: 'ã¤ã³ãã¦ã¹' }, 1715 tax_firm: { emoji: 'ð§¾', short: 'ç¨ç士æ³äºº', desc: 'ç¨åå°é' }, 1716 any: { emoji: 'ð', short: 'ãã ãããªã', desc: 'å¹ åºãè¦ãã' }, 1717 }; 1718 1719 // ============================================================ 1720 // ç«ã®åå¿ã»ãªãï¼é¸æè¢ããã£ããããæã®ä¸è¨ï¼ 1721 // ============================================================ 1722 const CAT_REACTIONS = { 1723 // Q1 çµé¨ã¬ãã« 1724 tenshoku_20s: 'ããã¼è¥ãï¼ããã³ã·ã£ã«æ ã§æ»ãããããâ¨', 1725 young_mid: 'ä¸çªè»¢è·ããããææãããï¼ãã£ã³ã¹ð¯', 1726 senior: '大ããã©ã³æ§ð«¡ å¹´åä¸ãã©ãã ã', 1727 career_change: 'åè ã ããï¼æå¤ã¨éãããðº', 1728 // Q2 è·ç¨®ï¼è»¢è·ï¼ 1729 tenshoku_strategy_consul: 'æ¦ç¥ã³ã³ãµã«ï¼è¯ããããã', 1730 tenshoku_it_consul: 'DXæ¡ä»¶ãã£ã¡ãå¤ãããçãç®', 1731 tenshoku_ma: 'M&Aç³»ã¯å¹´åã®ä¸æ¯ããã§ãã', 1732 tenshoku_engineer: 'ã¨ã³ã¸ãã¢éè¦ãã¾ã ã¾ã å¼·ãã', 1733 tenshoku_sales: 'å¶æ¥ã¯çµæåºãã°è·³ããï¼', 1734 tenshoku_marketing: 'ãã¼ã±ãæè¿ã¢ããããã', 1735 tenshoku_accounting: 'ä¼è¨ã»ç¨åã¯å®å®ã®çéð', 1736 tenshoku_other: 'å¹ åºãããããã', 1737 // Q3 伿¥ã¿ã¤ã 1738 mbb: 'MBBçããï¼æ°åå ¥ã£ã¦ãããð¥', 1739 big4: 'Big4ãå®å®æãã£ã¦ããã', 1740 boutique: 'ããã£ãã¯ãå°éæ§ã§åè² ï¼', 1741 enterprise_strategy: 'äºæ¥ä¼ç¤¾ã®æ¦ç¥ãã¸ãããã', 1742 accenture_big4_dx: 'DXæ¡ä»¶ã®æ¬ä¸¸ã ã', 1743 foreign_it: 'å¤è³ITãã°ãã¼ãã«å¿åð', 1744 japan_sier: 'æ¥ç³»SIer䏿µãå å®ï¼', 1745 enterprise_dx: 'äºæ¥ä¼ç¤¾DXãä»ã¢ãã', 1746 foreign_ibd: 'å¤è³IBDãã¬ãå¢ãããð°', 1747 japan_ibd: 'æ¥ç³»è¨¼å¸IBDãå®å®å¼·ã', 1748 fas: 'FASã財åã®ãããã§ãã·ã§ãã«', 1749 pe_fund: 'PEãã¡ã³ãï¼ä¸ä½å±¤çãã ã', 1750 enterprise_ma: 'äºæ¥ä¼ç¤¾M&A室ãéããã', 1751 mega_venture: 'ã¡ã¬ãã³ãå¢ããããã', 1752 startup: 'ã¹ã¿ã¼ãã¢ããï¼åéºã ãð', 1753 foreign_it_big: 'GAFAç³»ï¼å¤¢ãããã', 1754 japan_large: 'æ¥ç³»å¤§æãçéã®å®å®', 1755 sier: 'SIerãæå ã鏿', 1756 foreign: 'å¤è³ç³»ãã°ãã¼ãã«ã«ç¾½ã°ãã', 1757 big4_audit: 'Big4ç£æ»ãã´ã¼ã«ãã¹ã¿ã³ãã¼ã', 1758 enterprise_finance: 'äºæ¥ä¼ç¤¾çµçãè °ãæ®ãã¦', 1759 tax_firm: 'ç¨ç士æ³äººãå°éè·ã¨ãã¦', 1760 any: 'ãããå¹ åºãè¦ã¦ãããã', 1761 // Q4 éè¦ãããã® 1762 high_rate: 'ããããããé大äºï¼ð°', 1763 stability: 'å®å®ã大äºã«ãããããð¡', 1764 speed: 'ã¹ãã¼ãåè² ããäºè§£ð', 1765 career_up: 'æé·å¿åãããããð', 1766 }; 1767 const CAT_REACTIONS_FALLBACK = [ 1768 'äºè§£ï¼', 1769 'ãªãã»ã©ãã', 1770 'ãªãã±ã¼ãææ¡ãã', 1771 'ããããã', 1772 'ãµããµã', 1773 ]; 1774 1775 function SwipeDiagnostic({ mode, agents, onComplete }) { 1776 const isTenshoku = mode === 'tenshoku'; 1777 1778 // 質åã·ã¼ã±ã³ã¹ï¼ããªã¼ã©ã³ã¹: Q1âQ2âQ3ã転è·: Q1âQ2âQ3åçâQ4ï¼ 1779 const questionKeys = useMemo(() => { 1780 return isTenshoku ? ['q1', 'q2', 'q3_dyn', 'q4'] : ['q1', 'q2', 'q3']; 1781 }, [isTenshoku]); 1782 1783 const [qIdx, setQIdx] = useState(0); 1784 const [answers, setAnswers] = useState({}); 1785 const [liked, setLiked] = useState([]); 1786 const [catching, setCatching] = useState(null); // value of fish currently being caught 1787 const [catReaction, setCatReaction] = useState(null); // cat's speech bubble text 1788 const [tick, setTick] = useState(0); // drives fish motion 1789 const [finished, setFinished] = useState(false); 1790 const [showTutorial, setShowTutorial] = useState(true); 1791 const dismissTutorial = useCallback(() => setShowTutorial(false), []); 1792 1793 // éãåããããã®tick (50ms = 20fps, ååæ»ãã) 1794 useEffect(() => { 1795 if (finished) return; 1796 const id = setInterval(() => setTick(t => t + 1), 50); 1797 return () => clearInterval(id); 1798 }, [finished]); 1799 1800 // ç¾å¨ã®è³ªåã解決ï¼Q3転è·ã¯åçï¼ 1801 const currentQuestion = useMemo(() => {
1802 const key = questionKeys[qIdx]; 1803 if (!key) return null; 1804 if (key === 'q1') return isTenshoku ? Q1_TENSHOKU : Q1_FREELANCE; 1805 if (key === 'q2') return isTenshoku ? Q2_TENSHOKU : Q2_FREELANCE; 1806 if (key === 'q3') return Q3_FREELANCE; 1807 if (key === 'q4') return Q4_TENSHOKU; 1808 if (key === 'q3_dyn') { 1809 const q2vals = answers.q2 || []; 1810 const seen = new Set(); 1811 const opts = []; 1812 q2vals.forEach(v => { 1813 (Q3_OPTIONS_BY_Q2[v] || []).forEach(o => { 1814 if (!seen.has(o.value)) { 1815 seen.add(o.value); 1816 opts.push(o); 1817 } 1818 }); 1819 }); 1820 if (opts.length === 0) opts.push({ value: 'any', label: 'ãã ãããªã' }); 1821 return { 1822 id: 'q3', 1823 title: Q3_TENSHOKU.title || '叿ã®ä¼æ¥ã¿ã¤ãã¯ï¼', 1824 type: 'single', 1825 options: opts, 1826 }; 1827 } 1828 return null; 1829 }, [qIdx, questionKeys, isTenshoku, answers]); 1830 1831 const currentCards = useMemo(() => { 1832 if (!currentQuestion) return []; 1833 return currentQuestion.options.map(o => ({ 1834 value: o.value, 1835 label: o.label, 1836 ...(SWIPE_CARD_META[o.value] || { emoji: 'ð¡', short: o.label, desc: '' }), 1837 })); 1838 }, [currentQuestion]); 1839 1840 const isMulti = currentQuestion?.type === 'multi'; 1841 const maxLikes = isMulti ? (currentQuestion?.maxSelect || 2) : 1; 1842 1843 // 次ã®è³ªå㸠or 診æå®äº 1844 const advanceQuestion = useCallback((newAnswersObj) => { 1845 const nextQIdx = qIdx + 1; 1846 if (nextQIdx >= questionKeys.length) { 1847 setFinished(true); 1848 const finalAns = { q1: null, q2: [], q3: null, q4: null, ...newAnswersObj }; 1849 const results = computeRankedAgents(finalAns, mode, agents); 1850 setTimeout(() => onComplete(results), 520); 1851 return; 1852 } 1853 setAnswers(newAnswersObj); 1854 setQIdx(nextQIdx); 1855 setLiked([]); 1856 setCatching(null); 1857 setCatReaction(null); 1858 }, [qIdx, questionKeys, mode, agents, onComplete]); 1859 1860 // éãã¿ããã§æã¾ããï¼é£ã³ä¸ãã£ã¦ç«ã®å£ã¸ï¼ 1861 const catchFish = useCallback((cardValue) => { 1862 if (catching) return; 1863 if (liked.includes(cardValue)) return; 1864 if (!currentQuestion) return; 1865 if (showTutorial) setShowTutorial(false); 1866 setCatching(cardValue); 1867 // ç«ã®ä¸è¨ãªã¢ã¯ã·ã§ã³ 1868 const reaction = CAT_REACTIONS[cardValue] || CAT_REACTIONS_FALLBACK[Math.floor(Math.random() * CAT_REACTIONS_FALLBACK.length)]; 1869 setTimeout(() => setCatReaction(reaction), 420); 1870 setTimeout(() => { 1871 const qid = currentQuestion.id; 1872 if (isMulti) { 1873 const nextLiked = [...liked, cardValue]; 1874 if (nextLiked.length >= maxLikes) { 1875 const newAnswers = { ...answers, [qid]: nextLiked }; 1876 setTimeout(() => { 1877 setCatReaction(null); 1878 advanceQuestion(newAnswers); 1879 }, 900); 1880 } else { 1881 setLiked(nextLiked); 1882 setCatching(null); 1883 setTimeout(() => setCatReaction(null), 900); 1884 } 1885 } else { 1886 const newAnswers = { ...answers, [qid]: cardValue }; 1887 setTimeout(() => { 1888 setCatReaction(null); 1889 advanceQuestion(newAnswers); 1890 }, 900); 1891 } 1892 }, 620); 1893 }, [catching, liked, currentQuestion, showTutorial, isMulti, maxLikes, answers, advanceQuestion]); 1894 1895 // åéã®ãå®¶ã®ä½ç½®ãã¨æ³³ããã©ã¡ã¼ã¿ï¼æ±ºå®è«çã«seedï¼ 1896 // â ï¸ Rules of Hooks: æ©æ return ããåã«å¿ ãå¼ã¶ 1897 const fishLayout = useMemo(() => { 1898 const n = currentCards.length; 1899 return currentCards.map((c, i) => { 1900 const col = n <= 4 ? 2 : 3; 1901 const row = Math.floor(i / col); 1902 const colIdx = i % col; 1903 const rows = Math.ceil(n / col); 1904 const homeX = 10 + ((colIdx + 0.5) / col) * 80; 1905 const homeY = 32 + ((row + 0.5) / Math.max(rows, 1)) * 50; 1906 return { 1907 homeX, 1908 homeY, 1909 phaseX: (i * 1.7) % (Math.PI * 2), 1910 phaseY: (i * 2.3) % (Math.PI * 2), 1911 speedX: 0.7 + (i % 3) * 0.15, 1912 speedY: 0.9 + (i % 4) * 0.12, 1913 flipPhase: (i * 0.9) % (Math.PI * 2), 1914 }; 1915 }); 1916 }, [currentCards]); 1917 1918 if (finished) { 1919 return ( 1920 <div className="max-w-lg mx-auto"> 1921 <div className="text-center py-16"> 1922 <div className="text-6xl mb-4 animate-bounce">ð</div> 1923 <div className="inline-block w-10 h-10 border-4 border-brand-gold border-t-transparent roun
1923ded-full animate-spin mb-4"></div> 1924 <p className="text-sm text-gray-500">診æçµæãè¨ç®ä¸...</p> 1925 </div> 1926 </div> 1927 ); 1928 } 1929 1930 if (!currentQuestion || currentCards.length === 0) { 1931 return ( 1932 <div className="max-w-lg mx-auto text-center py-16"> 1933 <p className="text-sm text-gray-500">èªã¿è¾¼ã¿ä¸...</p> 1934 </div> 1935 ); 1936 } 1937 1938 // 鲿ãã¼ã»ã³ãã¼ã¸ 1939 const innerProgress = isMulti ? (liked.length / maxLikes) : 0; 1940 const progressPct = ((qIdx + innerProgress) / questionKeys.length) * 100; 1941 1942 // ãã¥ã¼ããªã¢ã«ã¯æåã®è³ªåã§ã®ã¿è¡¨ç¤º 1943 const showTutorialNow = showTutorial && qIdx === 0; 1944 1945 // æ± ã®å¯¸æ³ï¼% ãã¼ã¹ã®åº§æ¨ç³»ï¼ 1946 // ä¸é¨ ~28% ã¯ç«ã®ããã岸ããä¸å¤® ~72% ã¯æ°´é¢ 1947 const PondHeight = 440; // px 1948 const WaterTop = 0.28; // pond 28% 以ä¸ãæ°´ 1949 1950 const time = tick * 0.05; // ç§ 1951 1952 return ( 1953 <div className="max-w-lg mx-auto"> 1954 {/* ========== 鲿ãããã¼ ========== */} 1955 <div className="mb-3 px-3"> 1956 <div className="flex items-center gap-2 mb-3"> 1957 <span className="text-[11px] font-black text-white bg-gradient-to-r from-amber-500 to-orange-500 rounded-full px-3 py-1 shadow-sm whitespace-nowrap"> 1958 Q{qIdx + 1} / {questionKeys.length} 1959 </span> 1960 <div className="flex-1 h-2.5 bg-gray-100 rounded-full overflow-hidden shadow-inner"> 1961 <div 1962 className="h-full bg-gradient-to-r from-amber-400 via-orange-400 to-orange-500 rounded-full transition-all duration-500 shadow-sm" 1963 style={{ width: `${Math.min(100, progressPct)}%` }} 1964 ></div> 1965 </div> 1966 <span className="text-[10px] font-bold text-gray-500 whitespace-nowrap"> 1967 {Math.round(progressPct)}% 1968 </span> 1969 </div> 1970 </div> 1971 1972 {/* ========== 質åã¿ã¤ãã« ========== */} 1973 <div key={`q-${qIdx}`} className="animate-fade-in-up"> 1974 <div className="mb-3 px-3 text-center"> 1975 <h3 className="text-2xl font-black text-gray-900 leading-tight"> 1976 {currentQuestion.title} 1977 </h3> 1978 <p className="text-[11px] text-sky-600/80 font-bold mt-1"> 1979 {isMulti 1980 ? `ð£ æ°ã«ãªãéã${maxLikes}å¹ã¿ãããã¦ç«ã«ããã¦` 1981 : `ð£ æ°ã«ãªãéãã¿ãããã¦ç«ã«ãããã`} 1982 </p> 1983 {isMulti && ( 1984 <div className="flex items-center justify-center gap-1 mt-1 text-orange-500 font-black text-[11px]"> 1985 {Array.from({ length: maxLikes }).map((_, i) => ( 1986 <span key={i} className={i < liked.length ? 'opacity-100' : 'opacity-25'}>ð</span> 1987 ))} 1988 <span className="ml-1">{liked.length}/{maxLikes}</span> 1989 </div> 1990 )} 1991 </div> 1992 1993 {/* ========== æ± ï¼æ°´æé¤¨ï¼ ========== */} 1994 <div 1995 className="relative rounded-3xl border-2 border-sky-300 shadow-inner overflow-hidden select-none" 1996 style={{ height: `${PondHeight}px` }} 1997 > 1998 {/* 空ï¼å²¸ã®ä¸ï¼ */} 1999 <div className="absolute inset-x-0 top-0 bg-gradient-to-b from-amber-50 via-orange-50 to-amber-100" style={{ height: `${WaterTop * 100}%` }}></div> 2000 {/* æ°´é¢ */} 2001 <div className="absolute inset-x-0 bottom-0 bg-gradient-to-b from-sky-200 via-sky-300 to-blue-400" style={{ top: `${WaterTop * 100}%` }}> 2002 {/* æ°´é¢ã®ãã©ãã© */} 2003 <div className="absolute inset-x-0 top-0 h-3 bg-gradient-to-b from-white/60 to-transparent"></div> 2004 {/* æ°´ä¸ã®å ã®æºãã */} 2005 <div 2006 className="absolute inset-x-0 top-0 h-6 opacity-30" 2007 style={{ 2008 background: 'repeating-linear-gradient(90deg, transparent 0, transparent 15px, rgba(255,255,255,0.5) 15px, rgba(255,255,255,0.5) 18px)', 2009 transform: `translateX(${Math.sin(time * 0.8) * 8}px)`, 2010 }} 2011 ></div> 2012 </div> 2013 2014 {/* ç«ï¼å²¸ã®ä¸ã§å¾ æ©ï¼ */} 2015 <div className="absolute left-1/2 -translate-x-1/2 z-10 flex flex-col items-center pointer-events-none" style={{ top: '4px' }}> 2016 <div 2017 className="text-[72px] leading-none drop-shadow-lg transition-transform duration-150" 2018 style={{ 2019 transform: catching 2020 ? 'scale(1.3) translateY(-6px)' 2021 : `scale(1) translateY(${Math.sin(time * 1.4) * 2}px)`, 2022 }} 2023 > 2024 {catching ? 'ð' : (liked.length > 0 ? 'ðº' : 'ð±')} 2025 </div> 2026 {/* ç«ã®å¹ãåºãï¼ãªã¢ã¯ã·ã§ã³ï¼ */} 2027 {catReaction && ( 2028 <div className="absolute top-full left-1/2 -translate-x-1/2 mt-1 bg-white border-2 border-amber-300 rounded-2xl px-3 py-1.5 shadow-lg whitespace-nowrap animate-pop-in"> 2029 <div className="text-[11px] font-black text-gray-800">{catReaction}</div> 2030 <div className="absolute -top-1.5 left-1/2 -translate-x-1/2 w-3 h-3 bg-white border-t-2 border-l-2 border-amber-300 rotate-45"></div> 2031 </div> 2032 )} 2033 </div> 2034 2035 {/* éãã¡ï¼æ°´ä¸ã§æ³³ãï¼ */} 2036 {currentCards.map((c, i) => { 2037 const caught = liked.includes(c.value); 2038 const isCatching = catching === c.value; 2039 if (caught && !isCatching) return null; // é£ã¹ããã¦æ¶ãã 2040 2041 const p = fishLayout[i]; 2042 // æ³³ãã®ãµããµãï¼% ãã¼ã¹ï¼ 2043 const swimX = Math.sin(time * p.speedX + p.phaseX) * 10;
2044 const swimY = Math.cos(time * p.speedY + p.phaseY) * 5; 2045 2046 const posX = p.homeX + swimX; 2047 const posY = p.homeY + swimY; 2048 2049 // ãã£ããä¸ã¯ç«ã®å£ï¼top centerï¼ã«åãã£ã¦ä¸ç´ç·ã«é£ã¶ 2050 const flyStyle = isCatching ? { 2051 left: '50%', 2052 top: '8%', 2053 transform: 'translate(-50%, -50%) scale(0.2) rotate(20deg)', 2054 opacity: 0, 2055 transition: 'left 0.6s cubic-bezier(0.5, 0, 0.75, 0.5), top 0.6s cubic-bezier(0.5, 0, 0.75, 0.5), transform 0.6s ease-in, opacity 0.6s ease-in 0.2s', 2056 } : { 2057 left: `${posX}%`, 2058 top: `${posY}%`, 2059 transform: `translate(-50%, -50%) rotate(${Math.sin(time * 0.7 + p.flipPhase) * 4}deg)`, 2060 transition: 'left 0.1s linear, top 0.1s linear', 2061 }; 2062 2063 return ( 2064 <button 2065 key={c.value} 2066 type="button" 2067 onClick={() => catchFish(c.value)} 2068 disabled={!!catching} 2069 className="absolute flex flex-col items-center group focus:outline-none" 2070 style={{ 2071 ...flyStyle, 2072 cursor: catching ? 'default' : 'grab', 2073 }} 2074 > 2075 {/* éã®æ¬ä½ï¼ã«ã¼ãé¢¨ï¼ */} 2076 <div className="relative bg-gradient-to-br from-cyan-100 via-white to-sky-100 rounded-[22px] border-[3px] border-sky-400 px-2.5 py-1.5 shadow-lg group-hover:shadow-2xl group-hover:scale-110 group-active:scale-95 transition-all"> 2077 {/* ãã©ãã©ãã¼ã«ã¼ */} 2078 <div className="absolute -top-1 -right-1 w-3 h-3 bg-yellow-300 rounded-full shadow animate-pulse"></div> 2079 <div className="flex items-center gap-1.5"> 2080 <span className="text-2xl leading-none">{c.emoji || 'ð'}</span> 2081 <div className="text-[10px] font-black text-gray-800 leading-tight whitespace-nowrap"> 2082 {c.short || c.label} 2083 </div> 2084 </div> 2085 {/* å°¾ã³ã */} 2086 <div className="absolute top-1/2 -left-1.5 -translate-y-1/2 w-0 h-0 border-t-[6px] border-b-[6px] border-r-[8px] border-t-transparent border-b-transparent border-r-sky-400"></div> 2087 </div> 2088 </button> 2089 ); 2090 })} 2091 2092 {/* æ°æ³¡ã¨ãã§ã¯ãï¼è£ é£¾ï¼ */} 2093 {!catching && Array.from({ length: 6 }).map((_, i) => { 2094 const bubblePhase = (time * 0.4 + i * 1.1) % 3; 2095 const left = 10 + (i * 17) % 80; 2096 const bottom = (bubblePhase / 3) * 70; // 0ã70% 2097 const size = 3 + (i % 3) * 2; 2098 const opacity = Math.max(0, 1 - bubblePhase / 3) * 0.6; 2099 return ( 2100 <div 2101 key={i} 2102 className="absolute rounded-full bg-white pointer-events-none" 2103 style={{ 2104 left: `${left}%`, 2105 bottom: `${bottom}%`, 2106 width: `${size}px`, 2107 height: `${size}px`, 2108 opacity, 2109 }} 2110 ></div> 2111 ); 2112 })} 2113 2114 {/* ãã¥ã¼ããªã¢ã«: æåã®è³ªåã®ã¿ */} 2115 {showTutorialNow && ( 2116 <div className="absolute inset-0 bg-black/50 backdrop-blur-[2px] flex items-center justify-center z-20 rounded-3xl animate-fade-in" onClick={dismissTutorial}> 2117 <div className="bg-white rounded-3xl p-5 max-w-[280px] mx-4 text-center shadow-2xl animate-pop-in"> 2118 <div className="text-5xl mb-2">ð£ð±</div> 2119 <h4 className="text-sm font-black text-gray-900 mb-2">ç«ã«éãããããï¼</h4> 2120 <p className="text-[11px] text-gray-600 mb-3 leading-relaxed"> 2121 æ± ãæ³³ãã§ãéã<br/> 2122 <b className="text-orange-500">鏿è¢</b>ã ã ð<br/> 2123 æ°ã«ãªãéã<b className="text-sky-600">ã¿ãã</b>ããã¨<br/> 2124 ç«ããã£ãããã¦é£ã¹ããð¾ 2125 </p> 2126 <button 2127 onClick={(e) => { e.stopPropagation(); dismissTutorial(); }} 2128 className="w-full py-2.5 bg-gradient-to-r from-sky-500 to-blue-500 text-white text-xs font-black rounded-full shadow-md hover:shadow-lg transition-all" 2129 > 2130 ããã£ãï¼ é£ãéå§ ð£ 2131 </button> 2132 </div> 2133 </div> 2134 )} 2135 </div> 2136 </div> 2137 </div> 2138 ); 2139 } 2140 2141 // ============================================================ 2142 // COMPONENT: ã³ã³ãµã«é©æ§è¨ºæï¼tekiseiã¢ã¼ãã»èªå·±å®çµï¼ 2143 // ============================================================ 2144 const TEKISEI_QUESTIONS = [ 2145 { key: 'age', title: 'ããªãã®å¹´é½¢ã¯ï¼', sub: 'æ³å®ãã¸ã·ã§ã³ã»å¹´åã¬ã³ã¸ã®ç®åºã«ä½¿ãã¾ã', options: [ 2146 { value: 'a1', label: '24æ³ä»¥ä¸' }, 2147 { value: 'a2', label: '25ã29æ³' }, 2148 { value: 'a3', label: '30ã34æ³' }, 2149 { value: 'a4', label: '35ã39æ³' }, 2150 { value: 'a5', label: '40æ³ä»¥ä¸' }, 2151 ]}, 2152 { key: 'job', title: 'ç¾å¨ã®è·ç¨®ã«ä¸çªè¿ãã®ã¯ï¼', sub: '', options: [ 2153 { value: 'kikaku', label: 'ä¼ç»ã»ãã¼ã±ãã£ã³ã°' }, 2154 { value: 'sales', label: 'å¶æ¥ã»ã»ã¼ã«ã¹' }, 2155 { value: 'engineer', label: 'ã¨ã³ã¸ãã¢ã»IT' }, 2156 { value: 'keiri', label: 'çµçã»è²¡åã»ä¼è¨' }, 2157 { value: 'consul', label: 'ã³ã³ãµã«æ¥çï¼ç¾è·ï¼' }, 2158 { value: 'other', label: 'å ¬åå¡ã»ãã®ä»' }, 2159 ]}, 2160 { key: 'salary', title: 'ç¾å¨ã®å¹´åã¯ï¼', sub: 'æ³å®ã¢ããå¹ ã®ç®åºã«ä½¿ãã¾ãï¼å¿åã»ä¿åãã
2160ã¾ããï¼', options: [ 2161 { value: 's1', label: 'ã400ä¸å' }, 2162 { value: 's2', label: '400ã600ä¸å' }, 2163 { value: 's3', label: '600ã800ä¸å' }, 2164 { value: 's4', label: '800ã1,000ä¸å' }, 2165 { value: 's5', label: '1,000ä¸åã' }, 2166 ]}, 2167 { key: 'solve', title: 'è¤éãªåé¡ããåè§£ãã¦æ§é çã«æ´çããã®ã¯', sub: 'ãã¡ã¼ã ãæãéè¦ããè¦ç´ ã§ã', options: [ 2168 { value: 'high', label: '徿ï¼ä»äºã§ãããã£ã¦ããï¼' }, 2169 { value: 'mid', label: 'ããç¨åº¦ã§ãã' }, 2170 { value: 'low', label: 'è¦æãªã»ã' }, 2171 ]}, 2172 { key: 'domain', title: 'ããã®æ¥çã»é åãªã詳ãããã¨è¨ããå°éåéããã', sub: 'ä¾: éèã»è£½é ã»å»çã»ITã»äººäºã»ä¼è¨ãªã©', options: [ 2173 { value: 'high', label: 'ããï¼å®åçµé¨3年以ä¸ï¼' }, 2174 { value: 'mid', label: 'å°ããã' }, 2175 { value: 'low', label: 'ç¹ã«ãªã' }, 2176 ]}, 2177 { key: 'pm', title: 'è¤æ°äººãå·»ãè¾¼ãã ããã¸ã§ã¯ããæ¨é²ããçµé¨ã¯', sub: '社å ããã¸ã§ã¯ãã»å§å¡ä¼ã»ã¤ãã³ãéå¶ã
2177ªã©ãå«ã¿ã¾ã', options: [ 2178 { value: 'high', label: 'ãªã¼ãã¼ã¨ãã¦çµé¨ãã' }, 2179 { value: 'mid', label: 'ã¡ã³ãã¼ã¨ãã¦çµé¨ãã' }, 2180 { value: 'low', label: 'ã»ã¨ãã©ãªã' }, 2181 ]}, 2182 { key: 'logic', title: '話ãã¨ããçµè«ããå ¥ãã»ãã ', sub: 'ç´æã§é¸ãã§ãã ãã', options: [ 2183 { value: 'high', label: 'ããªãå½ã¦ã¯ã¾ã' }, 2184 { value: 'mid', label: 'ã©ã¡ããã¨ããã°å½ã¦ã¯ã¾ã' }, 2185 { value: 'low', label: 'ãã¾ãå½ã¦ã¯ã¾ããªã' }, 2186 ]}, 2187 { key: 'stamina', title: 'ç´æåã®éä¸çãªãã¼ãã¯ã¼ã¯ã¯è¦ã«ãªããªã', sub: '', options: [ 2188 { value: 'high', label: 'ããªãå½ã¦ã¯ã¾ã' }, 2189 { value: 'mid', label: 'ã©ã¡ããã¨ããã°å½ã¦ã¯ã¾ã' }, 2190 { value: 'low', label: 'ãã¾ãå½ã¦ã¯ã¾ããªã' }, 2191 ]}, 2192 { key: 'client', title: 'å対é¢ã®å½¹è·è ã¨è°è«ããã®ã¯', sub: '', options: [ 2193 { value: 'high', label: '徿ãªã»ã' }, 2194 { value: 'mid', label: 'æ®é' }, 2195 { value: 'low', label: 'æ£ç´ãè¦æ' }, 2196 ]}, 2197 { key: 'learn', title: 'æªç¥ã®æ¥çãçæéã§ãã£ããã¢ããããã®ã¯', sub: '', options: [ 2198 { value: 'high', label: '徿ãªã»ã' }, 2199 { value: 'mid', label: 'æ®é' }, 2200 { value: 'low', label: 'æ£ç´ãè¦æ' }, 2201 ]}, 2202 ]; 2203 2204 // 年齢帯 â æ³å®ãã¸ã·ã§ã³ã»å¹´åã¬ã³ã¸ï¼ä¸åï¼ãä¸è¬çãªå ¥ç¤¾æç®å® 2205 const TEKISEI_AGE_SALARY = { 2206 a1: { pos: 'ã¢ããªã¹ã', low: 550, high: 900 }, 2207 a2: { pos: 'ã¢ããªã¹ããã³ã³ãµã«ã¿ã³ã', low: 600, high: 1000 }, 2208 a3: { pos: 'ã³ã³ãµã«ã¿ã³ããã·ãã¢ã³ã³ãµã«ã¿ã³ã', low: 700, high: 1200 }, 2209 a4: { pos: 'ã·ãã¢ã³ã³ãµã«ã¿ã³ããããã¼ã¸ã£ã¼', low: 850, high: 1400 }, 2210 a5: { pos: 'ããã¼ã¸ã£ã¼ä»¥ä¸', low: 1000, high: 1600 }, 2211 }; 2212 const TEKISEI_CUR_SALARY_MID = { s1: 350, s2: 500, s3: 700, s4: 900, s5: 1150 }; 2213 2214 function computeTekisei(answers) { 2215 const traitPt = { high: 25, mid: 15, low: 6 }; 2216 // ã¹ãã«3åï¼åé¡è§£æ±ºã»æ¥çç¥è¦ã»PMï¼ã¯æ¡ç¨è¦ä»¶ã¨ãã¦éã¿1.4å 2217 const skills = ['solve', 'domain', 'pm'].reduce((s, k) => s + (traitPt[answers[k]] || 6) * 1.4, 0); // 25.2ã105 2218 const traits = ['logic', 'stamina', 'client', 'learn'].reduce((s, k) => s + (traitPt[answers[k]] || 6), 0); // 24ã100 2219 const ageAdj = { a1: 4, a2: 6, a3: 2, a4: -4, a5: -8 }[answers.age] || 0; 2220 const jobAdj = { consul: 8, kikaku: 6, engineer: 5, keiri: 5, sales: 3, other: 0 }[answers.job] || 0; 2221 let score = Math.round((skills + traits) * 0.39 + ageAdj + jobAdj + 8); 2222 score = Math.max(10, Math.min(97, score)); 2223 const competencies = { solve: answers.solve || 'low', domain: answers.domain || 'low', pm: answers.pm || 'low' }; 2224 const band = score >= 72 ? 'high' : score >= 48 ? 'mid' : 'low'; 2225 const sal = TEKISEI_AGE_SALARY[answers.age] || TEKISEI_AGE_SALARY.a3; 2226 const curMid = TEKISEI_CUR_SALARY_MID[answers.salary] || 500; 2227 const diffLow = sal.low - curMid; 2228 const diffHigh = sal.high - curMid; 2229 return { score, band, sal, curMid, diffLow, diffHigh, competencies }; 2230 } 2231 2232 function TekiseiFlow({ trackEvent, updateStatus, statuses, onExit }) { 2233 const [stage, setStage] = useState('start'); // 'start' | 'q' | 'result' 2234 const [qIndex, setQIndex] = useState(0); 2235 const [answers, setAnswers] = useState({}); 2236 // stageä¾å: çµæè¡¨ç¤ºæã«è§£æ±ºï¼Supabaseãªã¢ã¼ãè¨å®ã®èªè¾¼å®äºå¾ã®å¤ã使ãããï¼ 2237 const consulAgents = useMemo(() => resolveAgents(ARTICLE_CONFIGS['tenshoku_strategy_consul'] || ARTICLE_CONFIGS.strategy).filter(a => a.affiliateUrl).slice(0, 3), [stage]); 2238 const result = useMemo(() => (stage === 'result' ? computeTekisei(answers) : null), [stage, answers]); 2239 2240 useEffect(() => { if (stage === 'result') trackEvent('result_view'); }, [stage]); 2241 2242 const answer = (key, value) => { 2243 const next = { ...answers, [key]: value }; 2244 setAnswers(next); 2245 if (qIndex + 1 < TEKISEI_QUESTIONS.length) { 2246 setQIndex(qIndex + 1); 2247 } else { 2248 setStage('result'); 2249 } 2250 }; 2251 2252 const handleCtaClick = (agent) => { 2253 if (agent.affiliateUrl) { 2254 window.open(agent.affiliateUrl, '_blank'); 2255 updateStatus(agent.id, 'clicked'); 2256 } 2257 }; 2258 2259 // ---- ã¹ã¿ã¼ãç»é¢ ---- 2260 if (stage === 'start') { 2261 return ( 2262 <div className="max-w-lg mx-auto px-4 pt-3 pb-4"> 2263 <div className="bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden"> 2264 <div className="bg-gradient-to-br from-indigo-50 to-blue-50 px-5 py-6 text-center"> 2265 <div className="text-3xl mb-2">ð§</div> 2266 <h1 className="text-xl font-bold text-gray-800 mb
2266-2 leading-snug">ã³ã³ãµã«é©æ§è¨ºæ</h1> 2267 <p className="text-xs text-gray-500 mb-1">ãã¡ã¼ã ãæ¡ç¨ã§éè¦ããè¦ç´ ãå«ã10ã®è³ªåã§ã</p> 2268 <p className="text-xs text-gray-500 mb-5">ããªãã®ã³ã³ãµã«é©æ§åº¦ã¨<b className="text-indigo-600">æ³å®å¹´åã¬ã³ã¸</b>ããããã¾ã</p> 2269 <button 2270 onClick={() => { setStage('q'); trackEvent('start_diagnosis'); }} 2271 className="w-full max-w-xs mx-auto py-3.5 rounded-xl bg-indigo-600 hover:bg-indigo-700 text-white font-bold text-base transition-all hover:shadow-lg" 2272 > 2273 1åã§è¨ºæã¹ã¿ã¼ã â 2274 </button> 2275 <p className="text-[11px] text-gray-400 mt-3">ç¡æã»ç»é²ä¸è¦ã»ããã«çµæããããã¾ã</p> 2276 </div> 2277 <div className="px-4 py-2.5 bg-gray-50 border-t border-gray-100"> 2278 <div className="flex items-center justify-center gap-4 text-[11px] text-gray-500"> 2279 <span>â å®å ¨ç¡æ</span> 2280 <span>â ç´1åã§å®äº</span> 2281 <span>â 驿§åº¦ï¼æ³å®å¹´å</span> 2282 </div> 2283 </div> 2284 </div> 2285 <div className="flex items-center justify-between gap-2 mt-3 px-1"> 2286 <button onClick={onExit} className="text-[11px] text-gray-400 hover:text-gray-600">â ä»ã®è¨ºæãè¦ã</button> 2287 <span className="text-[10px] text-gray-400">PR</span> 2288 </div> 2289 </div> 2290 ); 2291 } 2292 2293 // ---- 質åç»é¢ ---- 2294 if (stage === 'q') { 2295 const q = TEKISEI_QUESTIONS[qIndex]; 2296 const progress = ((qIndex + 1) / TEKISEI_QUESTIONS.length) * 100; 2297 return ( 2298 <div className="max-w-lg mx-auto px-4 pt-3 pb-4"> 2299 <div className="text-right mb-1"><span className="text-[10px] text-gray-400">PR</span></div> 2300 <div className="flex items-center gap-2 mb-4"> 2301 {qIndex > 0 && ( 2302 <button onClick={() => setQIndex(qIndex - 1)} className="text-xs text-gray-400 hover:text-gray-600 shrink-0">â æ»ã</button> 2303 )} 2304 <div className="flex-1 h-1.5 bg-gray-100 rounded-full overflow-hidden"> 2305 <div className="h-full bg-indigo-500 rounded-full transition-all duration-300" style={{ width: progress + '%' }} /> 2306 </div> 2307 </div> 2308 <div className="text-center mb-4"> 2309 <p className="text-[11px] text-gray-400 mb-1">質å {qIndex + 1} / {TEKISEI_QUESTIONS.length}</p> 2310 <h2 className="text-lg font-bold text-gray-800 leading-snug">{q.title}</h2> 2311 {q.sub && <p className="text-[11px] text-gray-400 mt-1">{q.sub}</p>} 2312 </div> 2313 <div className="space-y-2.5 max-w-sm mx-auto"> 2314 {q.options.map(opt => ( 2315 <button 2316 key={opt.value} 2317 onClick={() => answer(q.key, opt.value)} 2318 className="w-full py-3 px-4 rounded-xl border border-gray-200 bg-white hover:border-indigo-400 hover:bg-indigo-50 text-left text-sm text-gray-700 font-medium transition-all" 2319 > 2320 {opt.label} 2321 </button> 2322 ))} 2323 </div> 2324 </div> 2325 ); 2326 } 2327 2328 // ---- çµæç»é¢ ---- 2329 if (stage === 'result' && result) { 2330 const bandInfo = { 2331 high: { label: 'ã³ã³ãµã«é©æ§ï¼é«ã', color: 'text-emerald-600', bar: 'bg-emerald-500', msg: 'æèæ§å¼ã»èæ§ã¨ãã«ã³ã³ãµã«ã¿ã³ãã®åãæ¹ã¨åã¿åã£ã¦ãã¾ãããã¨ã¯é¸è対çã®è³ªã§æ±ºã¾ãã¾ãã', cta: 'ã³ã³ãµã«è»¢è·ã®ããã«ç¸è«ããï¼ç¡æï¼' }, 2332 mid: { label: 'ã³ã³ãµã«é©æ§ï¼ç´ é¤ãã', color: 'text-indigo-600', bar: 'bg-indigo-500', msg: 'åå°ã¯ããã¾ããè¶³ããªãé¨åã¯ã±ã¼ã¹é¢æ¥å¯¾çã¨è·åçµæ´ã®è¨èªåã§åã¾ãç¯å²ã§ãã', cta: 'ããã«å¼·ã¿ã»å¼±ã¿ãèãã¦ã¿ãï¼ç¡æï¼' }, 2333 low: { label: 'ã³ã³ãµã«é©æ§ï¼ç¾æç¹ã§ã¯æªç¥æ°',
2333color: 'text-amber-600', bar: 'bg-amber-500', msg: 'èªå·±åçãã¼ã¹ã§ã¯æ éãªçµæã§ãããã ãèªå·±èªèã¨å¸å ´è©ä¾¡ã¯ãºã¬ããã¨ãå¤ããããã®é¢è«ã§è¦ãã±ã¼ã¹ã¯çããããã¾ããã', cta: 'ã¾ãã¯ããã«é©æ§ãèãã¦ã¿ãï¼ç¡æï¼' }, 2334 }[result.band]; 2335 const showUp = result.diffHigh > 0; 2336 return ( 2337 <div className="max-w-lg mx-auto px-4 pt-3 pb-6 space-y-5"> 2338 <div className="text-right"><span className="text-[10px] text-gray-400">PR</span></div> 2339 2340 {/* 驿§ã¹ã³ã¢ */} 2341 <div className="bg-white rounded-2xl shadow-lg border border-gray-100 px-5 py-6 text-center"> 2342 <p className="text-xs text-gray-400 mb-2">診æçµæ</p> 2343 <p className={`text-sm font-bold mb-3 ${bandInfo.color}`}>{bandInfo.label}</p> 2344 <div className="flex items-end justify-center gap-1 mb-3"> 2345 <span className={`text-5xl font-black ${bandInfo.color}`}>{result.score}</span> 2346 <span className="text-lg text-gray-400 font-bold mb-1">/100</span> 2347 </div> 2348 <div className="h-2.5 bg-gray-100 rounded-full overflow-hidden max-w-xs mx-auto mb-4"> 2349 <div className={`h-full rounded-full ${bandInfo.bar} transition-all duration-700`} style={{ width: result.score + '%' }} /> 2350 </div> 2351 <p className="text-xs text-gray-600 leading-relaxed max-w-sm mx-auto">{bandInfo.msg}</p> 2352 {/* ãã¡ã¼ã ãéè¦ãã3è¦ç´ ã®è©ä¾¡ */} 2353 <div className="mt-4 pt-3 border-t border-gray-100 max-w-sm mx-auto"> 2354 <p className="text-[10px] text-gray-400 mb-2">ãã¡ã¼ã ãæ¡ç¨ã§éè¦ãã3è¦ç´ </p> 2355 <div className="grid grid-cols-3 gap-1.5"> 2356 {[ 2357 { label: 'åé¡è§£æ±ºæè', key: 'solve' }, 2358 { label: 'æ¥çã»é åç¥è¦', key: 'domain' }, 2359 { label: 'PJæ¨é²çµé¨', key: 'pm' }, 2360 ].map(c => { 2361 const lv = result.competencies[c.key]; 2362 const style = lv === 'high' ? 'bg-emerald-50 text-emerald-700 border-emerald-200' : lv === 'mid' ? 'bg-indigo-50 text-indigo-600 border-indigo-200' : 'bg-gray-50 text-gray-500 border-gray-200'; 2363 const mark = lv === 'high' ? 'â å¼·ã¿' : lv === 'mid' ? 'â æ¨æº' : 'Ⳡ伸ã³ãã'; 2364 return ( 2365 <div key={c.key} className={`rounded-lg border px-1.5 py-2 text-center ${style}`}> 2366 <div className="text-[10px] font-bold leading-tight">{c.label}</div> 2367 <div className="text-[11px] font-black mt-1">{mark}</div> 2368 </div> 2369 ); 2370 })} 2371 </div> 2372 </div> 2373 </div> 2374 2375 {/* æ³å®å¹´å */} 2376 <div className="bg-white rounded-2xl shadow-lg border border-gray-100 px-5 py-5"> 2377 <p className="text-xs font-bold text-gray-500 mb-3">{result.band === 'low' ? 'ð° ä»®ã«ã³ã³ãµã«è»¢è·ãå®ç¾ããå ´åã®æ³å®å¹´åï¼ç®å®ï¼' : 'ð° ã³ã³ãµã«è»¢è·ããå ´åã®æ³å®å¹´åï¼ç®å®ï¼'}</p> 2378 <div className="bg-gradient-to-br from-indigo-50 to-blue-50 rounded-xl px-4 py-4 text-center mb-2"> 2379 <p className="text-[11px] text-gray-500 mb-1">æ³å®ãã¸ã·ã§ã³ï¼{result.sal.pos}</p> 2380 <p className="text-2xl font-black text-indigo-700">{result.sal.low.toLocaleString()}ã{result.sal.high.toLocaleString()}<span className="text-sm font-bold text-indigo-500">ä¸å</span></p> 2381 {showUp && ( 2382 <p className="text-xs font-bold text-emerald-600 mt-1.5"> 2383 ç¾å¨ã®å¹´å帯ãã {result.diffLow > 0 ? '+' + result.diffLow : '±0'}ã+{result.diffHigh}ä¸å 2384 </p> 2385 )} 2386 {!showUp && ( 2387 <p className="text-[11px] text-gray-500 mt-1.5">ç¾å¹´å帯ãé«ããããå¹´åãããè£éã»å¸å ´ä¾¡å¤ã®è¦³ç¹ã§ã®æ¤è¨ãããããã§ã</p> 2388 )} 2389 </div> 2390 <p className="text-[10px] text-gray-400 leading-relaxed">â»ä¸è¬çãªå ¥ç¤¾æã¬ã³ã¸ã®ç®å®ã§ããçµé¨ã»ãã¡ã¼ã ã»é¸èè©ä¾¡ã«ãã大ããç°ãªããæ¬è¨ºæã¯æ¡ç¨å¯å¦ãå¹´åãä¿è¨¼ãããã®ã§ã¯ããã¾ããã</p> 2391 </div> 2392 2393 {/* ã¨ã¼ã¸ã§ã³ãç´¹ä» */} 2394 <div> 2395 <div className="text-center mb-3"> 2396 <h2 className="text-base font-bold text-gray-800">{bandInfo.cta}</h2> 2397 <p className="text-[11px] text-gray-500 mt-1"> 2398 {result.band === 'low' 2399 ? 'ç¡æã®ãã£ãªã¢é¢è«ã§ã客観çãªé©æ§ã¨å¸å ´ä¾¡å¤ã確èªã§ãã¾ã' 2400 : 'ã³ã³ãµã«ç¹åã¨ã¼ã¸ã§ã³ããªããéå ¬éæ±äººã¨ã
2400±ã¼ã¹é¢æ¥å¯¾çã¾ã§ç¡æã§ã'} 2401 </p> 2402 </div> 2403 <div className="space-y-4"> 2404 {consulAgents.map((agent, i) => ( 2405 <AgentResultCard 2406 key={agent.id} 2407 agent={agent} 2408 matchScore={[93, 88, 84][i] || 80} 2409 order={i + 1} 2410 status={statuses[agent.id]?.status || 'pending'} 2411 onCtaClick={handleCtaClick} 2412 /> 2413 ))} 2414 </div> 2415 </div> 2416 2417 <div className="text-center pt-1"> 2418 <button 2419 onClick={() => { setStage('start'); setQIndex(0); setAnswers({}); }} 2420 className="text-sm text-indigo-500 hover:text-indigo-700 hover:underline transition-colors" 2421 > 2422 ð ããä¸åº¦è¨ºæãã 2423 </button> 2424 </div> 2425 </div> 2426 ); 2427 } 2428 2429 return null; 2430 } 2431 2432 // ============================================================ 2433 // MAIN APP 2434 // ============================================================ 2435 function AgentFinderApp() { 2436 const initialMode = useMemo(() => getInitialMode(), []); 2437 const [mode, setMode] = useState(initialMode || 'freelance'); 2438 const [modeSelected, setModeSelected] = useState(initialMode !== null); 2439 const config = useMemo(() => { 2440 const params = new URLSearchParams(window.location.search); 2441 const configKey = params.get('config') || (mode === 'tenshoku' ? 'tenshoku_strategy_consul' : 'strategy'); 2442 return ARTICLE_CONFIGS[configKey] || ARTICLE_CONFIGS.strategy; 2443 }, [mode]); 2444 const agents = useMemo(() => resolveAgents(config), [config]); 2445 const isTenshoku = mode === 'tenshoku'; 2446 2447 const [phase, setPhase] = useState(initialMode !== null ? 'start' : 'mode_select'); // 'mode_select' | 'start' | 'diagnostic' | 'diagnostic-simple' | 'result' | 'bulk-only' 2448 const [rankedAgents, setRankedAgents] = useState([]); 2449 const [statuses, setStatuses] = useState({}); 2450 2451 // iframeé«ãéç¥ 2452 useEffect(() => { 2453 const observer = new ResizeObserver(() => notifyHeight()); 2454 observer.observe(document.getElementById('root')); 2455 return () => observer.disconnect(); 2456 }, []); 2457 2458 useEffect(() => { notifyHeight(); }, [phase, rankedAgents, statuses]); 2459 2460 const updateStatus = useCallback((agentId, status) => { 2461 setStatuses(prev => ({ 2462 ...prev, 2463 [agentId]: { ...prev[agentId], status, clickedAt: status === 'clicked' ? new Date() : prev[agentId]?.clickedAt } 2464 })); 2465 // ã¯ãªãã¯ãã°ãlocalStorage + Supabaseã«è¨é² 2466 if (status === 'clicked') { 2467 const config = new URLSearchParams(window.location.search).get('config') || 'direct'; 2468 const currentMode = mode; 2469 // localStorageï¼å¾æ¹äºæï¼ 2470 try { 2471 const logs = JSON.parse(localStorage.getItem('af_click_log') || '[]'); 2472 logs.push({ agentId, timestamp: new Date().toISOString(), config }); 2473 localStorage.setItem('af_click_log', JSON.stringify(logs)); 2474 } catch(e) {} 2475 // 管çè ã»éçºç°å¢ã¯Supabaseéä¿¡ããªã 2476 let skipSupabase = false; 2477 try { 2478 if (localStorage.getItem('af_admin_auth') === 'true') skipSupabase = true; 2479 } catch(e) {} 2480 const host = (typeof window !== 'undefined' && window.location && window.location.hostname) || ''; 2481 if (host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0' || host.endsWith('.local')) skipSupabase = true; 2482 // Supabaseéä¿¡ 2483 if (!skipSupabase) { 2484 try { 2485 _supabase.from('click_logs').insert({ 2486 agent_id: agentId, 2487 agent_name: rankedAgents.find(r => r.agent.id === agentId)?.agent.shortName || agentId, 2488 config: config, 2489 mode: currentMode, 2490 user_agent: navigator.userAgent 2491 }).then(() => {}); 2492 } catch(e) {} 2493 } 2494 } 2495 }, [mode, rankedAgents]); 2496 2497 // ãã¡ãã«ã¤ãã³ã追跡 2498 const sessionId = useMemo(() => crypto.randomUUID(), []); 2499 const utmSource = useMemo(() => new URLSearchParams(window.location.search).get('utm_source') || 'direct', []); 2500 const trackEvent = useCallback((eventType) => { 2501 // 管çè ã»éçºç°å¢ã¯è¨æ¸¬ããªãï¼ã³ã¼ã«æã«æ¯åãã§ãã¯ãã¦ã¯ãã¼ã¸ã£å¤åãé²ãï¼ 2502 try { 2503 if (localStorage.getItem('af_admin_auth') === 'true') return; 2504 } catch(e) {} 2505 const host = (typeof window !== 'undefined' && window.location && window.location.hostname) || ''; 2506 if (host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0' || host.endsWith('.local')) return; 2507 try { 2508 _supabase.from('page_events').insert({ 2509 event_type: eventType, 2510 mode: mode, 2511 session_id: sessionId, 2512 user_agent: navigator.userAgent, 2513 utm_source: utmSource 2514 }).then(() => {}); 2515 } catch(e) {} 2516 }, [mode, sessionId, utmSource]); 2517 2518 // ååã©ã³ãã£ã³ã°è¨æ¸¬ 2519 useEffect(() => { trackEvent('landing'); }, []); 2520 2521 const handleDiagnosticComplete = useCallback((results) => { 2522 setRankedAgents(results); 2523 setPhase('result'); 2524 trackEvent('result_view'); 2525 }, [trackEvent]); 2526 2527 const handleRestart = useCallback(() => { 2528 setPhase(initialMode !== null ? 'start' : 'mode_select'); 2529 setRankedAgents([]); 2530 setStatuses({}); 2531 }, []); 2532 2533 const handleBulkOpen = useCallback((selectedAgents) => {
2534 selectedAgents.forEach(agent => { 2535 updateStatus(agent.id, 'clicked'); 2536 }); 2537 }, [updateStatus]); 2538 2539 // ã¢ã¼ã鏿ç»é¢ï¼ç¬ç« or 転è·ï¼ 2540 if (phase === 'mode_select') { 2541 return ( 2542 <div className="max-w-lg mx-auto px-4 pt-3 pb-4"> 2543 <div className="bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden"> 2544 <div className="bg-gradient-to-br from-brand-gold/5 to-brand-orange/5 px-5 py-6 text-center"> 2545 <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-gradient-to-br from-brand-gold to-brand-orange mb-3 shadow-lg shadow-brand-gold/20"> 2546 <svg className="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"> 2547 <path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> 2548 </svg> 2549 </div> 2550 <h1 className="text-xl font-bold text-gray-800 mb-1"> 2551 ã¨ã¼ã¸ã§ã³ã診æ 2552 </h1> 2553 <p className="text-xs text-gray-400 mb-5"> 2554 ããªãã®ç®çã«åã£ãã¨ã¼ã¸ã§ã³ããå³é¸ãã¾ã 2555 </p> 2556 2557 <div className="space-y-3 max-w-sm mx-auto"> 2558 {[ 2559 { 2560 mode: 'tenshoku', 2561 emoji: '\ud83c\udf1f', 2562 title: '転è·ã¨ã¼ã¸ã§ã³ã診æ', 2563 sub: 'ã³ã³ãµã«ã»äºæ¥ä¼ç¤¾ã»ã¹ã¿ã¼ãã¢ãã', 2564 tags: ['æ¦ç¥ã³ã³ãµã«', 'IT/DXã³ã³ãµã«', 'M&A/FAS', 'ã¨ã³ã¸ãã¢', 'å¶æ¥', 'ãã¼ã±', 'ä¼è¨/çµç'], 2565 gradient: 'from-emerald-500 to-teal-600', 2566 hoverGradient: 'hover:from-emerald-600 hover:to-teal-700', 2567 tagStyle: 'bg-white/20 text-white', 2568 }, 2569 { 2570 mode: 'freelance', 2571 emoji: '\ud83d\ude80', 2572 title: 'ããªã¼ã©ã³ã¹ã¨ã¼ã¸ã§ã³ã診æ', 2573 sub: 'æ¡ä»¶æ¢ãã»å価UPã»ã¨ã¼ã¸ã§ã³ãæ¯è¼', 2574 tags: ['æ¦ç¥ã³ã³ãµã«', 'IT/PMO', 'ã¨ã³ã¸ãã¢', 'SAP', 'ã»ãã¥ãªãã£'], 2575 gradient: 'from-brand-orange to-orange-600', 2576 hoverGradient: 'hover:from-brand-orange-dark hover:to-orange-700', 2577 tagStyle: 'bg-white/20 text-white', 2578 }, 2579 { 2580 mode: 'tekisei', 2581 emoji: 'ð§', 2582 title: 'ã³ã³ãµã«é©æ§è¨ºæ', 2583 sub: '驿§ã¹ã³ã¢ã¨æ³å®å¹´åã¬ã³ã¸ãããã', 2584 tags: ['驿§åº¦ã¹ã³ã¢', 'æ³å®å¹´å', 'ç´1åã»10å'], 2585 gradient: 'from-indigo-500 to-blue-600', 2586 hoverGradient: 'hover:from-indigo-600 hover:to-blue-700', 2587 tagStyle: 'bg-white/20 text-white', 2588 }, 2589 ].map(item => ( 2590 <button 2591 key={item.mode} 2592 onClick={() => { setMode(item.mode); setModeSelected(true); setPhase('start'); trackEvent('mode_select_' + item.mode); }} 2593 className={`w-full rounded-2xl bg-gradient-to-r ${item.gradient} ${item.hoverGradient} transition-all duration-200 hover:scale-[1.02] hover:shadow-lg shadow-md px-4 py-3.5 text-left group`} 2594 > 2595 <div className="flex items-start gap-3"> 2596 <div className="text-2xl shrink-0 mt-0.5">{item.emoji}</div> 2597 <div className="flex-1 min-w-0"> 2598 <div className="flex items-center gap-2"> 2599 <span className="text-[15px] font-bold text-white leading-tight">{item.title}</span> 2600 </div> 2601 <div className="text-[11px] mt-0.5 text-white/80">{item.sub}</div> 2602 <div className="flex flex-wrap gap-1 mt-2"> 2603 {item.tags.map(t => (
2604 <span key={t} className={`text-[10px] rounded-full px-1.5 py-0.5 font-medium ${item.tagStyle}`}>{t}</span> 2605 ))} 2606 </div> 2607 </div> 2608 <svg className="w-4 h-4 mt-1 text-white/60 group-hover:text-white group-hover:translate-x-1 transition-all shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"> 2609 <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" /> 2610 </svg> 2611 </div> 2612 </button> 2613 ))} 2614 </div> 2615 2616 <div className="flex items-center justify-center gap-3 mt-4 text-[10px] text-gray-400"> 2617 <span>å®å ¨ç¡æ</span> 2618 <span className="w-1 h-1 bg-gray-300 rounded-full" /> 2619 <span>10ç§ã§å®äº</span> 2620 <span className="w-1 h-1 bg-gray-300 rounded-full" /> 2621 <span>ä»ãã診æã§ãã</span> 2622 </div> 2623 </div> 2624 </div> 2625 2626 <div className="flex items-center justify-end gap-2 mt-3 px-1"> 2627 <span className="text-[10px] text-gray-400">PR</span> 2628 </div> 2629 </div> 2630 ); 2631 } 2632 2633 // ã³ã³ãµã«é©æ§è¨ºæï¼èªå·±å®çµããã¼ï¼ 2634 if (mode === 'tekisei') { 2635 return ( 2636 <TekiseiFlow 2637 trackEvent={trackEvent} 2638 updateStatus={updateStatus} 2639 statuses={statuses} 2640 onExit={() => { setMode('freelance'); setModeSelected(false); setPhase('mode_select'); }} 2641 /> 2642 ); 2643 } 2644 2645 // ã¹ã¿ã¼ãç»é¢ 2646 if (phase === 'start') { 2647 return ( 2648 <div className="max-w-lg mx-auto px-4 pt-3 pb-4"> 2649 <div className="bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden"> 2650 <div className="bg-gradient-to-br from-brand-gold/5 to-brand-orange/5 px-5 py-6 text-center"> 2651 <div className="text-3xl mb-2">{isTenshoku ? 'ð' : 'ð¯'}</div> 2652 <h1 className="text-xl font-bold text-gray-800 mb-2 leading-snug"> 2653 {isTenshoku ? (<>ããªãã«åã<br />転è·ã¨ã¼ã¸ã§ã³ãã診æ</>) : (<>ããªãã«åã<br />ããªã¼ã©ã³ã¹ã¨ã¼ã¸ã§ã³ãã診æ</>)} 2654 </h1> 2655 <p className="text-xs text-gray-500 mb-5"> 2656 {isTenshoku ? '4ã¤ã®è³ªåã«çããã ãã§ãæé©ãªè»¢è·ã¨ã¼ã¸ã§ã³ããè¦ã¤ããã¾ã' : '3ã¤ã®è³ªåã«çããã ãã§ãæé©ãªã¨ã¼ã¸ã§ã³ããè¦ã¤ããã¾ã'} 2657 </p> 2658 2659 <button 2660 onClick={() => { 2661 setPhase('diagnostic'); 2662 trackEvent('start_diagnosis'); 2663 }} 2664 className="w-full max-w-xs mx-auto py-3.5 rounded-xl bg-brand-orange hover:bg-brand-orange-dark text-white font-bold text-base transition-all hover:shadow-lg" 2665 > 2666 10ç§ã§è¨ºæã¹ã¿ã¼ã â 2667 </button> 2668 2669 <p className="text-[11px] text-gray-400 mt-3"> 2670 {'ç¡æã§ããã«çµæããããã¾ã'} 2671 </p> 2672 </div> 2673 2674 <div className="px-4 py-2.5 bg-gray-50 border-t border-gray-100"> 2675 <div className="flex items-center justify-center gap-4 text-[11px] text-gray-500"> 2676 <span>â å®å ¨ç¡æ</span> 2677 <span>â 10ç§ã§å®äº</span> 2678 <span>â ãããå¾¹åºæ¯è¼</span> 2679 </div> 2680 </div> 2681 </div> 2682 2683 <div className="flex items-center justify-end gap-2 mt-3 px-1"> 2684 <span className="text-[10px] text-gray-400">PR</span> 2685 </div> 2686 </div> 2687 ); 2688 } 2689 2690 // 診æä¸ï¼ãã£ããåç: æ¢åã®3-4åããã¼ï¼ 2691 if (phase === 'diagnostic') { 2692 return ( 2693 <div className="max-w-lg mx-auto px-4 pt-3 pb-4"> 2694 <DiagnosticFlow 2695 agents={agents} 2696 mode={mode} 2697 onComplete={handleDiagnosticComplete} 2698 /> 2699 </div> 2700 ); 2701 } 2702 2703 // çµæè¡¨ç¤º 2704 if (phase === 'result') { 2705 return ( 2706 <div className="max-w-lg mx-auto p-4 space-y-8"> 2707 <div className="text-right"><span className="text-xs text-gray-400">PR</span></div> 2708 <ResultView 2709 rankedAgents={rankedAgents} 2710 statuses={statuses} 2711 updateStatus={updateStatus} 2712 onBulkOpen={handleBulkOpen} 2713 onRestart={handleRestart} 2714 /> 2715 2716 {/* 䏿¬ç»é²ããã«ï¼6ä½ä»¥éã®ã¨ã¼ã¸ã§ã³ãï¼ */} 2717 {(() => { 2718 const extraAgents = rankedAgents.slice(5).map(r => r.agent).filter(a => a.affiliateUrl); 2719 return extraAgents.length > 0 ? ( 2720 <div className="mt-8"> 2721 <BulkRegistrationPanel 2722 agents={extraAgents} 2723 statuses={statuses} 2724 onBulkOpen={handleBulkOpen} 2725 /> 2726 </div> 2727 ) : null; 2728 })()} 2729 </div> 2730 ); 2731 } 2732 2733 // 䏿¬ç»é²ã®ã¿ï¼è¨ºæã¹ãããï¼â é åå¥ã«5社ãã¤è¡¨ç¤º 2734 if (phase === 'bulk-only') { 2735 const categories = isTenshoku 2736 ? [ 2737 { key: 'tenshoku_strategy_consul', label: 'æ¦ç¥ã»çµå¶ã³ã³ãµã«ã¿ã³ã', icon: 'ð¢', desc: 'æ¦ç¥ãã¡ã¼ã ã»çµå¶ã³ã³ãµã«ã¸ã®è»¢è·' }, 2738 { key: 'tenshoku_it_consul', label: 'ITã»DXã³ã³ãµã«ã¿ã³ã', icon: 'ð»', desc: 'ITã³ã³ãµã«ã»DXæ¨é²ã¸ã®è»¢è·' }, 2739 { key: 'tenshoku_ma', label: 'M&Aã»è²¡åã¢ããã¤ã¶ãªã¼', icon: 'ð°', desc: 'FAS/IBD/PEã¸ã®è»¢è·' }, 2740 { key: 'tenshoku_engineer', label: 'ã¨ã³ã¸ãã¢ã»ITæè¡è·', icon: 'â¡', desc: 'ITã¨ã³ã¸ãã¢ã»æè¡è·ã¸ã®è»¢è·' }, 2741 { key: 'tenshoku_accounting', label: 'ä¼è¨ã»ç¨åã»çµç', icon: 'ð', desc: 'çµçã»è²¡åã»ä¼è¨ã¸ã®è»¢è·' }, 2742 { key: 'tenshoku_sales', label: 'å¶æ¥ã»ã»ã¼ã«ã¹', icon: 'ð¤', desc: 'å¶æ¥ã»ã»ã¼ã«ã¹è·ã¸ã®è»¢è·' }, 2743 { key: 'tenshoku_marketing', label: 'ãã¼ã±ãã£ã³ã°', icon: 'ð£', desc: 'ãã¼ã±ãã£ã³ã°è·ã¸ã®è»¢è·' }, 2744 { key: 'tenshoku_other', label: 'ãã®ä»', icon: 'ð', desc: 'ãã®ä»ã®è·ç¨®ã¸ã®è»¢è·' }, 2745 ] 2746 : [ 2747 { key: 'strategy', label: 'æ¦ç¥ã³ã³ãµã«', icon: 'ð¯', desc: 'æ¦ç¥çå®ã»æ°è¦äºæ¥ã»M&A' },
2748 { key: 'biz_consul', label: 'æ¥åã³ã³ãµã«', icon: 'ð', desc: 'æ¥åæ¹åã»BPR' }, 2749 { key: 'it_consul', label: 'ITã³ã³ãµã« / PMO', icon: 'ð»', desc: 'ã·ã¹ãã å°å ¥ã»PMOã»DX' }, 2750 { key: 'engineering', label: 'ã¨ã³ã¸ãã¢ãªã³ã°', icon: 'â¡', desc: 'éçºã»ã¤ã³ãã©ã»SES' }, 2751 { key: 'sap', label: 'SAPæ¡ä»¶', icon: 'ð§', desc: 'SAPå°å ¥ã»éç¨ä¿å®' }, 2752 { key: 'security', label: 'ã»ãã¥ãªãã£', icon: 'ð¡ï¸', desc: 'ã»ãã¥ãªãã£ã³ã³ãµã«ã»ã¨ã³ã¸ãã¢' }, 2753 ]; 2754 2755 // ã«ãã´ãªãã¨ã®ã¨ã¼ã¸ã§ã³ãã¨ãã§ãã¯ç¶æ ã管ç 2756 const allCatData = categories.map(cat => { 2757 const cfg = ARTICLE_CONFIGS[cat.key]; 2758 if (!cfg) return null; 2759 const catAgents = resolveAgents(cfg).slice(0, 5); 2760 return { ...cat, agents: catAgents }; 2761 }).filter(Boolean); 2762 2763 return ( 2764 <BulkOnlyView 2765 categories={allCatData} 2766 statuses={statuses} 2767 updateStatus={updateStatus} 2768 onBack={() => setPhase('start')} 2769 /> 2770 ); 2771 } 2772 2773 return null; 2774 } 2775 2776 // ============================================================ 2777 // Supabase API ããè¨å®ãéåæèªã¿è¾¼ã¿ â ã°ãã¼ãã«å¤æ°ã䏿¸ããã¦åã¬ã³ãã¼ 2778 // ============================================================ 2779 let _remoteSettingsResolve; 2780 const _remoteSettingsReady = new Promise(r => { _remoteSettingsResolve = r; }); 2781 let _settingsVersion = 0; // ã¤ã³ã¯ãªã¡ã³ãã§åã¬ã³ãã¼ãããªã¬ã¼ 2782 2783 (async function _loadRemoteSettings() { 2784 try { 2785 const resp = await fetch('/api/settings'); 2786 if (!resp.ok) { _remoteSettingsResolve(); return; } 2787 const data = await resp.json(); 2788 if (!data || Object.keys(data).length === 0) { _remoteSettingsResolve(); return; } 2789 2790 // af_configs ãé©ç¨ 2791 if (data.af_configs) { 2792 const remoteConfigs = typeof data.af_configs === 'string' ? JSON.parse(data.af_configs) : data.af_configs; 2793 Object.keys(remoteConfigs).forEach(key => { 2794 if (ARTICLE_CONFIGS[key]) { 2795 ARTICLE_CONFIGS[key] = { ...ARTICLE_CONFIGS[key], ...remoteConfigs[key] }; 2796 } 2797 }); 2798 } 2799 2800 // af_questions ãé©ç¨ï¼ã°ãã¼ãã«å¤æ°ã䏿¸ãï¼ 2801 if (data.af_questions) { 2802 const rq = typeof data.af_questions === 'string' ? JSON.parse(data.af_questions) : data.af_questions; 2803 if (rq.Q1_FREELANCE) Object.assign(Q1_FREELANCE, rq.Q1_FREELANCE); 2804 if (rq.Q1_TENSHOKU) Object.assign(Q1_TENSHOKU, rq.Q1_TENSHOKU); 2805 if (rq.Q2_FREELANCE) Object.assign(Q2_FREELANCE, rq.Q2_FREELANCE); 2806 if (rq.Q2_TENSHOKU) Object.assign(Q2_TENSHOKU, rq.Q2_TENSHOKU); 2807 if (rq.Q3_FREELANCE) Object.assign(Q3_FREELANCE, rq.Q3_FREELANCE); 2808 if (rq.Q3_TENSHOKU) Object.assign(Q3_TENSHOKU, rq.Q3_TENSHOKU); 2809 if (rq.Q4_TENSHOKU) Object.assign(Q4_TENSHOKU, rq.Q4_TENSHOKU); 2810 // Q3åç鏿è¢: æ¢åãã¼ãå ¨åé¤ãã¦ãã䏿¸ãï¼adminå´ã§åé¤ããããã¼ãæ¶ãï¼ 2811 if (rq._Q3_OPTIONS_BY_Q2) { 2812 Object.keys(Q3_OPTIONS_BY_Q2).forEach(k => delete Q3_OPTIONS_BY_Q2[k]); 2813 Object.assign(Q3_OPTIONS_BY_Q2, rq._Q3_OPTIONS_BY_Q2); 2814 } 2815 if (rq._Q3_AGENT_BOOSTS) { 2816 // æ§å½¢å¼ï¼q3val ã®ã¿ï¼ãæ°å½¢å¼ï¼q2val:q3val è¤åãã¼ï¼ã«ç§»è¡ 2817 const migrated = _migrateQ3Boosts({ 2818 _Q3_OPTIONS_BY_Q2: rq._Q3_OPTIONS_BY_Q2 || Q3_OPTIONS_BY_Q2, 2819 _Q3_AGENT_BOOSTS: rq._Q3_AGENT_BOOSTS, 2820 }); 2821 Object.keys(Q3_AGENT_BOOSTS).forEach(k => delete Q3_AGENT_BOOSTS[k]); 2822 Object.assign(Q3_AGENT_BOOSTS, migrated._Q3_AGENT_BOOSTS); 2823 } 2824 } 2825 2826 // af_master ãé©ç¨ï¼è¡¨ç¤ºç³»ãã£ã¼ã«ãã®ã¿ä¸æ¸ããã¹ã³ã¢ãªã³ã°ç¨ãã£ã¼ã«ãã¯agents-master.jsãå¸¸ã«æ£ï¼ 2827 if (data.af_master) { 2828 const remoteMaster = typeof data.af_master === 'string' ? JSON.parse(data.af_master) : data.af_master; 2829 // ã¹ã³ã¢ãªã³ã°ã«å½±é¿ãããã£ã¼ã«ãã¯ä¸æ¸ãããªãï¼agents-master.jsãæ£ï¼ 2830 const PROTECTED_FIELDS = ['targetGoal', 'targetDomain', 'categories', 'targetExperience', 'targetWorkStyle']; 2831 if (Array.isArray(remoteMaster)) { 2832 remoteMaster.forEach(rm => { 2833 const idx = AGENTS_MASTER.findIndex(a =>
2833 a.id === rm.id); 2834 if (idx >= 0) { 2835 const safeOverride = { ...rm }; 2836 PROTECTED_FIELDS.forEach(f => delete safeOverride[f]); 2837 AGENTS_MASTER[idx] = { ...AGENTS_MASTER[idx], ...safeOverride }; 2838 } 2839 }); 2840 } 2841 } 2842 2843 // af_logic ãé©ç¨ 2844 if (data.af_logic) { 2845 const rl = typeof data.af_logic === 'string' ? JSON.parse(data.af_logic) : data.af_logic; 2846 if (rl.requiredAgents) Object.assign(Q2_REQUIRED_AGENTS, rl.requiredAgents); 2847 if (rl.domainMap) Object.assign(_domainMap, rl.domainMap); 2848 if (rl.goalMap) Object.assign(_goalMap, rl.goalMap); 2849 if (rl.weights) Object.assign(_weights, rl.weights); 2850 } 2851 2852 _settingsVersion++; 2853 console.log('[AF] Remote settings loaded from Supabase API'); 2854 } catch(e) { 2855 console.warn('[AF] Failed to load remote settings:', e.message); 2856 } 2857 _remoteSettingsResolve(); 2858 })(); 2859 2860 // ============================================================ 2861 // RENDER 2862 // ============================================================ 2863 const root = ReactDOM.createRoot(document.getElementById('root')); 2864 root.render(<AgentFinderApp />); 2865 2866 // ãªã¢ã¼ãè¨å®ãå°çãããåã¬ã³ãã¼ï¼è¨ºæéå§åã«é©ç¨ãããããï¼ 2867 _remoteSettingsReady.then(() => { 2868 if (_settingsVersion > 0) { 2869 root.render(<AgentFinderApp key={_settingsVersion} />); 2870 } 2871 }); 2872 </script>
2872 2873</body> 2874</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.