popilot 0.6.0 → 0.8.0

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 (165) hide show
  1. package/bin/cli.mjs +204 -2
  2. package/lib/doctor.mjs +38 -1
  3. package/lib/hydrate.mjs +15 -0
  4. package/lib/scaffold.mjs +5 -0
  5. package/lib/setup-wizard.mjs +35 -2
  6. package/package.json +1 -1
  7. package/scaffold/.context/project.yaml.example +19 -0
  8. package/scaffold/mcp-notification-server/package.json +18 -0
  9. package/scaffold/mcp-notification-server/src/index.ts +275 -0
  10. package/scaffold/mcp-notification-server/src/turso-client.ts +142 -0
  11. package/scaffold/mcp-notification-server/tsconfig.json +14 -0
  12. package/scaffold/mcp-pm/package.json +19 -0
  13. package/scaffold/mcp-pm/src/api-client.ts +69 -0
  14. package/scaffold/mcp-pm/src/index.ts +660 -0
  15. package/scaffold/mcp-pm/tsconfig.json +14 -0
  16. package/scaffold/pm-api/package.json +21 -0
  17. package/scaffold/pm-api/sql/001-memo-v2.sql +49 -0
  18. package/scaffold/pm-api/sql/002-notifications.sql +18 -0
  19. package/scaffold/pm-api/sql/003-content.sql +66 -0
  20. package/scaffold/pm-api/sql/004-agent-events.sql +21 -0
  21. package/scaffold/pm-api/sql/005-epic-sprint-decoupling.sql +6 -0
  22. package/scaffold/pm-api/sql/schema-core.sql +331 -0
  23. package/scaffold/pm-api/sql/schema-docs.sql +25 -0
  24. package/scaffold/pm-api/sql/schema-meetings.sql +17 -0
  25. package/scaffold/pm-api/sql/schema-rewards.sql +16 -0
  26. package/scaffold/pm-api/src/auth.ts +28 -0
  27. package/scaffold/pm-api/src/blockchain/adapter.ts +20 -0
  28. package/scaffold/pm-api/src/blockchain/tron.ts +62 -0
  29. package/scaffold/pm-api/src/db/adapter.ts +36 -0
  30. package/scaffold/pm-api/src/db/turso.ts +147 -0
  31. package/scaffold/pm-api/src/index.ts +114 -0
  32. package/scaffold/pm-api/src/mcp-tools/dashboard.ts +40 -0
  33. package/scaffold/pm-api/src/mcp-tools/epic.ts +67 -0
  34. package/scaffold/pm-api/src/mcp-tools/event.ts +89 -0
  35. package/scaffold/pm-api/src/mcp-tools/index.ts +11 -0
  36. package/scaffold/pm-api/src/mcp-tools/initiative.ts +51 -0
  37. package/scaffold/pm-api/src/mcp-tools/memo.ts +164 -0
  38. package/scaffold/pm-api/src/mcp-tools/notification.ts +37 -0
  39. package/scaffold/pm-api/src/mcp-tools/retro.ts +183 -0
  40. package/scaffold/pm-api/src/mcp-tools/sprint.ts +204 -0
  41. package/scaffold/pm-api/src/mcp-tools/standup.ts +136 -0
  42. package/scaffold/pm-api/src/mcp-tools/story.ts +230 -0
  43. package/scaffold/pm-api/src/mcp-tools/task.ts +187 -0
  44. package/scaffold/pm-api/src/mcp-tools/utils.ts +83 -0
  45. package/scaffold/pm-api/src/mcp.ts +871 -0
  46. package/scaffold/pm-api/src/nudge.ts +283 -0
  47. package/scaffold/pm-api/src/routes/auth.ts +32 -0
  48. package/scaffold/pm-api/src/routes/v2-activity.ts +27 -0
  49. package/scaffold/pm-api/src/routes/v2-admin.ts +165 -0
  50. package/scaffold/pm-api/src/routes/v2-dashboard.ts +189 -0
  51. package/scaffold/pm-api/src/routes/v2-docs.ts +34 -0
  52. package/scaffold/pm-api/src/routes/v2-initiatives.ts +118 -0
  53. package/scaffold/pm-api/src/routes/v2-kickoff.ts +265 -0
  54. package/scaffold/pm-api/src/routes/v2-meetings.ts +324 -0
  55. package/scaffold/pm-api/src/routes/v2-memos.ts +257 -0
  56. package/scaffold/pm-api/src/routes/v2-nav.ts +260 -0
  57. package/scaffold/pm-api/src/routes/v2-notifications.ts +79 -0
  58. package/scaffold/pm-api/src/routes/v2-page-content.ts +35 -0
  59. package/scaffold/pm-api/src/routes/v2-pm.ts +380 -0
  60. package/scaffold/pm-api/src/routes/v2-policy.ts +58 -0
  61. package/scaffold/pm-api/src/routes/v2-retro.ts +221 -0
  62. package/scaffold/pm-api/src/routes/v2-rewards.ts +132 -0
  63. package/scaffold/pm-api/src/routes/v2-scenarios.ts +48 -0
  64. package/scaffold/pm-api/src/routes/v2-search.ts +32 -0
  65. package/scaffold/pm-api/src/routes/v2-standup.ts +127 -0
  66. package/scaffold/pm-api/src/routes/v2-user.ts +38 -0
  67. package/scaffold/pm-api/src/types.ts +11 -0
  68. package/scaffold/pm-api/src/utils/activity.ts +22 -0
  69. package/scaffold/pm-api/src/utils/admin.ts +9 -0
  70. package/scaffold/pm-api/src/utils/agent-notify.ts +62 -0
  71. package/scaffold/pm-api/src/utils/assignee.ts +69 -0
  72. package/scaffold/pm-api/src/utils/db.ts +45 -0
  73. package/scaffold/pm-api/src/utils/initiative.ts +23 -0
  74. package/scaffold/pm-api/src/utils/retro-link.ts +32 -0
  75. package/scaffold/pm-api/src/utils/sprint-lifecycle.ts +96 -0
  76. package/scaffold/pm-api/tsconfig.json +15 -0
  77. package/scaffold/pm-api/wrangler.toml.hbs +11 -0
  78. package/scaffold/spec-site/package-lock.json +892 -0
  79. package/scaffold/spec-site/package.json +15 -1
  80. package/scaffold/spec-site/src/api/types.ts +6 -0
  81. package/scaffold/spec-site/src/components/AppHeader.vue +429 -55
  82. package/scaffold/spec-site/src/components/AuthGate.vue +117 -0
  83. package/scaffold/spec-site/src/components/BurndownChart.vue +78 -0
  84. package/scaffold/spec-site/src/components/DocComments.vue +137 -0
  85. package/scaffold/spec-site/src/components/DocEditor.vue +118 -0
  86. package/scaffold/spec-site/src/components/DocExportBar.vue +110 -0
  87. package/scaffold/spec-site/src/components/DocsSidebar.vue +309 -0
  88. package/scaffold/spec-site/src/components/EmptyState.vue +30 -0
  89. package/scaffold/spec-site/src/components/ErrorBanner.vue +38 -0
  90. package/scaffold/spec-site/src/components/Icon.vue +58 -0
  91. package/scaffold/spec-site/src/components/MemberSelect.vue +48 -0
  92. package/scaffold/spec-site/src/components/MemoChecklist.vue +88 -0
  93. package/scaffold/spec-site/src/components/MemoGraph.vue +75 -0
  94. package/scaffold/spec-site/src/components/MemoItem.vue +353 -0
  95. package/scaffold/spec-site/src/components/MemoRelations.vue +101 -0
  96. package/scaffold/spec-site/src/components/MemoTimeline.vue +53 -0
  97. package/scaffold/spec-site/src/components/MentionInput.vue +174 -0
  98. package/scaffold/spec-site/src/components/NotificationDropdown.vue +116 -0
  99. package/scaffold/spec-site/src/components/PriorityBadge.vue +23 -0
  100. package/scaffold/spec-site/src/components/SearchModal.vue +102 -0
  101. package/scaffold/spec-site/src/components/SlashCommand.ts +123 -0
  102. package/scaffold/spec-site/src/components/StateDisplay.vue +54 -0
  103. package/scaffold/spec-site/src/components/TreeNode.vue +82 -0
  104. package/scaffold/spec-site/src/components/UserAvatar.vue +24 -0
  105. package/scaffold/spec-site/src/components/VelocityChart.vue +77 -0
  106. package/scaffold/spec-site/src/composables/navTypes.ts +3 -0
  107. package/scaffold/spec-site/src/composables/pmTypes.ts +15 -2
  108. package/scaffold/spec-site/src/composables/useBottomSheet.ts +103 -0
  109. package/scaffold/spec-site/src/composables/useDashboard.ts +221 -0
  110. package/scaffold/spec-site/src/composables/useMediaQuery.ts +28 -0
  111. package/scaffold/spec-site/src/composables/useMemo.ts +39 -0
  112. package/scaffold/spec-site/src/composables/useNotification.ts +200 -0
  113. package/scaffold/spec-site/src/composables/usePmStore.ts +48 -1
  114. package/scaffold/spec-site/src/composables/useRetro.ts +6 -0
  115. package/scaffold/spec-site/src/composables/useStandup.ts +201 -0
  116. package/scaffold/spec-site/src/composables/useTheme.ts +37 -0
  117. package/scaffold/spec-site/src/composables/useTurso.ts +17 -0
  118. package/scaffold/spec-site/src/composables/useUser.ts +19 -1
  119. package/scaffold/spec-site/src/composables/useViewport.ts +26 -0
  120. package/scaffold/spec-site/src/features.ts +108 -0
  121. package/scaffold/spec-site/src/mockup/ComponentPalette.vue +61 -0
  122. package/scaffold/spec-site/src/mockup/MockupCanvas.vue +459 -0
  123. package/scaffold/spec-site/src/mockup/PropertyPanel.vue +217 -0
  124. package/scaffold/spec-site/src/mockup/componentCatalog.ts +68 -0
  125. package/scaffold/spec-site/src/mockup/useScenarios.ts +67 -0
  126. package/scaffold/spec-site/src/pages/AdminPage.vue +299 -0
  127. package/scaffold/spec-site/src/pages/DashboardPage.vue +650 -0
  128. package/scaffold/spec-site/src/pages/DocsEditor.vue +119 -0
  129. package/scaffold/spec-site/src/pages/DocsHub.vue +157 -0
  130. package/scaffold/spec-site/src/pages/DocsPage.vue +444 -0
  131. package/scaffold/spec-site/src/pages/InboxPage.vue +156 -0
  132. package/scaffold/spec-site/src/pages/MeetingsPage.vue +294 -0
  133. package/scaffold/spec-site/src/pages/MemosPage.vue +857 -0
  134. package/scaffold/spec-site/src/pages/MockupEditorPage.vue +611 -0
  135. package/scaffold/spec-site/src/pages/MockupListPage.vue +121 -0
  136. package/scaffold/spec-site/src/pages/MockupViewerPage.vue +199 -0
  137. package/scaffold/spec-site/src/pages/MyPage.vue +343 -0
  138. package/scaffold/spec-site/src/pages/NotificationSettingsPage.vue +59 -0
  139. package/scaffold/spec-site/src/pages/RewardsPage.vue +266 -0
  140. package/scaffold/spec-site/src/pages/SprintAdmin.vue +521 -0
  141. package/scaffold/spec-site/src/pages/SprintTimeline.vue +159 -0
  142. package/scaffold/spec-site/src/pages/board/BoardAdmin.vue +422 -0
  143. package/scaffold/spec-site/src/pages/board/BoardEpicSection.vue +54 -0
  144. package/scaffold/spec-site/src/pages/board/BoardPage.vue +884 -0
  145. package/scaffold/spec-site/src/pages/board/BoardStoryCard.vue +67 -0
  146. package/scaffold/spec-site/src/pages/board/BoardTaskItem.vue +52 -0
  147. package/scaffold/spec-site/src/pages/board/KanbanBoard.vue +93 -0
  148. package/scaffold/spec-site/src/pages/board/MyTasksPage.vue +202 -0
  149. package/scaffold/spec-site/src/pages/board/SprintClose.vue +167 -0
  150. package/scaffold/spec-site/src/pages/board/SprintColumn.vue +49 -0
  151. package/scaffold/spec-site/src/pages/board/SprintKickoff.vue +389 -0
  152. package/scaffold/spec-site/src/pages/board/StatusBadge.vue +52 -0
  153. package/scaffold/spec-site/src/pages/board/StoryDetailPanel.vue +495 -0
  154. package/scaffold/spec-site/src/pages/board/TaskCard.vue +42 -0
  155. package/scaffold/spec-site/src/pages/retro/RetroCard.vue +36 -2
  156. package/scaffold/spec-site/src/pages/retro/RetroHeader.vue +82 -66
  157. package/scaffold/spec-site/src/pages/retro/RetroPage.vue +47 -18
  158. package/scaffold/spec-site/src/pages/standup/StandupEntryCard.vue +551 -0
  159. package/scaffold/spec-site/src/pages/standup/StandupForm.vue +68 -0
  160. package/scaffold/spec-site/src/pages/standup/StandupList.vue +71 -0
  161. package/scaffold/spec-site/src/pages/standup/StandupPage.vue +225 -0
  162. package/scaffold/spec-site/src/router.ts +141 -0
  163. package/scaffold/spec-site/src/styles/buttons.css +124 -0
  164. package/scaffold/spec-site/src/utils/parseMentions.ts +56 -0
  165. package/scaffold/spec-site/src/utils/timezone.ts +18 -0
