repr-cli 0.2.16__py3-none-any.whl → 0.2.17__py3-none-any.whl

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 (43) hide show
  1. repr/__init__.py +1 -1
  2. repr/api.py +363 -62
  3. repr/auth.py +47 -38
  4. repr/change_synthesis.py +478 -0
  5. repr/cli.py +4099 -280
  6. repr/config.py +119 -11
  7. repr/configure.py +889 -0
  8. repr/cron.py +419 -0
  9. repr/dashboard/__init__.py +9 -0
  10. repr/dashboard/build.py +126 -0
  11. repr/dashboard/dist/assets/index-BYFVbEev.css +1 -0
  12. repr/dashboard/dist/assets/index-BrrhyJFO.css +1 -0
  13. repr/dashboard/dist/assets/index-CcEg74ts.js +270 -0
  14. repr/dashboard/dist/assets/index-Cerc-iA_.js +377 -0
  15. repr/dashboard/dist/assets/index-CjVcBW2L.css +1 -0
  16. repr/dashboard/dist/assets/index-Dfl3mR5E.js +377 -0
  17. repr/dashboard/dist/favicon.svg +4 -0
  18. repr/dashboard/dist/index.html +14 -0
  19. repr/dashboard/manager.py +234 -0
  20. repr/dashboard/server.py +1298 -0
  21. repr/db.py +980 -0
  22. repr/hooks.py +3 -2
  23. repr/loaders/__init__.py +22 -0
  24. repr/loaders/base.py +156 -0
  25. repr/loaders/claude_code.py +287 -0
  26. repr/loaders/clawdbot.py +313 -0
  27. repr/loaders/gemini_antigravity.py +381 -0
  28. repr/mcp_server.py +1196 -0
  29. repr/models.py +503 -0
  30. repr/openai_analysis.py +25 -0
  31. repr/session_extractor.py +481 -0
  32. repr/storage.py +328 -0
  33. repr/story_synthesis.py +1296 -0
  34. repr/templates.py +68 -4
  35. repr/timeline.py +710 -0
  36. repr/tools.py +17 -8
  37. {repr_cli-0.2.16.dist-info → repr_cli-0.2.17.dist-info}/METADATA +48 -10
  38. repr_cli-0.2.17.dist-info/RECORD +52 -0
  39. {repr_cli-0.2.16.dist-info → repr_cli-0.2.17.dist-info}/WHEEL +1 -1
  40. {repr_cli-0.2.16.dist-info → repr_cli-0.2.17.dist-info}/entry_points.txt +1 -0
  41. repr_cli-0.2.16.dist-info/RECORD +0 -26
  42. {repr_cli-0.2.16.dist-info → repr_cli-0.2.17.dist-info}/licenses/LICENSE +0 -0
  43. {repr_cli-0.2.16.dist-info → repr_cli-0.2.17.dist-info}/top_level.txt +0 -0
