github-project-manager 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +89 -50
  2. package/apps/backend/dist/modules/milestone/milestone.controller.d.ts +12 -0
  3. package/apps/backend/dist/modules/milestone/milestone.controller.js +59 -0
  4. package/apps/backend/dist/modules/milestone/milestone.controller.js.map +1 -0
  5. package/apps/backend/dist/modules/milestone/milestone.module.js +7 -2
  6. package/apps/backend/dist/modules/milestone/milestone.module.js.map +1 -1
  7. package/apps/backend/dist/modules/milestone/milestone.service.d.ts +15 -0
  8. package/apps/backend/dist/modules/milestone/milestone.service.js +75 -0
  9. package/apps/backend/dist/modules/milestone/milestone.service.js.map +1 -0
  10. package/apps/backend/dist/modules/project/project.controller.d.ts +1 -0
  11. package/apps/backend/dist/modules/project/project.controller.js +16 -0
  12. package/apps/backend/dist/modules/project/project.controller.js.map +1 -1
  13. package/apps/backend/dist/modules/project/project.entity.d.ts +2 -0
  14. package/apps/backend/dist/modules/project/project.entity.js +10 -0
  15. package/apps/backend/dist/modules/project/project.entity.js.map +1 -1
  16. package/apps/backend/dist/modules/project/project.service.d.ts +1 -0
  17. package/apps/backend/dist/modules/project/project.service.js +3 -0
  18. package/apps/backend/dist/modules/project/project.service.js.map +1 -1
  19. package/apps/backend/dist/modules/sync/github.service.d.ts +18 -0
  20. package/apps/backend/dist/modules/sync/github.service.js +14 -4
  21. package/apps/backend/dist/modules/sync/github.service.js.map +1 -1
  22. package/apps/backend/dist/modules/sync/sync.controller.d.ts +3 -2
  23. package/apps/backend/dist/modules/sync/sync.controller.js +29 -6
  24. package/apps/backend/dist/modules/sync/sync.controller.js.map +1 -1
  25. package/apps/backend/dist/modules/sync/sync.module.js +2 -1
  26. package/apps/backend/dist/modules/sync/sync.module.js.map +1 -1
  27. package/apps/backend/dist/modules/sync/sync.service.d.ts +5 -1
  28. package/apps/backend/dist/modules/sync/sync.service.js +80 -2
  29. package/apps/backend/dist/modules/sync/sync.service.js.map +1 -1
  30. package/apps/backend/dist/modules/task/task.controller.d.ts +6 -6
  31. package/apps/backend/dist/modules/task/task.controller.js +41 -27
  32. package/apps/backend/dist/modules/task/task.controller.js.map +1 -1
  33. package/apps/backend/dist/modules/task/task.entity.d.ts +10 -1
  34. package/apps/backend/dist/modules/task/task.entity.js +27 -0
  35. package/apps/backend/dist/modules/task/task.entity.js.map +1 -1
  36. package/apps/backend/dist/modules/task/task.service.js +2 -2
  37. package/apps/backend/dist/modules/task/task.service.js.map +1 -1
  38. package/apps/cli/dist/commands/hooks.d.ts +11 -0
  39. package/apps/cli/dist/commands/hooks.js +113 -0
  40. package/apps/cli/dist/commands/hooks.js.map +1 -0
  41. package/apps/cli/dist/commands/init.d.ts +4 -1
  42. package/apps/cli/dist/commands/init.js +16 -1
  43. package/apps/cli/dist/commands/init.js.map +1 -1
  44. package/apps/cli/dist/hooks/hook-registry.d.ts +9 -0
  45. package/apps/cli/dist/hooks/hook-registry.js +29 -0
  46. package/apps/cli/dist/hooks/hook-registry.js.map +1 -0
  47. package/apps/cli/dist/index.js +23 -2
  48. package/apps/cli/dist/index.js.map +1 -1
  49. package/apps/cli/dist/utils/settings.d.ts +10 -0
  50. package/apps/cli/dist/utils/settings.js +93 -0
  51. package/apps/cli/dist/utils/settings.js.map +1 -0
  52. package/apps/cli/dist/utils/template.d.ts +1 -0
  53. package/apps/cli/dist/utils/template.js +20 -0
  54. package/apps/cli/dist/utils/template.js.map +1 -0
  55. package/apps/frontend/dist/assets/index-C-VkfAuU.css +1 -0
  56. package/apps/frontend/dist/assets/index-CJ-smTcW.js +92 -0
  57. package/apps/frontend/dist/index.html +2 -2
  58. package/package.json +2 -2
  59. package/templates/claude-settings-hooks.json +35 -0
  60. package/templates/hooks/gpm-commit-linker.sh +72 -0
  61. package/templates/hooks/gpm-response-suggest.sh +63 -0
  62. package/templates/hooks/gpm-session-briefing.sh +104 -0
  63. package/apps/frontend/dist/assets/index-5kQElBCK.css +0 -1
  64. package/apps/frontend/dist/assets/index-DzMlDUgv.js +0 -60
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>GPM - GitHub Project Manager</title>
7
- <script type="module" crossorigin src="/assets/index-DzMlDUgv.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-5kQElBCK.css">
7
+ <script type="module" crossorigin src="/assets/index-CJ-smTcW.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-C-VkfAuU.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-project-manager",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "GitHub Project V2 manager - CLI + Web UI for task management with Claude Code",
5
5
  "bin": {
6
6
  "gpm": "./apps/cli/bin/gpm.js"
@@ -16,7 +16,7 @@
16
16
  "apps/*"
17
17
  ],