@@ -0,0 +1,266 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, onMounted } from 'vue'
3
+ import { apiGet, apiPost, apiPatch, isStaticMode } from '@/api/client'
4
+
5
+ interface Reward {
6
+ id: number; member_name: string; type: 'reward' | 'penalty'
7
+ amount: number; reason: string; status: string; issued_by: string
8
+ tx_hash: string | null; paid_at: string | null; created_at: string
9
+ }
10
+ interface Summary {
11
+ member_name: string; total_rewards: number; total_penalties: number
12
+ balance: number; pending_balance: number
13
+ }
14
+ interface Member {
15
+ id: number; display_name: string; wallet_address: string | null
16
+ }
17
+
18
+ const rewards = ref<Reward[]>([])
19
+ const summary = ref<Summary[]>([])
20
+ const members = ref<Member[]>([])
21
+ const loading = ref(true)
22
+ const filterMember = ref('')
23
+ const activeTab = ref<'pending' | 'all'>('pending')
24
+
25
+ // Create form
26
+ const showForm = ref(false)
27
+ const formMember = ref('')
28
+ const formType = ref<'reward' | 'penalty'>('reward')
29
+ const formAmount = ref(0)
30
+ const formReason = ref('')
31
+
32
+ // Wallet editing
33
+ const editingWallet = ref<number | null>(null)
34
+ const walletInput = ref('')
35
+
36
+ // Payment
37
+ const selectedIds = ref<Set<number>>(new Set())
38
+ const txHashInput = ref('')
39
+
40
+ const pendingRewards = computed(() => rewards.value.filter(r => r.status === 'pending'))
41
+
42
+ const filteredRewards = computed(() => {
43
+ let list = activeTab.value === 'pending' ? pendingRewards.value : rewards.value
44
+ if (filterMember.value) list = list.filter(r => r.member_name === filterMember.value)
45
+ return list
46
+ })
47
+
48
+ async function loadData() {
49
+ if (isStaticMode()) { loading.value = false; return }
50
+ loading.value = true
51
+ const [rewardsRes, summaryRes, membersRes] = await Promise.all([
52
+ apiGet('/api/v2/rewards'),
53
+ apiGet('/api/v2/rewards/summary'),
54
+ apiGet('/api/v2/admin/members'),
55
+ ])
56
+ if (rewardsRes.data?.rewards) rewards.value = rewardsRes.data.rewards as Reward[]
57
+ if (summaryRes.data?.summary) summary.value = summaryRes.data.summary as Summary[]
58
+ if (membersRes.data?.members) members.value = (membersRes.data.members as Member[]).filter((m: any) => m.is_active)
59
+ loading.value = false
60
+ }
61
+
62
+ async function addReward() {
63
+ if (!formMember.value || !formAmount.value || !formReason.value) return
64
+ await apiPost('/api/v2/rewards', { memberName: formMember.value, type: formType.value, amount: formAmount.value, reason: formReason.value })
65
+ showForm.value = false; formMember.value = ''; formAmount.value = 0; formReason.value = ''
66
+ await loadData()
67
+ }
68
+
69
+ async function paySelected() {
70
+ for (const id of selectedIds.value) {
71
+ await apiPatch(`/api/v2/rewards/${id}/pay`, { txHash: txHashInput.value || null })
72
+ }
73
+ selectedIds.value.clear(); txHashInput.value = ''
74
+ await loadData()
75
+ }
76
+
77
+ async function batchPay(memberName: string) {
78
+ await apiPatch('/api/v2/rewards/batch-pay', { memberName, txHash: txHashInput.value || null })
79
+ txHashInput.value = ''
80
+ await loadData()
81
+ }
82
+
83
+ function startEditWallet(m: Member) {
84
+ editingWallet.value = m.id; walletInput.value = m.wallet_address || ''
85
+ }
86
+
87
+ async function saveWallet(memberId: number) {
88
+ await apiPatch(`/api/v2/admin/members/${memberId}`, { wallet_address: walletInput.value })
89
+ editingWallet.value = null
90
+ await loadData()
91
+ }
92
+
93
+ function toggleSelect(id: number) {
94
+ if (selectedIds.value.has(id)) selectedIds.value.delete(id)
95
+ else selectedIds.value.add(id)
96
+ }
97
+
98
+ function formatDate(d: string) {
99
+ if (!d) return ''
100
+ const date = new Date(d.endsWith('Z') ? d : d + 'Z')
101
+ return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
102
+ }
103
+
104
+ function getMemberWallet(name: string) {
105
+ return members.value.find(m => m.display_name === name)?.wallet_address || ''
106
+ }
107
+
108
+ function copyAddr(addr: string | null) {
109
+ if (!addr) return
110
+ navigator.clipboard.writeText(addr)
111
+ }
112
+
113
+ onMounted(loadData)
114
+ </script>
115
+
116
+ <template>
117
+ <div class="rewards-page">
118
+ <div class="rewards-header">
119
+ <h1>Rewards &amp; Penalties</h1>
120
+ <button class="btn-add" @click="showForm = !showForm">{{ showForm ? 'Cancel' : '+ Add Entry' }}</button>
121
+ </div>
122
+
123
+ <!-- Create form -->
124
+ <div v-if="showForm" class="form-card">
125
+ <div class="form-row">
126
+ <select v-model="formMember" class="input">
127
+ <option value="">Select Member</option>
128
+ <option v-for="m in members" :key="m.id" :value="m.display_name">{{ m.display_name }}</option>
129
+ </select>
130
+ <select v-model="formType" class="input">
131
+ <option value="reward">Reward (+)</option>
132
+ <option value="penalty">Penalty (-)</option>
133
+ </select>
134
+ <input v-model.number="formAmount" class="input" type="number" placeholder="Amount" />
135
+ </div>
136
+ <input v-model="formReason" class="input full" placeholder="Reason" @keydown.enter="addReward" />
137
+ <button class="btn-submit" @click="addReward" :disabled="!formMember || !formAmount || !formReason">Submit</button>
138
+ </div>
139
+
140
+ <!-- Member balance cards -->
141
+ <section class="summary-section">
142
+ <h2>Balance by Member</h2>
143
+ <div class="summary-grid">
144
+ <div v-for="s in summary" :key="s.member_name" class="summary-card">
145
+ <div class="summary-top">
146
+ <div class="summary-name">{{ s.member_name }}</div>
147
+ <div class="summary-balance" :class="{ negative: s.balance < 0 }">{{ s.balance.toLocaleString() }}</div>
148
+ </div>
149
+ <div class="summary-wallet">
150
+ <template v-if="editingWallet === members.find(m => m.display_name === s.member_name)?.id">
151
+ <input v-model="walletInput" class="wallet-input" placeholder="Wallet address" @keydown.enter="saveWallet(members.find(m => m.display_name === s.member_name)!.id)" />
152
+ <button class="btn-sm" @click="saveWallet(members.find(m => m.display_name === s.member_name)!.id)">Save</button>
153
+ </template>
154
+ <template v-else>
155
+ <span class="wallet-addr" @click="copyAddr(getMemberWallet(s.member_name))" :title="getMemberWallet(s.member_name)">{{ getMemberWallet(s.member_name)?.slice(0,12) || 'Not set' }}...</span>
156
+ <button class="btn-edit" @click="startEditWallet(members.find(m => m.display_name === s.member_name)!)">Edit</button>
157
+ </template>
158
+ </div>
159
+ <div class="summary-detail">
160
+ <span class="reward-text">+{{ s.total_rewards.toLocaleString() }}</span>
161
+ <span class="penalty-text">-{{ s.total_penalties.toLocaleString() }}</span>
162
+ <span v-if="s.pending_balance" class="pending-text">Pending: {{ s.pending_balance.toLocaleString() }}</span>
163
+ </div>
164
+ <button v-if="s.pending_balance > 0" class="btn-batch-pay" @click="batchPay(s.member_name)">Pay All</button>
165
+ </div>
166
+ </div>
167
+ </section>
168
+
169
+ <!-- Tabs -->
170
+ <div class="tab-row">
171
+ <button :class="['tab-btn', { active: activeTab === 'pending' }]" @click="activeTab = 'pending'">Pending</button>
172
+ <button :class="['tab-btn', { active: activeTab === 'all' }]" @click="activeTab = 'all'">All</button>
173
+ <select v-model="filterMember" class="input filter-select">
174
+ <option value="">All Members</option>
175
+ <option v-for="s in summary" :key="s.member_name" :value="s.member_name">{{ s.member_name }}</option>
176
+ </select>
177
+ </div>
178
+
179
+ <!-- Batch pay bar -->
180
+ <div v-if="selectedIds.size > 0 && activeTab === 'pending'" class="pay-bar">
181
+ <span>{{ selectedIds.size }} selected</span>
182
+ <input v-model="txHashInput" class="input" placeholder="TX hash (optional)" />
183
+ <button class="btn-pay" @click="paySelected">Pay Selected</button>
184
+ </div>
185
+
186
+ <!-- List -->
187
+ <div v-if="loading" class="loading">Loading...</div>
188
+ <div v-else-if="!filteredRewards.length" class="empty">No entries found.</div>
189
+ <div v-else class="rewards-list">
190
+ <div v-for="r in filteredRewards" :key="r.id" class="reward-item" :class="'type--' + r.type">
191
+ <input v-if="activeTab === 'pending'" type="checkbox" :checked="selectedIds.has(r.id)" @change="toggleSelect(r.id)" class="reward-check" />
192
+ <div class="reward-main">
193
+ <span class="reward-type-badge">{{ r.type === 'reward' ? 'Reward' : 'Penalty' }}</span>
194
+ <span class="reward-member">{{ r.member_name }}</span>
195
+ <span class="reward-amount" :class="r.type">{{ r.type === 'reward' ? '+' : '-' }}{{ r.amount.toLocaleString() }}</span>
196
+ <span class="reward-status" :class="'st--' + r.status">{{ r.status === 'paid' ? 'Paid' : 'Pending' }}</span>
197
+ </div>
198
+ <div class="reward-reason">{{ r.reason }}</div>
199
+ <div class="reward-meta">
200
+ <span>{{ r.issued_by }}</span>
201
+ <span>{{ formatDate(r.created_at) }}</span>
202
+ <a v-if="r.tx_hash" class="tx-hash" :href="`https://tronscan.org/#/transaction/${r.tx_hash}`" target="_blank">TX: {{ r.tx_hash.slice(0, 12) }}...</a>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </template>
208
+
209
+ <style scoped>
210
+ .rewards-page { max-width: 800px; margin: 0 auto; padding: 24px 16px; }
211
+ .rewards-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
212
+ .rewards-header h1 { font-size: 20px; font-weight: 700; }
213
+ .btn-add { background: #3b82f6; color: #fff; border: none; border-radius: 10px; padding: 8px 16px; font-size: 13px; font-weight: 600; cursor: pointer; }
214
+
215
+ .form-card { background: var(--card-bg, #fff); border-radius: 16px; padding: 16px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
216
+ .form-row { display: flex; gap: 8px; margin-bottom: 8px; }
217
+ .input { border: 1px solid rgba(0,0,0,0.15); border-radius: 8px; padding: 8px 12px; font-size: 13px; }
218
+ .input.full { width: 100%; margin-bottom: 8px; box-sizing: border-box; }
219
+ .btn-submit { background: #22c55e; color: #fff; border: none; border-radius: 8px; padding: 8px 16px; font-size: 13px; cursor: pointer; }
220
+
221
+ .summary-section { margin-bottom: 24px; }
222
+ .summary-section h2 { font-size: 16px; font-weight: 600; margin-bottom: 12px; }
223
+ .summary-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
224
+ .summary-card { background: var(--card-bg, #fff); border-radius: 12px; padding: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
225
+ .summary-top { display: flex; justify-content: space-between; align-items: center; }
226
+ .summary-name { font-size: 14px; font-weight: 600; }
227
+ .summary-balance { font-size: 18px; font-weight: 700; color: #22c55e; }
228
+ .summary-balance.negative { color: #ef4444; }
229
+ .summary-wallet { display: flex; align-items: center; gap: 4px; margin-top: 8px; font-size: 11px; color: var(--text-muted, #888); }
230
+ .wallet-input { flex: 1; font-size: 11px; padding: 4px 6px; border: 1px solid #ddd; border-radius: 4px; }
231
+ .wallet-addr { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px; cursor: pointer; }
232
+ .wallet-addr:hover { color: #3b82f6; }
233
+ .btn-edit { background: none; border: none; color: #3b82f6; font-size: 11px; cursor: pointer; }
234
+ .btn-sm { background: #3b82f6; color: #fff; border: none; border-radius: 4px; padding: 2px 6px; font-size: 10px; cursor: pointer; }
235
+ .summary-detail { font-size: 11px; color: var(--text-muted, #888); margin-top: 4px; display: flex; gap: 8px; }
236
+ .reward-text { color: #22c55e; } .penalty-text { color: #ef4444; } .pending-text { color: #f59e0b; }
237
+ .btn-batch-pay { width: 100%; margin-top: 8px; background: #3b82f6; color: #fff; border: none; border-radius: 8px; padding: 6px; font-size: 12px; cursor: pointer; }
238
+
239
+ .tab-row { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
240
+ .tab-btn { background: #f3f4f6; border: none; border-radius: 8px; padding: 6px 14px; font-size: 13px; cursor: pointer; }
241
+ .tab-btn.active { background: #3b82f6; color: #fff; }
242
+ .filter-select { margin-left: auto; }
243
+
244
+ .pay-bar { display: flex; gap: 8px; align-items: center; background: #eff6ff; padding: 8px 12px; border-radius: 8px; margin-bottom: 12px; font-size: 13px; }
245
+ .btn-pay { background: #22c55e; color: #fff; border: none; border-radius: 6px; padding: 6px 12px; font-size: 12px; cursor: pointer; }
246
+
247
+ .rewards-list { display: flex; flex-direction: column; gap: 6px; }
248
+ .reward-item { display: flex; align-items: flex-start; gap: 8px; background: var(--card-bg, #fff); border-radius: 10px; padding: 10px 14px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); flex-wrap: wrap; }
249
+ .type--reward { border-left: 3px solid #22c55e; } .type--penalty { border-left: 3px solid #ef4444; }
250
+ .reward-check { margin-top: 4px; }
251
+ .reward-main { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; }
252
+ .reward-type-badge { font-size: 10px; font-weight: 600; padding: 1px 5px; border-radius: 4px; }
253
+ .type--reward .reward-type-badge { background: #dcfce7; color: #16a34a; }
254
+ .type--penalty .reward-type-badge { background: #fef2f2; color: #dc2626; }
255
+ .reward-member { font-weight: 600; font-size: 13px; }
256
+ .reward-amount { font-weight: 700; font-size: 14px; margin-left: auto; }
257
+ .reward-amount.reward { color: #22c55e; } .reward-amount.penalty { color: #ef4444; }
258
+ .reward-status { font-size: 10px; padding: 1px 5px; border-radius: 4px; }
259
+ .st--pending { background: #fef3c7; color: #d97706; } .st--paid { background: #dcfce7; color: #16a34a; }
260
+ .reward-reason { font-size: 12px; color: var(--text-secondary, #666); width: 100%; }
261
+ .reward-meta { display: flex; gap: 8px; font-size: 11px; color: var(--text-muted, #888); width: 100%; }
262
+ .tx-hash { font-family: monospace; font-size: 10px; color: #3b82f6; text-decoration: none; }
263
+ .tx-hash:hover { text-decoration: underline; }
264
+ .loading, .empty { text-align: center; color: var(--text-muted, #888); padding: 40px; }
265
+ @media (max-width: 640px) { .form-row { flex-direction: column; } .summary-grid { grid-template-columns: 1fr; } .tab-row { flex-wrap: wrap; } }
266
+ </style>