repr/cron.py ADDED
@@ -0,0 +1,419 @@
1
+ """
2
+ Cron-based story generation scheduling for repr.
3
+
4
+ Provides predictable, scheduled story generation as an alternative to
5
+ hook-triggered generation. Runs every 4 hours by default, skipping
6
+ if there aren't enough commits in the queue.
7
+ """
8
+
9
+ import os
10
+ import re
11
+ import subprocess
12
+ import sys
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ from .config import REPR_HOME, load_config, save_config
18
+
19
+ # Cron job identifier - used to find/modify our entry
20
+ CRON_MARKER = "# repr-auto-generate"
21
+ CRON_MARKER_PAUSED = "# repr-auto-generate-PAUSED"
22
+
23
+ # Default interval: every 4 hours
24
+ DEFAULT_INTERVAL_HOURS = 4
25
+
26
+ # Minimum commits needed to trigger generation
27
+ DEFAULT_MIN_COMMITS = 3
28
+
29
+
30
+ def _get_repr_path() -> str:
31
+ """Get the path to repr executable."""
32
+ # Check common installation paths first
33
+ common_paths = [
34
+ "/usr/local/bin/repr",
35
+ "/opt/homebrew/bin/repr",
36
+ Path.home() / ".local/bin/repr",
37
+ ]
38
+
39
+ for p in common_paths:
40
+ if Path(p).exists():
41
+ return str(p)
42
+
43
+ # Try to find repr in PATH (avoiding venv paths)
44
+ try:
45
+ result = subprocess.run(
46
+ ["which", "-a", "repr"],
47
+ capture_output=True,
48
+ text=True,
49
+ )
50
+ if result.returncode == 0:
51
+ for path in result.stdout.strip().split('\n'):
52
+ # Skip venv/virtualenv paths
53
+ if 'venv' not in path and '.venv' not in path:
54
+ return path
55
+ # If all are venv, use the first one
56
+ return result.stdout.strip().split('\n')[0]
57
+ except Exception:
58
+ pass
59
+
60
+ # Fallback to python -m repr (using system python, not venv)
61
+ return "python3 -m repr"
62
+
63
+
64
+ def _get_helper_script_path() -> Path:
65
+ """Get path to the cron helper script."""
66
+ return REPR_HOME / "bin" / "cron-generate.sh"
67
+
68
+
69
+ def _create_helper_script(min_commits: int = DEFAULT_MIN_COMMITS) -> Path:
70
+ """
71
+ Create the helper script for cron job.
72
+
73
+ This is cleaner than embedding complex shell in crontab.
74
+ """
75
+ repr_path = _get_repr_path()
76
+ log_file = REPR_HOME / "logs" / "cron.log"
77
+
78
+ script_path = _get_helper_script_path()
79
+ script_path.parent.mkdir(parents=True, exist_ok=True)
80
+
81
+ script = f'''#!/bin/bash
82
+ # repr cron helper - auto-generated, do not edit
83
+ # Checks queue and generates stories if enough commits
84
+
85
+ LOG="{log_file}"
86
+ MIN_COMMITS={min_commits}
87
+
88
+ # Get total queue count across all repos
89
+ QUEUE_COUNT=$({repr_path} hooks status --json 2>/dev/null | \\
90
+ python3 -c "import sys,json; d=json.load(sys.stdin); print(sum(r.get('queue_count',0) for r in d))" 2>/dev/null || echo 0)
91
+
92
+ if [ "$QUEUE_COUNT" -ge "$MIN_COMMITS" ]; then
93
+ echo "[$(date -Iseconds)] Generating stories (queue: $QUEUE_COUNT commits)" >> "$LOG"
94
+ {repr_path} generate --local >> "$LOG" 2>&1
95
+ else
96
+ echo "[$(date -Iseconds)] Skipping (queue: $QUEUE_COUNT < $MIN_COMMITS)" >> "$LOG"
97
+ fi
98
+ '''
99
+
100
+ script_path.write_text(script)
101
+ # Make executable
102
+ script_path.chmod(script_path.stat().st_mode | 0o755)
103
+
104
+ return script_path
105
+
106
+
107
+ def _get_cron_command(min_commits: int = DEFAULT_MIN_COMMITS) -> str:
108
+ """
109
+ Build the cron command.
110
+
111
+ Creates a helper script and returns the path to run it.
112
+ """
113
+ script_path = _create_helper_script(min_commits)
114
+ return str(script_path)
115
+
116
+
117
+ def _get_cron_line(interval_hours: int = DEFAULT_INTERVAL_HOURS, min_commits: int = DEFAULT_MIN_COMMITS) -> str:
118
+ """Build the full crontab line."""
119
+ cmd = _get_cron_command(min_commits)
120
+ # Run at minute 0 every N hours
121
+ # Format: minute hour day month weekday command
122
+ return f"0 */{interval_hours} * * * {cmd} {CRON_MARKER}"
123
+
124
+
125
+ def _read_crontab() -> list[str]:
126
+ """Read current user's crontab."""
127
+ try:
128
+ result = subprocess.run(
129
+ ["crontab", "-l"],
130
+ capture_output=True,
131
+ text=True,
132
+ )
133
+ if result.returncode == 0:
134
+ return result.stdout.strip().split('\n') if result.stdout.strip() else []
135
+ return []
136
+ except Exception:
137
+ return []
138
+
139
+
140
+ def _write_crontab(lines: list[str]) -> bool:
141
+ """Write lines to user's crontab."""
142
+ try:
143
+ content = '\n'.join(lines) + '\n' if lines else ''
144
+ result = subprocess.run(
145
+ ["crontab", "-"],
146
+ input=content,
147
+ capture_output=True,
148
+ text=True,
149
+ )
150
+ return result.returncode == 0
151
+ except Exception:
152
+ return False
153
+
154
+
155
+ def _find_repr_cron_line(lines: list[str]) -> tuple[int, bool]:
156
+ """
157
+ Find repr cron line in crontab.
158
+
159
+ Returns:
160
+ (index, is_paused) - index is -1 if not found
161
+ """
162
+ for i, line in enumerate(lines):
163
+ # Check PAUSED first since MARKER is a substring of MARKER_PAUSED
164
+ if CRON_MARKER_PAUSED in line:
165
+ return i, True
166
+ if CRON_MARKER in line:
167
+ return i, False
168
+ return -1, False
169
+
170
+
171
+ def get_cron_status() -> dict[str, Any]:
172
+ """
173
+ Get current cron job status.
174
+
175
+ Returns:
176
+ Dict with 'installed', 'paused', 'interval_hours', 'next_run' keys
177
+ """
178
+ lines = _read_crontab()
179
+ idx, is_paused = _find_repr_cron_line(lines)
180
+
181
+ if idx == -1:
182
+ return {
183
+ "installed": False,
184
+ "paused": False,
185
+ "interval_hours": None,
186
+ "cron_line": None,
187
+ }
188
+
189
+ line = lines[idx]
190
+
191
+ # Parse interval from cron expression (e.g., "0 */4 * * *")
192
+ interval_hours = DEFAULT_INTERVAL_HOURS
193
+ match = re.search(r'\*/(\d+)', line)
194
+ if match:
195
+ interval_hours = int(match.group(1))
196
+
197
+ return {
198
+ "installed": True,
199
+ "paused": is_paused,
200
+ "interval_hours": interval_hours,
201
+ "cron_line": line if not is_paused else line.lstrip('# '),
202
+ }
203
+
204
+
205
+ def install_cron(
206
+ interval_hours: int = DEFAULT_INTERVAL_HOURS,
207
+ min_commits: int = DEFAULT_MIN_COMMITS,
208
+ ) -> dict[str, Any]:
209
+ """
210
+ Install cron job for automatic story generation.
211
+
212
+ Args:
213
+ interval_hours: Hours between runs (default 4)
214
+ min_commits: Minimum commits in queue to trigger generation
215
+
216
+ Returns:
217
+ Dict with 'success', 'message', 'already_installed' keys
218
+ """
219
+ # Ensure log directory exists
220
+ log_dir = REPR_HOME / "logs"
221
+ log_dir.mkdir(parents=True, exist_ok=True)
222
+
223
+ lines = _read_crontab()
224
+ idx, is_paused = _find_repr_cron_line(lines)
225
+
226
+ cron_line = _get_cron_line(interval_hours, min_commits)
227
+
228
+ if idx >= 0:
229
+ if is_paused:
230
+ # Replace paused line with active one
231
+ lines[idx] = cron_line
232
+ if _write_crontab(lines):
233
+ _update_config_cron_status(True, interval_hours, min_commits)
234
+ return {
235
+ "success": True,
236
+ "message": "Cron job resumed and updated",
237
+ "already_installed": False,
238
+ }
239
+ return {
240
+ "success": False,
241
+ "message": "Failed to write crontab",
242
+ "already_installed": False,
243
+ }
244
+ else:
245
+ # Update existing active line
246
+ lines[idx] = cron_line
247
+ if _write_crontab(lines):
248
+ _update_config_cron_status(True, interval_hours, min_commits)
249
+ return {
250
+ "success": True,
251
+ "message": "Cron job updated",
252
+ "already_installed": True,
253
+ }
254
+ return {
255
+ "success": False,
256
+ "message": "Failed to write crontab",
257
+ "already_installed": True,
258
+ }
259
+
260
+ # Add new line
261
+ lines.append(cron_line)
262
+ if _write_crontab(lines):
263
+ _update_config_cron_status(True, interval_hours, min_commits)
264
+ return {
265
+ "success": True,
266
+ "message": f"Cron job installed (every {interval_hours}h, min {min_commits} commits)",
267
+ "already_installed": False,
268
+ }
269
+
270
+ return {
271
+ "success": False,
272
+ "message": "Failed to write crontab",
273
+ "already_installed": False,
274
+ }
275
+
276
+
277
+ def remove_cron() -> dict[str, Any]:
278
+ """
279
+ Remove cron job for story generation.
280
+
281
+ Returns:
282
+ Dict with 'success', 'message' keys
283
+ """
284
+ lines = _read_crontab()
285
+ idx, _ = _find_repr_cron_line(lines)
286
+
287
+ if idx == -1:
288
+ return {
289
+ "success": True,
290
+ "message": "No cron job to remove",
291
+ }
292
+
293
+ lines.pop(idx)
294
+ if _write_crontab(lines):
295
+ _update_config_cron_status(False, None, None)
296
+ return {
297
+ "success": True,
298
+ "message": "Cron job removed",
299
+ }
300
+
301
+ return {
302
+ "success": False,
303
+ "message": "Failed to write crontab",
304
+ }
305
+
306
+
307
+ def pause_cron() -> dict[str, Any]:
308
+ """
309
+ Pause cron job by commenting it out.
310
+
311
+ Returns:
312
+ Dict with 'success', 'message' keys
313
+ """
314
+ lines = _read_crontab()
315
+ idx, is_paused = _find_repr_cron_line(lines)
316
+
317
+ if idx == -1:
318
+ return {
319
+ "success": False,
320
+ "message": "No cron job installed",
321
+ }
322
+
323
+ if is_paused:
324
+ return {
325
+ "success": True,
326
+ "message": "Cron job already paused",
327
+ }
328
+
329
+ # Comment out the line, change marker to PAUSED
330
+ line = lines[idx]
331
+ paused_line = "# " + line.replace(CRON_MARKER, CRON_MARKER_PAUSED)
332
+ lines[idx] = paused_line
333
+
334
+ if _write_crontab(lines):
335
+ config = load_config()
336
+ if "cron" not in config:
337
+ config["cron"] = {}
338
+ config["cron"]["paused"] = True
339
+ save_config(config)
340
+ return {
341
+ "success": True,
342
+ "message": "Cron job paused",
343
+ }
344
+
345
+ return {
346
+ "success": False,
347
+ "message": "Failed to write crontab",
348
+ }
349
+
350
+
351
+ def resume_cron() -> dict[str, Any]:
352
+ """
353
+ Resume paused cron job.
354
+
355
+ Returns:
356
+ Dict with 'success', 'message' keys
357
+ """
358
+ lines = _read_crontab()
359
+ idx, is_paused = _find_repr_cron_line(lines)
360
+
361
+ if idx == -1:
362
+ return {
363
+ "success": False,
364
+ "message": "No cron job installed",
365
+ }
366
+
367
+ if not is_paused:
368
+ return {
369
+ "success": True,
370
+ "message": "Cron job already active",
371
+ }
372
+
373
+ # Uncomment the line, change marker back
374
+ line = lines[idx].lstrip('# ')
375
+ active_line = line.replace(CRON_MARKER_PAUSED, CRON_MARKER)
376
+ lines[idx] = active_line
377
+
378
+ if _write_crontab(lines):
379
+ config = load_config()
380
+ if "cron" not in config:
381
+ config["cron"] = {}
382
+ config["cron"]["paused"] = False
383
+ save_config(config)
384
+ return {
385
+ "success": True,
386
+ "message": "Cron job resumed",
387
+ }
388
+
389
+ return {
390
+ "success": False,
391
+ "message": "Failed to write crontab",
392
+ }
393
+
394
+
395
+ def _update_config_cron_status(
396
+ installed: bool,
397
+ interval_hours: int | None,
398
+ min_commits: int | None,
399
+ ) -> None:
400
+ """Update cron status in config file."""
401
+ config = load_config()
402
+
403
+ if installed:
404
+ config["cron"] = {
405
+ "installed": True,
406
+ "paused": False,
407
+ "interval_hours": interval_hours,
408
+ "min_commits": min_commits,
409
+ "installed_at": datetime.now().isoformat(),
410
+ }
411
+ else:
412
+ config["cron"] = {
413
+ "installed": False,
414
+ "paused": False,
415
+ "interval_hours": None,
416
+ "min_commits": None,
417
+ }
418
+
419
+ save_config(config)
@@ -0,0 +1,9 @@
1
+ """
2
+ Timeline web dashboard module.
3
+
4
+ Provides a local web server for exploring the unified timeline.
5
+ """
6
+
7
+ from .server import run_server
8
+
9
+ __all__ = ["run_server"]
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Build script for the dashboard
4
+ Inlines all CSS and JavaScript into a single HTML file for distribution
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+
10
+
11
+ def read_file(path: Path) -> str:
12
+ """Read file contents"""
13
+ with open(path, 'r', encoding='utf-8') as f:
14
+ return f.read()
15
+
16
+
17
+ def inline_css(html: str, src_dir: Path) -> str:
18
+ """Replace CSS link tags with inline styles"""
19
+ styles = []
20
+
21
+ # Read all CSS files
22
+ css_files = [
23
+ src_dir / 'styles' / 'main.css',
24
+ src_dir / 'styles' / 'components.css'
25
+ ]
26
+
27
+ for css_file in css_files:
28
+ if css_file.exists():
29
+ styles.append(read_file(css_file))
30
+
31
+ # Combine all styles
32
+ combined_styles = '\n'.join(styles)
33
+
34
+ # Replace link tags with inline style
35
+ import re
36
+ pattern = r'<link rel="stylesheet" href="styles/[^"]+\.css">'
37
+
38
+ # Find first occurrence and replace with all styles
39
+ match = re.search(pattern, html)
40
+ if match:
41
+ replacement = f'<style>\n{combined_styles}\n </style>'
42
+ html = html[:match.start()] + replacement + re.sub(pattern, '', html[match.end():])
43
+
44
+ return html
45
+
46
+
47
+ def inline_js(html: str, src_dir: Path) -> str:
48
+ """Replace JavaScript script tags with inline scripts"""
49
+ scripts = []
50
+
51
+ # Read all JS files in order
52
+ js_files = [
53
+ src_dir / 'scripts' / 'utils.js',
54
+ src_dir / 'scripts' / 'api.js',
55
+ src_dir / 'scripts' / 'state.js',
56
+ src_dir / 'scripts' / 'theme.js',
57
+ src_dir / 'scripts' / 'keyboard.js',
58
+ src_dir / 'scripts' / 'stories.js',
59
+ src_dir / 'scripts' / 'settings.js',
60
+ src_dir / 'scripts' / 'repos.js'
61
+ ]
62
+
63
+ for js_file in js_files:
64
+ if js_file.exists():
65
+ scripts.append(read_file(js_file))
66
+
67
+ # Combine all scripts
68
+ combined_scripts = '\n\n'.join(scripts)
69
+
70
+ # Replace script tags with inline script
71
+ import re
72
+ pattern = r'<script src="scripts/[^"]+\.js"></script>'
73
+
74
+ # Find first occurrence and replace with all scripts
75
+ match = re.search(pattern, html)
76
+ if match:
77
+ replacement = f'<script>\n{combined_scripts}\n </script>'
78
+ html = html[:match.start()] + replacement + re.sub(pattern, '', html[match.end():])
79
+
80
+ return html
81
+
82
+
83
+ def build():
84
+ """Build the single-file dashboard"""
85
+ # Get paths
86
+ dashboard_dir = Path(__file__).parent
87
+ src_dir = dashboard_dir / 'src'
88
+ src_html = src_dir / 'index.html'
89
+ output_html = dashboard_dir / 'index.html'
90
+
91
+ if not src_html.exists():
92
+ print(f"Error: Source HTML not found at {src_html}")
93
+ return 1
94
+
95
+ print(f"Building dashboard from {src_dir}...")
96
+
97
+ # Read source HTML
98
+ html = read_file(src_html)
99
+
100
+ # Inline CSS
101
+ print(" Inlining CSS...")
102
+ html = inline_css(html, src_dir)
103
+
104
+ # Inline JavaScript
105
+ print(" Inlining JavaScript...")
106
+ html = inline_js(html, src_dir)
107
+
108
+ # Write output
109
+ print(f" Writing to {output_html}...")
110
+ with open(output_html, 'w', encoding='utf-8') as f:
111
+ f.write(html)
112
+
113
+ # Get file sizes
114
+ src_size = sum(f.stat().st_size for f in src_dir.rglob('*') if f.is_file())
115
+ output_size = output_html.stat().st_size
116
+
117
+ print(f"\n✓ Build complete!")
118
+ print(f" Source: {len(list(src_dir.rglob('*.js')))} JS + {len(list(src_dir.rglob('*.css')))} CSS files ({src_size:,} bytes)")
119
+ print(f" Output: {output_html.name} ({output_size:,} bytes)")
120
+ print(f" Lines: {len(html.splitlines())}")
121
+
122
+ return 0
123
+
124
+
125
+ if __name__ == '__main__':
126
+ exit(build())
@@ -0,0 +1 @@
1
+ :root{--bg: #f9f7f4;--surface: #ffffff;--surface-hover: #f8fafc;--surface-card: #ffffff;--border: #eaecf0;--text-primary: #111827;--text-secondary: #4b5563;--text-muted: #9ca3af;--accent: #FF5A1F;--accent-hover: #e04f1a;--green: #16a34a;--red: #dc2626;--font-mono: "JetBrains Mono", "Monaco", monospace;--font-serif: "Spectral", "Georgia", serif}[data-theme=dark]{--bg: #0d1117;--surface: #161b22;--surface-hover: #21262d;--surface-card: #0d1117;--border: #30363d;--text-primary: #e6edf3;--text-secondary: #8b949e;--text-muted: #6e7681;--accent: #ff7a33;--accent-hover: #ff8f52;--green: #3fb950;--red: #f85149}*{margin:0;padding:0;box-sizing:border-box}body{font-family:Inter,-apple-system,BlinkMacSystemFont,sans-serif;background:var(--bg);color:var(--text-primary);line-height:1.4;overflow-y:scroll}.dashboard{display:grid;grid-template-columns:200px 1fr;min-height:100vh;padding:0;gap:0;width:100%;align-items:start}.main-content{grid-column:2;width:100%;min-height:100vh;background:var(--bg);display:flex;justify-content:center;padding:20px}.main-layout{display:grid;grid-template-columns:minmax(auto,600px) 300px;gap:32px;width:100%;max-width:1000px;align-items:start;margin-top:20px}.feed-column{flex:1;max-width:600px;border-right:1px solid var(--border);min-height:100vh;background:var(--surface)}.view-title{font-size:20px;font-weight:700;padding:0 16px;display:none}@media (min-width: 1024px){.view-title{display:block}}.right-sidebar{width:350px;padding:12px 24px;display:none;position:sticky;top:24px;align-self:start;height:-moz-fit-content;height:fit-content;z-index:10}@media (min-width: 1100px){.right-sidebar{display:block}}.widget{background:var(--surface-hover);border-radius:16px;margin-bottom:16px;overflow:hidden;border:1px solid transparent}.widget-title{font-size:19px;font-weight:800;padding:12px 16px;color:var(--text-primary)}.widget-list{display:flex;flex-direction:column}.widget-item{padding:12px 16px;cursor:pointer;transition:background .2s;display:flex;align-items:center;gap:12px}.widget-item:hover{background:#00000008}[data-theme=dark] .widget-item:hover{background:#ffffff08}.widget-item-avatar{width:40px;height:40px;border-radius:8px;background:var(--accent);display:flex;align-items:center;justify-content:center;font-weight:700;color:#fff;flex-shrink:0}.widget-item-info{flex:1;min-width:0}.widget-item-name{font-weight:700;font-size:19px;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.widget-item-subtitle{font-size:13px;color:var(--text-muted)}.btn-text{width:100%;padding:16px;text-align:left;background:none;border:none;color:var(--accent);font-size:15px;cursor:pointer}.btn-text:hover{background:#00000008}.stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--border);padding:1px}.stat-box{background:var(--surface-hover);padding:16px;text-align:center}.stat-label{font-size:12px;color:var(--text-muted);text-transform:uppercase;margin-bottom:4px}.stat-value{font-size:20px;font-weight:700;color:var(--text-primary)}.right-sidebar-footer{padding:16px;font-size:13px;color:var(--text-muted)}.right-sidebar-footer nav{display:flex;flex-wrap:wrap;gap:8px 12px}.right-sidebar-footer a{color:var(--text-muted);text-decoration:none}.right-sidebar-footer a:hover{text-decoration:underline}.content-header{padding:28px 32px;border-bottom:1px solid var(--border);background:var(--surface);position:sticky;top:0;z-index:50;display:flex;flex-direction:column}.mobile-menu-toggle{display:none;font-size:24px;cursor:pointer;margin-bottom:12px;width:-moz-fit-content;width:fit-content}.content-header h1{font-family:var(--font-serif);font-size:28px;font-weight:700;color:var(--text-primary);letter-spacing:-.02em}.content-header-subtitle{font-size:14px;color:var(--text-muted);margin-top:4px}.content-body{padding:0}.container{max-width:800px;margin:0 auto;min-height:100vh;background:#fff}.feed-container{max-width:680px;margin:0 auto}.settings-container{max-width:700px;margin:0 auto}.loading-text{color:var(--text-muted);font-size:14px;padding:20px;text-align:center}.empty-state{text-align:center;padding:40px 20px;color:var(--text-muted)}.empty-state-title{font-size:16px;font-weight:500;color:var(--text-secondary);margin-bottom:8px}@keyframes slideIn{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.post{padding:28px 24px;border-bottom:1px solid var(--border);cursor:pointer;transition:all .2s;background:var(--surface);animation:fadeIn .4s ease-out}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}#view-detail{display:none;width:100%}#view-detail.open{display:block}#view-profile{position:fixed;top:0;right:0;bottom:0;left:0;z-index:200;background:var(--bg);display:none;overflow-y:auto}#view-profile.open{display:block}#view-user{position:fixed;top:0;right:0;bottom:0;left:0;z-index:200;background:var(--bg);display:none;overflow-y:auto}#view-user.open{display:block}#view-profile .sidebar{position:relative;height:100%}#view-profile .main-content{margin-left:0}.mobile-nav{display:none}@media (max-width: 768px){.sidebar{transform:translate(-100%);transition:transform .3s ease;box-shadow:2px 0 10px #0000001a}.sidebar.open{transform:translate(0)}.main-content{margin-left:0!important}.content-header{padding:16px 20px}.date-header{padding:16px 20px 8px}.date-header:before{left:20px;right:20px}.mobile-menu-toggle{display:block}.feed-container,.settings-container{padding:0 10px}.feed-wrapper{border-left:none;border-right:none}.post-card{padding:12px 16px}.post-avatar{width:36px;height:36px;font-size:14px}.post-text{font-size:15px}.post-actions{max-width:100%}.date-header{padding:10px 16px;top:0}.tabs-bar{flex-direction:column;align-items:flex-start;padding:10px 16px;gap:12px}.search-container{width:100%;margin-left:0}.search-input{flex:1;width:100%!important}.sidebar-overlay{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:#0000004d;z-index:90}.sidebar-overlay.open{display:block}.mobile-nav{display:flex;position:fixed;bottom:0;left:0;right:0;height:64px;background:var(--surface);border-top:1px solid var(--border);z-index:100;justify-content:space-around;align-items:center;padding-bottom:env(safe-area-inset-bottom)}.mobile-nav-item{display:flex;flex-direction:column;align-items:center;gap:4px;font-size:11px;color:var(--text-muted);cursor:pointer}.mobile-nav-item.active{color:var(--accent)}.mobile-nav-item span:first-child{font-size:20px}body{padding-bottom:64px}}.btn,.sidebar-item,.repo-tab,.repo-action-btn{min-height:44px;display:flex;align-items:center}.date-header{padding:12px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:56px;z-index:40;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);background:#ffffffd9}[data-theme=dark] .date-header{background:#161b22d9}.date-header-label{font-size:15px;font-weight:700;color:var(--text-primary)}#recommended-repos{max-height:195px;overflow:hidden;transition:max-height .3s ease-in-out}#recommended-repos.expanded{max-height:400px;overflow-y:auto}.sidebar{grid-column:1;width:100%;flex-shrink:0;background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;position:sticky;top:0;height:100vh;z-index:100;padding:0 12px;overflow-y:auto}.sidebar-header{padding:24px 12px;border-bottom:none}.sidebar-logo{display:flex;align-items:center;gap:12px;padding:12px;border-radius:50%;width:-moz-fit-content;width:fit-content;transition:background .2s}.sidebar-logo:hover{background:#ff5a1f1a}.sidebar-avatar{width:44px;height:44px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:20px;color:#fff;box-shadow:0 4px 12px #ff5a1f33}.sidebar-title{font-size:19px;font-weight:800;color:var(--text-primary);letter-spacing:-.5px}.sidebar-subtitle{font-size:12px;color:var(--text-muted)}.sidebar-nav{flex:1;padding:12px 0}.sidebar-section{margin-bottom:32px}.sidebar-section-title{display:none}.sidebar-item{display:flex;align-items:center;gap:16px;padding:12px 16px;border-radius:30px;font-size:19px;font-weight:500;color:var(--text-primary);cursor:pointer;transition:background .2s;margin-bottom:4px;width:-moz-fit-content;width:fit-content}.sidebar-item:hover{background:var(--surface-hover)}.sidebar-item.active{background:transparent;color:var(--text-primary);font-weight:700}.sidebar-item.active .sidebar-icon{color:var(--text-primary)}.sidebar-btn{margin-top:24px;width:100%;height:52px;border-radius:26px;font-size:17px;font-weight:700;display:flex;align-items:center;justify-content:center;gap:12px;box-shadow:0 4px 12px #ff5a1f33}.sidebar-btn-icon{display:none}@media (max-width: 1280px){.sidebar{width:80px;align-items:center}.sidebar-btn{width:52px;padding:0}.sidebar-btn-text,.sidebar-title,.sidebar-subtitle,.sidebar-item span:last-child{display:none}.sidebar-btn-icon{display:block;width:24px;height:24px}.sidebar-item{padding:12px;border-radius:50%}}.sidebar-icon{width:26px;height:26px;display:flex;align-items:center;justify-content:center;color:var(--text-primary)}.sidebar-icon svg{width:26px;height:26px;stroke-width:2px}.sidebar-footer{padding:24px 12px;border-top:none}.tabs-bar{display:flex;align-items:center;background:#ffffffd9;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 16px;position:sticky;top:0;z-index:50;height:53px}[data-theme=dark] .tabs-bar{background:#0d1117d9}.post-card{display:flex;flex-direction:column;gap:2px;padding:24px 20px;border-bottom:1px solid var(--border);cursor:pointer;transition:background .15s ease;background:var(--surface)}.post-avatar{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px;color:#fff;flex-shrink:0;overflow:hidden;position:relative;top:8px}.post-avatar img{width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.post-card-header{display:flex;align-items:center;gap:12px}.post-header-info{display:flex;flex-direction:column}.post-header-author{line-height:1.4}.post-header-meta{display:flex;align-items:center;gap:6px;margin-top:2px;color:var(--text-secondary);font-size:13.5px}.post-author{font-weight:600;font-size:15px;color:var(--text-primary)}.post-author:hover{text-decoration:underline}.post-title-inline{color:var(--text-primary);font-weight:500;font-size:15px}.post-badge-container{margin-left:auto}.post-content-full{margin-top:0;margin-left:40px}.post-handle,.post-time,.post-meta-sep{font-size:13px;color:inherit}.post-handle,.category-meta,.post-time,.post-meta-sep{font-size:13.5px;color:inherit;font-weight:400}.category-meta{text-transform:lowercase;letter-spacing:normal}.post-handle:hover{text-decoration:underline}.post-text{font-size:15px;line-height:1.5;margin-top:0}.post-text strong{display:block;font-size:15px;margin-bottom:4px;font-weight:400;letter-spacing:normal;color:var(--text-primary)}.post-text strong:hover{text-decoration:none;cursor:text}.post-actions{display:flex;justify-content:space-between;margin-top:12px;max-width:425px}.post-action{display:flex;align-items:center;gap:8px;color:var(--text-muted);font-size:13px;transition:all .2s}.post-action:hover{color:var(--accent)}.post-action:hover svg{background:#ff5a1f1a;border-radius:50%}.post-action svg{width:18px;height:18px;padding:8px;margin:-8px;box-sizing:content-box}.post-detail{padding:16px;background:var(--surface)}.post-detail-header{display:flex;gap:12px;align-items:center;margin-bottom:20px}.post-detail-author-info{flex:1}.post-detail-title{font-family:var(--font-serif);font-size:28px;font-weight:800;line-height:1.2;margin-bottom:24px;color:var(--text-primary);letter-spacing:-.5px}.post-detail-text{font-size:16px;white-space:pre-wrap;word-wrap:break-word}.detail-list{padding-left:20px;color:var(--text-secondary);font-size:16px;line-height:1.6;margin-bottom:24px}.detail-list li{margin-bottom:8px}.post-detail-footer{margin-top:40px}.post-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}.hashtag{font-size:15px;color:var(--text-secondary);font-weight:500;opacity:.8}.hashtag:hover{color:var(--accent);opacity:1;text-decoration:underline}.post-actions{display:flex;justify-content:space-between;max-width:400px;margin-top:12px}.post-action{display:flex;align-items:center;gap:8px;color:var(--text-muted);font-size:13px;padding:8px;margin:-8px;border-radius:50%;transition:all .2s;cursor:pointer}.post-action:hover{color:var(--accent);background:#ff5a1f1a}.post-action svg{width:18px;height:18px}.post-media{margin-top:12px;border:1px solid var(--border);border-radius:16px;overflow:hidden}.post-diagram,.post-output{margin:0;padding:12px 16px;font-family:var(--font-mono);font-size:13px;line-height:1.5;color:var(--text-secondary);background:var(--bg);white-space:pre;overflow-x:auto}.post-output{background:#0d1117;color:#e6edf3}[data-theme=dark] .post-diagram{background:#161b22}.post{padding:24px 0;border-bottom:1px solid var(--border);cursor:pointer}.post-content{flex:1;min-width:0}.name{font-weight:600;font-size:14px;color:var(--text-primary)}.category{font-size:11px;padding:2px 8px;border-radius:4px;font-weight:500;text-transform:uppercase;letter-spacing:.03em}.category.feature{background:#e8f4fd;color:#1e6bb8}.category.bugfix{background:#fde8e8;color:#b91c1c}.category.refactor{background:#fef3cd;color:#92400e}.category.docs{background:#dcfce7;color:#15803d}.category.chore{background:#f3f4f6;color:#4b5563}.category.infra{background:#f3e8ff;color:#7c3aed}[data-theme=dark] .category.feature{background:#1e6bb833;color:#58a6ff}[data-theme=dark] .category.bugfix{background:#b91c1c33;color:#f85149}[data-theme=dark] .category.refactor{background:#92400e33;color:#d29922}[data-theme=dark] .category.docs{background:#15803d33;color:#3fb950}[data-theme=dark] .category.chore{background:#4b556333;color:#8b949e}[data-theme=dark] .category.infra{background:#7c3aed33;color:#a371f7}.time{color:var(--text-muted);font-size:14px}.post-title{font-family:var(--font-serif);font-weight:700;font-size:20px;margin:0 0 8px;line-height:1.4;color:var(--text-primary);letter-spacing:-.01em;transition:color .15s ease}.post-card.selected{background:var(--surface-hover);margin:0 -16px;padding-left:16px;padding-right:16px;border-radius:8px}.skills-inline{display:inline-flex;flex-wrap:wrap;gap:6px}.skill-tag{color:var(--text-muted);font-size:12px;font-weight:500}.skill-tag+.skill-tag:before{content:"·";margin-right:6px}.verified-badge{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;background:var(--accent);color:#fff;border-radius:50%;font-size:9px;margin-left:4px;vertical-align:middle}.diagram-block{background:var(--surface-card);border-left:2px solid var(--border);padding:12px 16px;margin-top:12px;border-radius:0 6px 6px 0;overflow-x:auto}.diagram-block pre{font-family:var(--font-mono);font-size:11px;line-height:1.4;color:var(--text-secondary);margin:0;white-space:pre}.file-metrics-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}.file-metric-card{background:var(--surface-card);padding:16px;border-radius:8px;border:1px solid var(--border)}.file-name{font-family:var(--font-mono);font-size:13px;font-weight:600;color:var(--text-primary);margin-bottom:12px;word-break:break-all}.metric-row{display:flex;justify-content:space-between;font-size:12px;margin-bottom:4px}.metric-label{color:var(--text-muted)}.metric-val{font-family:var(--font-mono)}.insight-box{background:linear-gradient(135deg,#fff7ed,#ffedd5);border-left:4px solid var(--accent);padding:24px;border-radius:0 8px 8px 0;margin-bottom:24px}[data-theme=dark] .insight-box{background:linear-gradient(135deg,#ff67191a,#ff7a330d)}.insight-label{font-size:11px;font-weight:600;color:var(--accent);text-transform:uppercase;letter-spacing:.1em;margin-bottom:10px}.insight-text{font-family:var(--font-serif);font-size:16px;line-height:1.6;color:var(--text-primary);font-weight:400;font-style:italic}.snippet-block{background:#0d1117;border-radius:8px;overflow:hidden;margin-bottom:16px;font-family:var(--font-mono)}.snippet-header{background:#161b22;padding:10px 16px;font-size:13px;color:#c9d1d9;border-bottom:1px solid #30363d;display:flex;justify-content:space-between;align-items:center}.snippet-code{padding:16px;font-size:13px;line-height:1.6;color:#e6edf3;overflow-x:auto}.snippet-code pre{margin:0;font-family:var(--font-mono)}.section-subtitle{font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:8px;margin-top:16px}.search-container{display:flex;align-items:center;margin:0 auto;position:relative;width:100%;max-width:600px}.search-icon{position:absolute;left:16px;color:var(--text-muted);pointer-events:none;display:flex;align-items:center}.search-input{background:var(--bg);border:2px solid var(--border);padding:12px 16px 12px 44px;border-radius:12px;font-size:15px;color:var(--text-primary);width:100%;outline:none;transition:all .2s ease}.search-input::-moz-placeholder{color:var(--text-muted)}.search-input::placeholder{color:var(--text-muted)}.search-input:hover{border-color:var(--text-muted)}.search-input:focus{border-color:var(--accent);background:var(--surface);box-shadow:0 0 0 4px #ff7a331a}.search-input:focus+.search-shortcut{opacity:0}.search-shortcut{position:absolute;right:12px;display:flex;align-items:center;gap:2px;padding:4px 8px;background:var(--surface-hover);border:1px solid var(--border);border-radius:6px;font-family:var(--font-mono);font-size:11px;color:var(--text-muted);pointer-events:none;transition:opacity .15s}.search-shortcut-mod:after{content:"⌘"}body.is-windows .search-shortcut-mod:after{content:"Ctrl+"}@media (pointer: coarse){.search-shortcut{display:none}}.highlight{background-color:#ffeb3b66;border-radius:2px;padding:0 1px}[data-theme=dark] .highlight{background-color:#d299224d}.files-row{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:24px}.file-tag{background:var(--surface-card);border:1px solid var(--border);padding:4px 10px;border-radius:4px;font-family:var(--font-mono);font-size:12px;color:var(--text-secondary);display:inline-block}.settings-nav{display:flex;gap:0;border-bottom:1px solid var(--border);background:var(--surface);margin:24px 24px 0;border-radius:8px 8px 0 0}.settings-nav-item{padding:14px 24px;font-size:14px;font-weight:500;color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;transition:all .2s}.settings-nav-item:hover{color:var(--text-primary)}.settings-nav-item.active{color:var(--accent);border-bottom-color:var(--accent)}.settings-content{padding:32px 24px;background:var(--surface);margin:0 24px 24px;border-radius:0 0 8px 8px;border:1px solid var(--border);border-top:none}.settings-container>.settings-content:first-child{margin-top:24px;border-radius:8px;border-top:1px solid var(--border)}.settings-section{margin-bottom:32px}.settings-section-title{font-family:var(--font-serif);font-size:18px;font-weight:600;color:var(--text-primary);margin-bottom:20px;padding-bottom:12px;border-bottom:1px solid var(--border);letter-spacing:-.01em}.settings-group{margin-bottom:20px}.settings-label{display:block;font-size:13px;font-weight:600;color:var(--text-primary);margin-bottom:6px}.settings-hint{font-size:12px;color:var(--text-muted);margin-top:4px}.settings-input{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:6px;font-size:14px;color:var(--text-primary);background:var(--bg);transition:border-color .2s}.settings-input:focus{outline:none;border-color:var(--accent)}.settings-select{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:6px;font-size:14px;color:var(--text-primary);background:var(--bg);cursor:pointer}.settings-checkbox-row{display:flex;align-items:center;gap:10px;padding:8px 0}.settings-checkbox{width:18px;height:18px;cursor:pointer}.settings-checkbox-label{font-size:14px;color:var(--text-primary);cursor:pointer}.settings-row{display:grid;grid-template-columns:1fr 1fr;gap:16px}.json-editor{width:100%;min-height:500px;padding:16px;font-family:var(--font-mono);font-size:13px;line-height:1.5;border:1px solid var(--border);border-radius:8px;background:#0d1117;color:#e6edf3;resize:vertical}.json-editor:focus{outline:none;border-color:var(--accent)}.btn{padding:10px 20px;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s;border:none}.btn-primary{background:var(--accent);color:#fff}.btn-primary:hover{background:var(--accent-hover)}.btn-primary:disabled{background:#ccc;cursor:not-allowed}.btn-secondary{background:var(--surface-card);color:var(--text-primary);border:1px solid var(--border)}.btn-secondary:hover{background:var(--surface-hover)}.btn-row{display:flex;gap:12px;margin-top:20px}.toast{position:fixed;bottom:24px;right:24px;padding:12px 20px;border-radius:8px;font-size:14px;font-weight:500;z-index:1000;animation:slideIn .3s ease}.toast.success{background:var(--green);color:#fff}.toast.error{background:var(--red);color:#fff}.tags-input{display:flex;flex-wrap:wrap;gap:6px;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg);min-height:42px;align-items:center}.tag-item{display:flex;align-items:center;gap:4px;background:var(--surface-card);border:1px solid var(--border);padding:4px 8px;border-radius:4px;font-size:13px}.tag-remove{cursor:pointer;color:var(--text-muted);font-size:16px;line-height:1}.tag-remove:hover{color:var(--red)}.tags-input input{border:none;outline:none;flex:1;min-width:100px;font-size:14px;background:transparent}.repos-list{display:flex;flex-direction:column;gap:12px}.repo-item{display:flex;align-items:center;gap:16px;padding:16px;background:var(--bg);border:1px solid var(--border);border-radius:8px}.repo-item.paused{opacity:.6}.repo-item.missing{border-color:var(--red);background:#fef2f2}.repo-info{flex:1;min-width:0}.repo-name-row{display:flex;align-items:center;gap:8px;margin-bottom:4px}.repo-name{font-weight:600;font-size:15px;color:var(--text-primary);cursor:pointer;border-bottom:1px dashed transparent;transition:all .15s}.repo-name:hover{color:var(--accent);border-bottom-color:var(--accent)}.repo-edit-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:2px 6px;border-radius:4px;opacity:.4;transition:all .15s}.repo-name-row:hover .repo-edit-btn{opacity:1}.repo-edit-btn:hover{color:var(--accent);opacity:1;background:var(--surface-hover);color:var(--text-primary)}.repo-link{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:4px;color:var(--text-muted);text-decoration:none;transition:all .15s;font-size:13px;line-height:1}.repo-link:hover{color:var(--accent);background:var(--surface-hover)}.repo-link-icon{display:inline-block;transform:translateY(-1px)}.repo-path{font-family:var(--font-mono);font-size:12px;color:var(--text-muted);word-break:break-all}.repo-status{display:flex;align-items:center;gap:8px;margin-top:6px}.repo-badge{font-size:11px;padding:2px 8px;border-radius:12px;font-weight:500}.repo-badge.active{background:#dcfce7;color:#15803d}.repo-badge.paused{background:#fef3cd;color:#92400e}.repo-badge.missing{background:#fde8e8;color:#b91c1c}.repo-badge.hook{background:#e8f4fd;color:#1e6bb8}[data-theme=dark] .repo-badge.active{background:#15803d33;color:#3fb950}[data-theme=dark] .repo-badge.paused{background:#92400e33;color:#d29922}[data-theme=dark] .repo-badge.missing{background:#b91c1c33;color:#f85149}[data-theme=dark] .repo-badge.hook{background:#1e6bb833;color:#58a6ff}.repo-actions{display:flex;gap:8px}.repo-action-btn{padding:6px 12px;font-size:12px;font-weight:500;border:1px solid var(--border);border-radius:6px;background:var(--surface);color:var(--text-secondary);cursor:pointer;transition:all .15s}.repo-action-btn:hover{background:var(--surface-hover);color:var(--text-primary)}.repo-action-btn.danger:hover{background:#fde8e8;border-color:var(--red);color:var(--red)}.cron-status-box{padding:20px;background:var(--bg);border:1px solid var(--border);border-radius:8px}.cron-status-row{display:flex;justify-content:space-between;align-items:center;padding:8px 0}.cron-status-label{font-size:14px;color:var(--text-secondary)}.cron-status-value{font-size:14px;font-weight:500;color:var(--text-primary)}.cron-status-value.active{color:var(--green)}.cron-status-value.inactive{color:var(--text-muted)}.cron-status-value.paused{color:#92400e}.cli-hint{margin-bottom:16px}.cli-hint code{display:block;font-family:var(--font-mono);font-size:13px;background:#1a1a1a;color:#e6edf3;padding:12px 16px;border-radius:6px;margin-bottom:6px}.skeleton{background:var(--surface-hover);background:linear-gradient(90deg,var(--surface-hover) 25%,var(--border) 50%,var(--surface-hover) 75%);background-size:200% 100%;animation:shimmer 1.5s infinite linear;border-radius:4px}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.skeleton-card{display:flex;gap:12px;padding:20px;background:var(--surface);border:1px solid var(--border);border-radius:16px}.skeleton-card .skeleton-avatar{width:44px;height:44px;border-radius:50%;flex-shrink:0}.skeleton-body{flex:1}.skeleton-header{height:14px;width:40%;margin-bottom:8px}.skeleton-title{height:18px;width:80%;margin-bottom:8px}.skeleton-text{height:14px;margin-bottom:6px;width:100%}.skeleton-post{padding:28px 24px;border-bottom:1px solid var(--border)}.skeleton-avatar{width:20px}.date-header{padding:24px 24px 8px;margin-top:8px;border-bottom:1px solid var(--border)}.date-header:first-child{margin-top:0;padding-top:16px}.date-header-label{font-size:12px;font-weight:500;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000;opacity:0;visibility:hidden;transition:all .2s ease;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-overlay.open{opacity:1;visibility:visible}.modal-container{background:var(--surface);border-radius:12px;width:100%;max-width:480px;box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a;transform:translateY(20px);transition:all .3s cubic-bezier(.34,1.56,.64,1);overflow:hidden;border:1px solid var(--border)}.modal-overlay.open .modal-container{transform:translateY(0)}.modal-header{padding:20px 24px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between}.modal-title{font-family:var(--font-serif);font-size:18px;font-weight:700;color:var(--text-primary)}.modal-close{background:none;border:none;font-size:24px;color:var(--text-muted);cursor:pointer;padding:4px;line-height:1;border-radius:4px}.modal-close:hover{background:var(--surface-hover);color:var(--text-primary)}.modal-body{padding:24px}.modal-footer{padding:16px 24px;border-top:1px solid var(--border);background:var(--bg);display:flex;justify-content:flex-end;gap:12px}.editable-field-container{position:relative;display:flex;align-items:center}.editable-field-container .settings-input{padding-right:40px}.editable-field-icon{position:absolute;right:12px;color:var(--text-muted);pointer-events:none;font-size:14px}.profile-container{max-width:600px;margin:0 auto;min-height:100vh;background:var(--surface);border-left:1px solid var(--border);border-right:1px solid var(--border);position:relative}.profile-header-nav{position:sticky;top:0;z-index:100;background:#ffffffd9;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border-bottom:1px solid transparent;padding:0 16px;height:53px;display:flex;align-items:center;gap:20px}[data-theme=dark] .profile-header-nav{background:#0d1117d9}.back-button{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .15s ease;color:var(--text-primary);margin-left:-8px;background:transparent}.back-button:hover{background:var(--surface-hover);transform:scale(1.05)}.back-button:active{background:var(--surface-active);transform:scale(.95)}.profile-nav-title{font-weight:700;font-size:17px;color:var(--text-primary)}.profile-cover{height:200px;width:100%;background:linear-gradient(135deg,#ff9a9e,#fecfef 99%,#fecfef);background-size:cover;background-position:center}[data-theme=dark] .profile-cover{background:linear-gradient(135deg,#4b2c34,#3e1f2b)}.profile-content{position:relative}.profile-top-section{padding:12px 16px;display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px}.profile-avatar-wrapper{margin-top:-15%;padding:4px;background:var(--surface);border-radius:50%;display:inline-block}.profile-avatar-large{width:134px;height:134px;border-radius:50%;background:var(--green);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:56px;color:#fff;border:4px solid var(--surface);position:relative;overflow:hidden}.profile-avatar-large img{width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.profile-actions{display:flex;gap:8px}.btn-sm{padding:6px 16px;font-size:14px;border-radius:20px;border-width:1px}.profile-details{padding:0 16px 16px}.profile-name-row{display:flex;align-items:center;gap:4px;margin-bottom:2px}.profile-name-row h1{font-size:20px;font-weight:800;color:var(--text-primary);margin:0;line-height:1.2}.verified-badge-large{display:inline-flex;align-items:center;justify-content:center;color:var(--accent);width:20px;height:20px}.profile-handle{font-size:15px;color:var(--text-secondary);margin-bottom:12px}.profile-bio{font-size:15px;color:var(--text-primary);line-height:1.5;margin-bottom:12px;white-space:pre-wrap}.profile-metadata-row{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:12px;align-items:center}.profile-pill{display:inline-flex;align-items:center;gap:6px;background:var(--surface-card);padding:4px 12px;border-radius:16px;font-size:13px;color:var(--text-secondary);font-weight:500}.profile-pill:hover{background:var(--surface-hover);color:var(--text-primary);cursor:pointer}.pill-icon{font-size:14px}.profile-pill-count{font-size:13px;color:var(--text-secondary);font-weight:500;background:var(--surface-card);padding:4px 12px;border-radius:16px}.profile-stats-row{display:flex;gap:20px;font-size:14px;color:var(--text-secondary)}.stat-item strong{color:var(--text-primary);font-weight:700}.profile-tabs{display:flex;border-bottom:1px solid var(--border);margin-top:16px}.profile-tab{flex:1;text-align:center;padding:16px 0;font-size:15px;font-weight:500;color:var(--text-secondary);cursor:pointer;position:relative;transition:all .2s}.profile-tab:hover{background:var(--surface-hover);color:var(--text-primary)}.profile-tab.active{color:var(--text-primary);font-weight:700}.profile-tab.active:after{content:"";position:absolute;bottom:0;left:50%;transform:translate(-50%);width:56px;height:4px;background:var(--accent);border-radius:2px}.auth-section{padding:12px;border-top:1px solid var(--border)}.auth-connect-btn{width:100%;display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--accent);color:#fff;border:none;border-radius:24px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s}.auth-connect-btn:hover{opacity:.9;transform:scale(1.02)}.auth-connect-btn .auth-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center}.auth-connect-btn .auth-icon svg{width:18px;height:18px}.auth-connected{display:flex;align-items:center;justify-content:space-between;gap:8px}.auth-user{display:flex;align-items:center;gap:8px;min-width:0;flex:1}.auth-user .auth-icon{width:32px;height:32px;min-width:32px;background:var(--accent);border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff}.auth-user .auth-icon svg{width:18px;height:18px}.auth-name{font-size:13px;font-weight:500;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.auth-logout-btn{width:32px;height:32px;min-width:32px;padding:0;background:transparent;border:none;border-radius:50%;color:var(--text-secondary);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.auth-logout-btn:hover{background:var(--surface-hover);color:var(--red)}.auth-logout-btn svg{width:18px;height:18px}.auth-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s}.auth-modal.open{opacity:1;visibility:visible}.auth-modal-content{pointer-events:auto;background:var(--surface);border-radius:16px;width:100%;max-width:400px;margin:20px;position:relative;box-shadow:0 20px 40px #0003;animation:modalSlideIn .2s ease-out}@keyframes modalSlideIn{0%{opacity:0;transform:translateY(-20px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.auth-modal-close{position:absolute;top:16px;right:16px;width:32px;height:32px;padding:0;background:transparent;border:none;border-radius:50%;color:var(--text-secondary);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.auth-modal-close:hover{background:var(--surface-hover);color:var(--text-primary)}.auth-modal-close svg{width:20px;height:20px}.auth-modal-header{padding:32px 32px 24px;text-align:center;border-bottom:1px solid var(--border)}.auth-modal-icon{width:56px;height:56px;margin:0 auto 16px;background:var(--accent);border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff}.auth-modal-icon svg{width:28px;height:28px}.auth-modal-header h2{font-size:20px;font-weight:700;color:var(--text-primary);margin-bottom:8px}.auth-modal-header p{font-size:14px;color:var(--text-secondary)}.auth-modal-body{padding:24px 32px;text-align:center}.auth-code{font-size:32px;font-weight:700;font-family:SF Mono,Monaco,Cascadia Code,monospace;letter-spacing:4px;color:var(--text-primary);background:var(--surface-hover);padding:16px 24px;border-radius:12px;margin-bottom:20px;-webkit-user-select:all;-moz-user-select:all;user-select:all}.auth-verify-btn{display:inline-flex;align-items:center;gap:8px;padding:12px 24px;background:var(--accent);color:#fff;text-decoration:none;border-radius:24px;font-size:14px;font-weight:600;transition:all .2s;margin-bottom:20px}.auth-verify-btn:hover{opacity:.9;transform:scale(1.02)}.auth-verify-btn svg{width:16px;height:16px}.auth-modal-status{display:flex;align-items:center;justify-content:center;gap:10px;font-size:13px;color:var(--text-secondary)}.auth-spinner{width:16px;height:16px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.auth-modal-footer{padding:16px 32px 24px;display:flex;gap:12px;justify-content:center}.auth-cancel-btn{padding:10px 24px;background:transparent;border:1px solid var(--border);border-radius:24px;color:var(--text-secondary);font-size:14px;cursor:pointer;transition:all .2s}.auth-cancel-btn:hover{background:var(--surface-hover);color:var(--text-primary)}.auth-retry-btn{padding:10px 24px;background:var(--accent);border:none;border-radius:24px;color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s}.auth-retry-btn:hover{opacity:.9}.auth-modal-icon-error{background:var(--red)}