18
18
  "scripts": {
19
- "dev:backend": "yarn workspace @gpm/backend start:dev",
19
+ "dev:backend": "yarn workspace @gpm/backend dev",
20
20
  "dev:frontend": "yarn workspace @gpm/frontend dev",
21
21
  "build:backend": "yarn workspace @gpm/backend build",
22
22
  "build:frontend": "yarn workspace @gpm/frontend build",
@@ -0,0 +1,35 @@
1
+ {
2
+ "hooks": {
3
+ "UserPromptSubmit": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": ".claude/hooks/gpm-session-briefing.sh"
9
+ }
10
+ ]
11
+ }
12
+ ],
13
+ "PostToolUse": [
14
+ {
15
+ "matcher": "Bash",
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": ".claude/hooks/gpm-commit-linker.sh"
20
+ }
21
+ ]
22
+ }
23
+ ],
24
+ "Stop": [
25
+ {
26
+ "hooks": [
27
+ {
28
+ "type": "command",
29
+ "command": ".claude/hooks/gpm-response-suggest.sh"
30
+ }
31
+ ]
32
+ }
33
+ ]
34
+ }
35
+ }
@@ -0,0 +1,72 @@
1
+ #!/bin/bash
2
+ set -u
3
+ # GPM Commit Linker — PostToolUse hook (Bash)
4
+ # 커밋 감지 시 In Progress 태스크와 연결하고 완료 처리를 제안
5
+ # stderr → 사용자 알림으로 표시
6
+ # 필수: jq
7
+
8
+ # jq 없으면 스킵
9
+ command -v jq &>/dev/null || exit 0
10
+
11
+ # gpm CLI 실행 (gpm 직접 사용 → npx fallback)
12
+ gpm_cli() {
13
+ if command -v gpm &>/dev/null; then
14
+ gpm "$@"
15
+ else
16
+ npx github-project-manager "$@"
17
+ fi
18
+ }
19
+
20
+ # stdin에서 hook input JSON 읽기
21
+ INPUT=$(cat)
22
+
23
+ # tool_name, command 추출
24
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
25
+ [ "$TOOL_NAME" != "Bash" ] && exit 0
26
+
27
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
28
+
29
+ # git commit 명령이 아니면 스킵
30
+ echo "$COMMAND" | grep -qE 'git\s+commit' || exit 0
31
+
32
+ # 프로젝트 루트 및 .gpmrc 확인
33
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
34
+ GPMRC="${PROJECT_ROOT:-.}/.gpmrc"
35
+ [ ! -f "$GPMRC" ] && exit 0
36
+
37
+ # 최근 커밋 메시지 가져오기
38
+ COMMIT_MSG=$(cd "$PROJECT_ROOT" && git log -1 --format="%s" 2>/dev/null || echo "")
39
+ [ -z "$COMMIT_MSG" ] && exit 0
40
+
41
+ # In Progress 태스크 조회
42
+ cd "$PROJECT_ROOT" || exit 0
43
+ TASKS=$(gpm_cli task list --json --limit 30 2>/dev/null || echo "")
44
+ [ -z "$TASKS" ] || [ "$TASKS" = "[]" ] && exit 0
45
+
46
+ IN_PROGRESS=$(echo "$TASKS" | jq -r '
47
+ [.[] | select(.status == "In Progress")]
48
+ | if length > 0 then
49
+ map("#\(.number // .id): \(.title)")
50
+ | join(", ")
51
+ else "없음" end
52
+ ' 2>/dev/null || echo "없음")
53
+ IN_PROGRESS_COUNT=$(echo "$TASKS" | jq '[.[] | select(.status == "In Progress")] | length' 2>/dev/null || echo 0)
54
+
55
+ # 커밋 마커 파일 생성 (Stop hook에서 참조)
56
+ PROJ_HASH=$(echo "$PROJECT_ROOT" | md5 -q 2>/dev/null || echo "$PROJECT_ROOT" | md5sum 2>/dev/null | cut -d' ' -f1)
57
+ PROJ_HASH="${PROJ_HASH:0:12}"
58
+ echo "$COMMIT_MSG" > "/tmp/gpm-commit-marker-${PROJ_HASH}"
59
+
60
+ # 브리핑 캐시 무효화 (다음 프롬프트에서 갱신되도록)
61
+ rm -f "/tmp/gpm-briefing-${PROJ_HASH}"
62
+
63
+ # 알림 출력 (stderr → 사용자에게 표시)
64
+ {
65
+ echo "[GPM] 커밋 감지: \"${COMMIT_MSG}\""
66
+ if [ "$IN_PROGRESS_COUNT" != "0" ] && [ "$IN_PROGRESS_COUNT" != "?" ]; then
67
+ echo "In Progress: ${IN_PROGRESS}"
68
+ echo "태스크를 완료했다면 /gpm done 을 실행하세요."
69
+ fi
70
+ } >&2
71
+
72
+ exit 0
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+ set -u
3
+ # GPM Response Suggest — Stop hook
4
+ # 커밋 후 응답 완료 시 Claude가 태스크 완료/다음 작업을 제안하도록 유도
5
+ # stdout JSON {"decision": "block", "reason": "..."} → Claude 추가 응답 트리거
6
+ # 필수: jq
7
+
8
+ # jq 없으면 스킵
9
+ command -v jq &>/dev/null || exit 0
10
+
11
+ # gpm CLI 실행 (gpm 직접 사용 → npx fallback)
12
+ gpm_cli() {
13
+ if command -v gpm &>/dev/null; then
14
+ gpm "$@"
15
+ else
16
+ npx github-project-manager "$@"
17
+ fi
18
+ }
19
+
20
+ INPUT=$(cat)
21
+
22
+ # 무한 루프 방지: stop_hook_active가 true이면 즉시 종료 (Claude 중단 허용)
23
+ ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false' 2>/dev/null)
24
+ [ "$ACTIVE" = "true" ] && exit 0
25
+
26
+ # 프로젝트 확인
27
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
28
+ GPMRC="${PROJECT_ROOT:-.}/.gpmrc"
29
+ [ ! -f "$GPMRC" ] && exit 0
30
+
31
+ # 커밋 마커 확인 (gpm-commit-linker.sh에서 생성)
32
+ PROJ_HASH=$(echo "$PROJECT_ROOT" | md5 -q 2>/dev/null || echo "$PROJECT_ROOT" | md5sum 2>/dev/null | cut -d' ' -f1)
33
+ PROJ_HASH="${PROJ_HASH:0:12}"
34
+ MARKER="/tmp/gpm-commit-marker-${PROJ_HASH}"
35
+
36
+ # 마커 없으면 조용히 종료 → Claude 정상 중단
37
+ [ ! -f "$MARKER" ] && exit 0
38
+
39
+ # 마커 읽고 삭제
40
+ COMMIT_MSG=$(cat "$MARKER")
41
+ rm -f "$MARKER"
42
+
43
+ # In Progress 태스크 조회
44
+ cd "$PROJECT_ROOT" || exit 0
45
+ TASKS=$(gpm_cli task list --json --limit 20 2>/dev/null || echo "")
46
+
47
+ IN_PROGRESS="확인 필요"
48
+ if [ -n "$TASKS" ] && [ "$TASKS" != "[]" ]; then
49
+ IN_PROGRESS=$(echo "$TASKS" | jq -r '
50
+ [.[] | select(.status == "In Progress")]
51
+ | if length > 0 then
52
+ map("#\(.number // .id) \(.title)")
53
+ | join(", ")
54
+ else "없음" end
55
+ ' 2>/dev/null || echo "확인 필요")
56
+ fi
57
+
58
+ # Claude에게 전달할 메시지 구성 + JSON 안전 출력
59
+ REASON="[GPM Stop Hook] 커밋이 감지되었습니다. In Progress 태스크: ${IN_PROGRESS}. 이 커밋으로 완료된 태스크가 있는지 사용자에게 간결하게(1-2줄) 물어보고, 완료했다면 /gpm done을 제안하세요. 다음 작업이 필요하면 /gpm next를 안내하세요."
60
+
61
+ jq -n --arg reason "$REASON" '{"decision": "block", "reason": $reason}'
62
+
63
+ exit 0
@@ -0,0 +1,104 @@
1
+ #!/bin/bash
2
+ set -u
3
+ # GPM Session Briefing — UserPromptSubmit hook
4
+ # 세션 시작 시 In Progress 태스크와 마일스톤별 다음 추천 태스크를 표시
5
+ # stdout → Claude context에 주입
6
+
7
+ # gpm CLI 실행 (gpm 직접 사용 → npx fallback)
8
+ gpm_cli() {
9
+ if command -v gpm &>/dev/null; then
10
+ gpm "$@"
11
+ else
12
+ npx github-project-manager "$@"
13
+ fi
14
+ }
15
+
16
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
17
+ GPMRC="${PROJECT_ROOT:-.}/.gpmrc"
18
+
19
+ # .gpmrc 없으면 스킵
20
+ [ ! -f "$GPMRC" ] && exit 0
21
+
22
+ # 프로젝트 해시 (캐시 키)
23
+ PROJ_HASH=$(echo "$PROJECT_ROOT" | md5 -q 2>/dev/null || echo "$PROJECT_ROOT" | md5sum 2>/dev/null | cut -d' ' -f1)
24
+ PROJ_HASH="${PROJ_HASH:0:12}"
25
+ CACHE_FILE="/tmp/gpm-briefing-${PROJ_HASH}"
26
+
27
+ # 30분 캐시 (1800초) — 빈번한 CLI 호출 방지
28
+ if [ -f "$CACHE_FILE" ]; then
29
+ CACHE_MOD=$(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0)
30
+ CACHE_AGE=$(( $(date +%s) - CACHE_MOD ))
31
+ if [ "$CACHE_AGE" -lt 1800 ]; then
32
+ cat "$CACHE_FILE"
33
+ exit 0
34
+ fi
35
+ fi
36
+
37
+ # 태스크 목록 조회 (로컬 SQLite 캐시, GitHub API 호출 없음)
38
+ cd "$PROJECT_ROOT" || exit 0
39
+ TASKS=$(gpm_cli task list --json --limit 50 2>/dev/null || echo "")
40
+
41
+ # 빈 결과 또는 에러 시 스킵
42
+ if [ -z "$TASKS" ] || [ "$TASKS" = "[]" ]; then
43
+ exit 0
44
+ fi
45
+
46
+ # jq 사용 가능 여부 확인
47
+ if ! command -v jq &>/dev/null; then
48
+ cat > "$CACHE_FILE" << 'NOJQ'
49
+ ### GPM 브리핑
50
+ 태스크 현황을 확인하려면 `/gpm status`를 실행하세요.
51
+ NOJQ
52
+ cat "$CACHE_FILE"
53
+ exit 0
54
+ fi
55
+
56
+ # 태스크 파싱
57
+ IN_PROGRESS=$(echo "$TASKS" | jq -r '
58
+ [.[] | select(.status == "In Progress")]
59
+ | if length > 0 then
60
+ map("- #\(.number // .id): \(.title)\(if .milestone then " [\(.milestone)]" else "" end)")
61
+ | join("\n")
62
+ else empty end
63
+ ' 2>/dev/null || echo "")
64
+
65
+ IN_PROGRESS_COUNT=$(echo "$TASKS" | jq '[.[] | select(.status == "In Progress")] | length' 2>/dev/null || echo 0)
66
+
67
+ TODO_LIST=$(echo "$TASKS" | jq -r '
68
+ [.[] | select(.status == "Todo" or .status == "TODO" or .status == "To Do")]
69
+ | sort_by(.milestone // "zzz")
70
+ | .[0:5]
71
+ | if length > 0 then
72
+ map("- #\(.number // .id): \(.title)\(if .milestone then " [\(.milestone)]" else "" end)")
73
+ | join("\n")
74
+ else empty end
75
+ ' 2>/dev/null || echo "")
76
+
77
+ TODO_COUNT=$(echo "$TASKS" | jq '[.[] | select(.status == "Todo" or .status == "TODO" or .status == "To Do")] | length' 2>/dev/null || echo 0)
78
+
79
+ # 브리핑 생성
80
+ {
81
+ echo "### GPM 태스크 브리핑"
82
+ echo ""
83
+
84
+ if [ -n "$IN_PROGRESS" ] && [ "$IN_PROGRESS_COUNT" -gt 0 ] 2>/dev/null; then
85
+ echo "**In Progress (${IN_PROGRESS_COUNT})**:"
86
+ echo "$IN_PROGRESS"
87
+ fi
88
+
89
+ if [ -n "$TODO_LIST" ] && [ "$TODO_COUNT" -gt 0 ] 2>/dev/null; then
90
+ echo ""
91
+ echo "**Todo 추천 (${TODO_COUNT}개 중 상위 5개, 마일스톤 순)**:"
92
+ echo "$TODO_LIST"
93
+ fi
94
+
95
+ if [ -z "$IN_PROGRESS" ] && [ -z "$TODO_LIST" ]; then
96
+ echo "등록된 태스크가 없습니다. \`/gpm sync\`로 GitHub과 동기화하세요."
97
+ fi
98
+
99
+ echo ""
100
+ echo "> /gpm status | /gpm next | /gpm done"
101
+ } > "$CACHE_FILE"
102
+
103
+ cat "$CACHE_FILE"
104
+ exit 0
@@ -1 +0,0 @@
1
- /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-500:oklch(63.7% .237 25.331);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-3{margin-top:calc(var(--spacing) * 3)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.block{display:block}.flex{display:flex}.grid{display:grid}.min-h-\[50vh\]{min-height:50vh}.min-h-screen{min-height:100vh}.max-w-7xl{max-width:var(--container-7xl)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-white{background-color:var(--color-white)}.p-5{padding:calc(var(--spacing) * 5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-8{padding-block:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-blue-500{color:var(--color-blue-500)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-red-500{color:var(--color-red-500)}.text-white{color:var(--color-white)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.hover\:text-blue-600:hover{color:var(--color-blue-600)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media(min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}