@h5l0/codelens 0.1.2 → 0.1.4
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.
- package/README.md +98 -85
- package/README.zh.md +151 -0
- package/dist/cli/index.js +10 -6
- package/dist/core/profile.js +54 -31
- package/dist/web/assets/index-CRwiLphE.js +9 -0
- package/dist/web/assets/index-iMj81ozS.css +1 -0
- package/dist/web/index.html +2 -2
- package/docs/screenshots/en/calendar.png +0 -0
- package/docs/screenshots/en/loc.png +0 -0
- package/docs/screenshots/zh/calendar.png +0 -0
- package/docs/screenshots/zh/loc.png +0 -0
- package/package.json +2 -2
- package/README.en.md +0 -137
- package/dist/web/assets/index-B4yfgYFQ.css +0 -1
- package/dist/web/assets/index-BIIwVDXB.js +0 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--cw: 36px;--ch: 26px;--gap: 4px;--ink: #1a1f27;--sub: #5b6470;--muted: #676f7b;--line: #e8ecf0;--cell-bg: #dde3e9;--cell-out-bg: #edf0f3;--green: #2b8f4f;--red: #d4513f;--accent: #2f63c8;--track: #848e9a}*{box-sizing:border-box}body{font-family:-apple-system,Segoe UI,PingFang SC,Microsoft YaHei,sans-serif;margin:0;color:var(--ink);background:#f7f8fa;-webkit-font-smoothing:antialiased}header{position:sticky;top:0;z-index:20;background:#ffffffd9;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}.header-inner{max-width:980px;margin:0 auto;padding:16px 28px;display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}h1{font-size:18px;margin:0;font-weight:650;letter-spacing:.2px}h1 .range{font-size:12px;font-weight:400;color:var(--muted);margin-left:10px}.header-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.seg{position:relative;display:flex;background:#edf0f3;border-radius:999px;padding:3px}.seg[hidden]{display:none}.seg-pill{position:absolute;top:3px;bottom:3px;border-radius:999px;background:#fff;box-shadow:0 1px 3px #10182824;transition:left .22s cubic-bezier(.4,0,.2,1),width .22s cubic-bezier(.4,0,.2,1)}.seg button{position:relative;z-index:1;border:0;background:transparent;padding:6px 16px;font-size:13px;color:var(--sub);cursor:pointer;border-radius:999px;font-family:inherit;transition:color .18s}.seg button.active{color:var(--ink);font-weight:600}main{max-width:980px;margin:0 auto;padding:24px 28px 90px}.stats{display:grid;gap:14px;margin-top:18px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr))}.stat{background:#fff;border:1px solid var(--line);border-radius:12px;padding:14px 18px;min-width:0;transition:border-color .15s,background .15s}.stats.live .stat{border-color:#c3d2ee;background:#fbfcff}.stat .k{font-size:12px;color:var(--muted)}.stat .v{font-size:21px;font-weight:650;margin-top:4px;font-variant-numeric:tabular-nums;white-space:nowrap}.stat .s{font-size:11px;color:var(--muted);margin-top:3px}.stats.live .stat .s{visibility:hidden}.stat .v .plus{color:var(--green)}.stat .v .minus{color:var(--red)}.commits-card{background:#fff;border:1px solid var(--line);border-radius:12px;margin-top:14px;overflow:hidden}.commits-head{font-size:12px;color:var(--muted);padding:10px 18px;border-bottom:1px solid #f0f2f5}.commits-head.live{color:var(--accent);font-weight:600}.commit-list{height:168px;overflow-y:auto}.commit{display:flex;align-items:baseline;gap:12px;padding:7px 18px;font-size:13px}.commit+.commit{border-top:1px solid #f6f8fa}.commit .hash{font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:12px;color:var(--accent);flex-shrink:0}.commit .subj{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ink)}.commit .nums{flex-shrink:0;font-size:12px;font-variant-numeric:tabular-nums}.commit .nums .plus{color:var(--green)}.commit .nums .minus{color:var(--red)}.commit-empty{padding:14px 18px;font-size:13px;color:var(--muted)}.card{background:#fff;border:1px solid var(--line);border-radius:14px;padding:24px 26px 18px;box-shadow:0 1px 2px #10182808,0 6px 24px #1018280d}.cal-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;font-size:12px;color:var(--sub);flex-wrap:wrap}.cal-head .cal-range{color:var(--ink);font-weight:650;font-variant-numeric:tabular-nums}.cal-head .cal-legend{display:flex;align-items:center;gap:18px}.calendar-wrap{display:flex;align-items:flex-start;gap:10px}.dow-labels{display:flex;flex-direction:column;gap:var(--gap);font-size:10px;color:var(--muted);padding-top:24px}.dow-labels span{height:var(--ch);line-height:var(--ch)}.grid-area{flex:1;min-width:0;padding-bottom:4px}.grid-area>.month-row,.grid-area>.grid{width:fit-content;margin-left:auto}.month-row{display:grid;gap:var(--gap);font-size:11px;color:var(--sub);height:20px;margin-bottom:4px}.month-label{white-space:nowrap}.grid{display:flex;flex-direction:column;gap:var(--gap)}.row{display:flex;gap:var(--gap)}.cell{width:var(--cw);height:var(--ch);border:0;padding:0;font:inherit;border-radius:5px;background:var(--cell-bg);display:flex;flex-direction:column;align-items:flex-start;overflow:hidden;cursor:pointer;transition:transform .08s ease,box-shadow .08s ease,opacity .18s ease}.cell.pad{background:transparent;cursor:default}.cell.out{background:var(--cell-out-bg);cursor:default}.cell.today{box-shadow:inset 0 0 0 1.5px #1a1f2766}.cell.selected{box-shadow:inset 0 0 0 1.5px var(--accent)}.cell:not(.pad):not(.out):hover{transform:scale(1.3);box-shadow:0 3px 10px #10182833;z-index:5}.cell:focus-visible{outline:2px solid var(--accent);outline-offset:2px;z-index:6}@media(prefers-reduced-motion:reduce){.cell{transition:none}.cell:not(.pad):hover{transform:none}}.hbar{display:block;height:50%}.hbar.add{background:var(--green)}.hbar.del{background:var(--red)}.cell.dim:not(.pad){opacity:.35}.legend-item{display:flex;align-items:center;gap:7px}.sw{display:inline-block;width:12px;height:12px}.scrub{margin-top:16px;padding-top:12px;border-top:1px solid #f0f2f5}.scrub-track{position:relative;display:flex;width:fit-content;margin-left:auto;cursor:grab;border-radius:3px}.scrub.dragging .scrub-track{cursor:grabbing}.scrub-track:focus-visible{outline:2px solid var(--accent);outline-offset:3px}.scrub-cells{display:flex;gap:2px;height:20px;-webkit-mask-image:linear-gradient(90deg,transparent,#000 min(64px,50%),#000 calc(100% - min(64px,50%)),transparent);mask-image:linear-gradient(90deg,transparent,#000 min(64px,50%),#000 calc(100% - min(64px,50%)),transparent)}.scrub-cells .sc{flex:0 0 auto;width:5px;height:20px;border-radius:1.5px;background:#e6eaee;overflow:hidden}.scrub-cells .sc.void{background:transparent}.scrub-cells .sc i{display:block;width:100%;height:100%;background:var(--green)}.scrub-frame{position:absolute;top:-2px;bottom:-2px;border:1.5px solid var(--accent);border-radius:4px;background:#2f63c812;pointer-events:none}.load-error{max-width:980px;margin:60px auto;padding:0 28px;font-size:14px;color:var(--sub);line-height:2}.load-error code{background:#edf0f3;border-radius:5px;padding:2px 8px;font-family:ui-monospace,Consolas,monospace;font-size:13px}.load-error button{display:block;margin-top:12px;font:inherit;font-size:13px;color:var(--accent);background:#fff;border:1px solid var(--line);border-radius:8px;padding:6px 14px;cursor:pointer;transition:background .15s}.load-error button:hover{background:#f4f6f8}.loc-bar{display:flex;align-items:center;justify-content:space-between;gap:10px 22px;flex-wrap:wrap;margin:2px 4px 12px}.loc-summary{font-size:13px;color:var(--sub)}.loc-summary b{color:var(--ink);font-variant-numeric:tabular-nums}.loc-summary .total{color:var(--muted);font-variant-numeric:tabular-nums}.loc-switches{display:flex;align-items:center;flex-wrap:wrap;gap:10px 20px;margin-left:auto}.sw-item{position:relative;display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--sub);cursor:pointer;-webkit-user-select:none;user-select:none;transition:color .15s}.sw-item input{position:absolute;opacity:0;width:0;height:0}.sw-item .track{position:relative;width:32px;height:18px;border-radius:999px;background:var(--track);transition:background .18s;flex-shrink:0}.sw-item .track:after{content:"";position:absolute;left:2px;top:2px;width:14px;height:14px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #10182833;transition:transform .18s}.sw-item input:checked+.track{background:var(--accent)}.sw-item input:checked+.track:after{transform:translate(14px)}.sw-item input:focus-visible+.track{outline:2px solid #b9cdf3;outline-offset:2px}.sw-item:has(input:checked){color:var(--ink)}.tm-card{padding:0;overflow:hidden}.tm-crumbs{display:flex;align-items:center;gap:1px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid #f0f2f5}.tm-crumbs .crumb{border:0;background:transparent;font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:12px;color:var(--accent);cursor:pointer;padding:2px 6px;border-radius:6px;transition:background .15s}.tm-crumbs button.crumb:hover{background:#eef3fd}.tm-crumbs .crumb.current{color:var(--ink);font-weight:600;cursor:default;padding:2px 4px}.tm-crumbs .crumb.preview{color:var(--muted);cursor:default;padding:2px 4px}.tm-crumbs .crumb-sep{color:var(--muted);font-size:12px}.tm-crumbs .crumbs-meta{margin-left:auto;color:var(--muted);font-size:12px;font-variant-numeric:tabular-nums}.tm-wrap{position:relative;height:min(70vh,720px);min-height:420px;background:#f4f6f8;overflow:hidden}.tm-stage{position:absolute;inset:0}.tm-stage.entering{transition:transform .3s cubic-bezier(.22,.61,.36,1),opacity .18s ease-out}.tm-stage.leaving{transition:opacity .24s ease-out;opacity:0;pointer-events:none}@media(prefers-reduced-motion:reduce){.tm-stage.entering,.tm-stage.leaving{transition:none}}.tm-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--muted)}.tm-node{position:absolute;overflow:hidden;border:1px solid rgba(255,255,255,.8);background:var(--c);color:var(--fg);cursor:pointer}.tm-node.is-file{cursor:default}.tm-node.hot{filter:brightness(1.14);z-index:12;box-shadow:0 0 0 1.5px #10182859 inset}.tm-node:focus-visible{outline:0;box-shadow:inset 0 0 0 2px #fff,inset 0 0 0 5px #0009;z-index:13}.tm-head{position:absolute;left:0;right:0;top:0;height:15px;display:flex;align-items:center;gap:6px;padding:0 4px;font-size:10.5px;line-height:15px;white-space:nowrap;overflow:hidden;pointer-events:none}.tm-name{overflow:hidden;text-overflow:ellipsis}.tm-val{margin-left:auto;flex-shrink:0;font-variant-numeric:tabular-nums}.tm-body{position:absolute;left:0;right:0;bottom:0}.tm-badge{position:absolute;left:5px;right:5px;bottom:4px;font-size:10.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none}.tm-legend{display:flex;align-items:center;flex-wrap:wrap;gap:8px 16px;padding:12px 18px;border-top:1px solid #f0f2f5}.tm-legend .lg{display:inline-flex;align-items:center;gap:7px;border:0;background:transparent;font-family:inherit;font-size:12px;color:var(--sub);cursor:pointer;padding:2px 6px;border-radius:6px;transition:background .15s,color .15s}.tm-legend .lg:hover{background:#f4f6f8}.tm-legend .lg.off{color:var(--muted)}.tm-legend .sq{width:11px;height:11px;border-radius:3px;background:var(--c);flex-shrink:0}.tm-legend .lg.off .sq{background:transparent;box-shadow:inset 0 0 0 2px var(--c)}.tm-legend .lg-val{color:var(--muted);font-variant-numeric:tabular-nums}
|
package/dist/web/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<!-- 语言与标题由页面按浏览器语言设置,这里只给出脚本运行前的默认值 -->
|
|
7
7
|
<title>codelens</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CRwiLphE.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-iMj81ozS.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h5l0/codelens",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "把任意 git 仓库的改动历史与代码行数可视化成本地看板 | Local dashboard for a git repository's commit history and lines of code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dist",
|
|
32
32
|
"docs/screenshots",
|
|
33
33
|
"README.md",
|
|
34
|
-
"README.
|
|
34
|
+
"README.zh.md",
|
|
35
35
|
"LICENSE"
|
|
36
36
|
],
|
|
37
37
|
"engines": {
|
package/README.en.md
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# codelens
|
|
2
|
-
|
|
3
|
-
A local dashboard for a git repository: the change history and the lines of code.
|
|
4
|
-
|
|
5
|
-
English | [简体中文](./README.md)
|
|
6
|
-
|
|
7
|
-
## Quick start
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npx @h5l0/codelens # analyze the current directory and open the browser
|
|
11
|
-
npx @h5l0/codelens ../my-repo # analyze another repository
|
|
12
|
-
npx @h5l0/codelens --profile web # split the calendar into frontend / backend
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Or install it globally:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install -g @h5l0/codelens
|
|
19
|
-
codelens
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
No configuration is necessary. The tool collects the data at startup and serves it to the local browser only. It does not write to the analyzed repository.
|
|
23
|
-
|
|
24
|
-
## The two views
|
|
25
|
-
|
|
26
|
-
### Change calendar
|
|
27
|
-
|
|
28
|
-
A weekly heat map of the changes: each cell is one day and shows the lines added and removed. You can filter by group, or move the time window to see other weeks.
|
|
29
|
-
|
|
30
|
-

|
|
31
|
-
|
|
32
|
-
### Lines of code
|
|
33
|
-
|
|
34
|
-
A treemap: the area of a rectangle is the line count, the color is the category, and the shade is the directory depth. Click a rectangle to open that directory, or use the switches to set the counting mode and the depth.
|
|
35
|
-
|
|
36
|
-

|
|
37
|
-
|
|
38
|
-
## Command line
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
codelens [directory] [options]
|
|
42
|
-
|
|
43
|
-
--profile <name|file> Profile, see below; built in: all, web
|
|
44
|
-
--config <file> Config file, defaults to <directory>/codelens.config.json
|
|
45
|
-
--days <days> Calendar time span, 0 means full history (default 120)
|
|
46
|
-
--exclude <glob> Extra paths to ignore, repeatable
|
|
47
|
-
--port <port> Listen port, default 5178, tries the next ports when busy
|
|
48
|
-
--host <address> Listen address, default 127.0.0.1; other addresses expose the page to your network
|
|
49
|
-
--no-open Do not open the browser automatically
|
|
50
|
-
--no-gitignore Ignore .gitignore; only built-in heavy directories are skipped
|
|
51
|
-
--dump <dir> Write data.json and loc.json, then exit without starting a server
|
|
52
|
-
--dev Dev mode with Vite hot reload
|
|
53
|
-
-h, --help Show help
|
|
54
|
-
-v, --version Show version
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Profiles
|
|
58
|
-
|
|
59
|
-
`--profile` sets how the tool splits the repository:
|
|
60
|
-
|
|
61
|
-
1. Point it at a JSON file: `--profile ./my-profile.json`;
|
|
62
|
-
2. Or use a name under `profiles` in `codelens.config.json`. Use `--config` to move that file: `--profile web`.
|
|
63
|
-
|
|
64
|
-
Two profiles are built in:
|
|
65
|
-
|
|
66
|
-
| Name | What it does |
|
|
67
|
-
| --- | --- |
|
|
68
|
-
| `all` | Default; no groups, the tool counts the whole repository together |
|
|
69
|
-
| `web` | `frontend/`, `web/`, `client/`, `ui/` and similar directories count as frontend, everything else as backend |
|
|
70
|
-
|
|
71
|
-
### Config file format
|
|
72
|
-
|
|
73
|
-
```jsonc
|
|
74
|
-
{
|
|
75
|
-
"profiles": {
|
|
76
|
-
"modules": {
|
|
77
|
-
"label": "By module",
|
|
78
|
-
// Calendar groups: a file goes to the first group whose patterns match.
|
|
79
|
-
// End the list with ["**"] to get an "A / everything else" split.
|
|
80
|
-
"groups": [
|
|
81
|
-
{ "id": "core", "label": "Core", "hue": 214, "sat": 58, "match": ["src/core/**"] },
|
|
82
|
-
{ "id": "web", "label": "UI", "hue": 152, "sat": 46, "match": ["src/web/**"] },
|
|
83
|
-
{ "id": "other", "label": "Other", "hue": 32, "sat": 62, "match": ["**"] }
|
|
84
|
-
],
|
|
85
|
-
// Line-count categories: drive the legend and colors; the entry without a match is the fallback.
|
|
86
|
-
"categories": [
|
|
87
|
-
{ "id": "core", "label": "Core code", "hue": 214, "sat": 58, "match": ["src/core/**"] },
|
|
88
|
-
{ "id": "test", "label": "Tests", "hue": 152, "sat": 46, "defaultOn": false, "match": ["**/*.test.ts"] },
|
|
89
|
-
{ "id": "app", "label": "Other code", "hue": 220, "sat": 20 }
|
|
90
|
-
],
|
|
91
|
-
// Extra paths to ignore on top of .gitignore
|
|
92
|
-
"ignore": ["data/**", "**/*.snap"]
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Field notes:
|
|
99
|
-
|
|
100
|
-
- `groups[].match`, `categories[].match` and `ignore` use globs relative to the repository root: `**` crosses directories, `*` does not, `?` matches one character, and `{a,b}` matches either. A pattern without `/` matches items of that name at any depth. `/foo` is anchored to the root. `foo/` means the directory and everything inside it.
|
|
101
|
-
- The config file accepts `//` and `/* */` comments and trailing commas.
|
|
102
|
-
- `groups[].id` must not be `all`, and it must be unique in one profile.
|
|
103
|
-
- `hue` and `sat` are HSL color components for the group and category colors. The defaults are 214 and 50.
|
|
104
|
-
- `categories[].defaultOn: false` means the category starts off in the legend. Of the built-in categories, "generated code", "docs" and "config" start off.
|
|
105
|
-
|
|
106
|
-
Without `categories`, the tool uses six built-in categories: application code, tests, scripts, docs, config and generated code.
|
|
107
|
-
|
|
108
|
-
A profile name that the config file does not define falls back to the built-in profile. A repository with a `codelens.config.json` that defines only custom profiles still supports `codelens --profile all` and `--profile web`. A name that is missing from both places is an error.
|
|
109
|
-
|
|
110
|
-
## Counting rules
|
|
111
|
-
|
|
112
|
-
- The tool honors `.gitignore` by default. If the directory is not a git repository, the tool applies equivalent ignore rules instead. `--no-gitignore` turns this off.
|
|
113
|
-
- The tool always skips dependency and build directories, such as `node_modules`, `dist`, `build`, `coverage`, `.venv`, `__pycache__` and `target`.
|
|
114
|
-
- `--exclude` and the config's `ignore` apply to both views. The tool does not count excluded directories, and the calendar does not show them.
|
|
115
|
-
- The tool skips binary files, files larger than 3MB and empty files. If the tool cannot read a file, it skips that file and reports the count in the startup log. Line counts are physical lines; the newline at the end of a file does not count as one more line.
|
|
116
|
-
- The calendar puts each commit in the day of its commit time (committer date). This matches the `--days` filter. "Changed lines" means insertions plus deletions.
|
|
117
|
-
- Merge commits, empty commits and commits that only change file modes or binaries have no line counts. They still appear in the commit list and count as commits.
|
|
118
|
-
- If you run the tool on a subdirectory of a repository, both views count that subdirectory only and resolve paths relative to it.
|
|
119
|
-
|
|
120
|
-
## Known limits
|
|
121
|
-
|
|
122
|
-
- The change calendar needs git. A repository without commits, or without git, leaves the calendar empty. The lines view still works.
|
|
123
|
-
- Line counts do not show the code complexity.
|
|
124
|
-
- The treemap draws at most 6000 rectangles. The rest are not shown.
|
|
125
|
-
- The calendar is padded to whole weeks. A window that does not start or end on a week boundary shows a few extra days without data, with a paler background.
|
|
126
|
-
|
|
127
|
-
## Languages
|
|
128
|
-
|
|
129
|
-
The page follows the browser language. Simplified Chinese, English, Japanese and Korean are built in. Other languages fall back to English. Append `?lang={langCode}` to the URL to override the language, for example `?lang=zh` or `?lang=ko`.
|
|
130
|
-
|
|
131
|
-
## Development
|
|
132
|
-
|
|
133
|
-
See [DEV.md](./DEV.md) for the development setup, the project layout and the release process.
|
|
134
|
-
|
|
135
|
-
## License
|
|
136
|
-
|
|
137
|
-
[MIT](./LICENSE)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--cw: 36px;--ch: 26px;--gap: 4px;--ink: #1a1f27;--sub: #5b6470;--muted: #676f7b;--line: #e8ecf0;--cell-bg: #edf0f3;--green: #2b8f4f;--red: #d4513f;--accent: #2f63c8;--track: #848e9a}*{box-sizing:border-box}body{font-family:-apple-system,Segoe UI,PingFang SC,Microsoft YaHei,sans-serif;margin:0;color:var(--ink);background:#f7f8fa;-webkit-font-smoothing:antialiased}header{position:sticky;top:0;z-index:20;background:#ffffffd9;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}.header-inner{max-width:980px;margin:0 auto;padding:16px 28px;display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}h1{font-size:18px;margin:0;font-weight:650;letter-spacing:.2px}h1 .range{font-size:12px;font-weight:400;color:var(--muted);margin-left:10px}.header-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.seg{position:relative;display:flex;background:#edf0f3;border-radius:999px;padding:3px}.seg[hidden]{display:none}.seg-pill{position:absolute;top:3px;bottom:3px;border-radius:999px;background:#fff;box-shadow:0 1px 3px #10182824;transition:left .22s cubic-bezier(.4,0,.2,1),width .22s cubic-bezier(.4,0,.2,1)}.seg button{position:relative;z-index:1;border:0;background:transparent;padding:6px 16px;font-size:13px;color:var(--sub);cursor:pointer;border-radius:999px;font-family:inherit;transition:color .18s}.seg button.active{color:var(--ink);font-weight:600}main{max-width:980px;margin:0 auto;padding:24px 28px 90px}.stats{display:grid;gap:14px;margin-top:18px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr))}.stat{background:#fff;border:1px solid var(--line);border-radius:12px;padding:14px 18px;min-width:0;transition:border-color .15s,background .15s}.stats.live .stat{border-color:#c3d2ee;background:#fbfcff}.stat .k{font-size:12px;color:var(--muted)}.stat .v{font-size:21px;font-weight:650;margin-top:4px;font-variant-numeric:tabular-nums;white-space:nowrap}.stat .s{font-size:11px;color:var(--muted);margin-top:3px}.stats.live .stat .s{visibility:hidden}.stat .v .plus{color:var(--green)}.stat .v .minus{color:var(--red)}.commits-card{background:#fff;border:1px solid var(--line);border-radius:12px;margin-top:14px;overflow:hidden}.commits-head{font-size:12px;color:var(--muted);padding:10px 18px;border-bottom:1px solid #f0f2f5}.commits-head.live{color:var(--accent);font-weight:600}.commit-list{height:168px;overflow-y:auto}.commit{display:flex;align-items:baseline;gap:12px;padding:7px 18px;font-size:13px}.commit+.commit{border-top:1px solid #f6f8fa}.commit .hash{font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:12px;color:var(--accent);flex-shrink:0}.commit .subj{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ink)}.commit .nums{flex-shrink:0;font-size:12px;font-variant-numeric:tabular-nums}.commit .nums .plus{color:var(--green)}.commit .nums .minus{color:var(--red)}.commit-empty{padding:14px 18px;font-size:13px;color:var(--muted)}.card{background:#fff;border:1px solid var(--line);border-radius:14px;padding:24px 26px 18px;box-shadow:0 1px 2px #10182808,0 6px 24px #1018280d}.cal-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;font-size:12px;color:var(--sub);flex-wrap:wrap}.cal-head .cal-range{color:var(--ink);font-weight:650;font-variant-numeric:tabular-nums}.cal-head .cal-legend{display:flex;align-items:center;gap:18px}.calendar-wrap{display:flex;align-items:flex-start;gap:10px}.dow-labels{display:flex;flex-direction:column;gap:var(--gap);font-size:10px;color:var(--muted);padding-top:24px}.dow-labels span{height:var(--ch);line-height:var(--ch)}.grid-area{flex:1;overflow:hidden;padding-bottom:4px}.grid-area>.month-row,.grid-area>.grid{width:fit-content;margin-left:auto}.month-row{display:grid;gap:var(--gap);font-size:11px;color:var(--sub);height:20px;margin-bottom:4px}.month-label{white-space:nowrap}.grid{display:flex;flex-direction:column;gap:var(--gap)}.row{display:flex;gap:var(--gap)}.cell{width:var(--cw);height:var(--ch);border:0;padding:0;font:inherit;border-radius:5px;background:var(--cell-bg);display:flex;flex-direction:column;align-items:flex-start;overflow:hidden;cursor:pointer;transition:transform .08s ease,box-shadow .08s ease,opacity .18s ease}.cell.pad{background:transparent;cursor:default}.cell.out{background:#f8fafb}.cell.today{box-shadow:inset 0 0 0 1.5px #1a1f2766}.cell.selected{box-shadow:inset 0 0 0 1.5px var(--accent)}.cell:not(.pad):hover{transform:scale(1.3);box-shadow:0 3px 10px #10182833;z-index:5}.cell:focus-visible{outline:2px solid var(--accent);outline-offset:2px;z-index:6}@media(prefers-reduced-motion:reduce){.cell{transition:none}.cell:not(.pad):hover{transform:none}}.hbar{display:block;height:50%}.hbar.add{background:var(--green)}.hbar.del{background:var(--red)}.cell.dim:not(.pad){opacity:.35}.legend-item{display:flex;align-items:center;gap:7px}.sw{display:inline-block;width:12px;height:12px}.scrub{margin-top:16px;padding-top:12px;border-top:1px solid #f0f2f5}.scrub-track{position:relative;display:flex;width:fit-content;margin-left:auto;cursor:grab;border-radius:3px}.scrub.dragging .scrub-track{cursor:grabbing}.scrub-track:focus-visible{outline:2px solid var(--accent);outline-offset:3px}.scrub-cells{display:flex;gap:2px;height:20px;-webkit-mask-image:linear-gradient(90deg,transparent,#000 min(64px,50%),#000 calc(100% - min(64px,50%)),transparent);mask-image:linear-gradient(90deg,transparent,#000 min(64px,50%),#000 calc(100% - min(64px,50%)),transparent)}.scrub-cells .sc{flex:0 0 auto;width:5px;height:20px;border-radius:1.5px;background:#e6eaee;overflow:hidden}.scrub-cells .sc.void{background:transparent}.scrub-cells .sc i{display:block;width:100%;height:100%;background:var(--green)}.scrub-frame{position:absolute;top:-2px;bottom:-2px;border:1.5px solid var(--accent);border-radius:4px;background:#2f63c812;pointer-events:none}.load-error{max-width:980px;margin:60px auto;padding:0 28px;font-size:14px;color:var(--sub);line-height:2}.load-error code{background:#edf0f3;border-radius:5px;padding:2px 8px;font-family:ui-monospace,Consolas,monospace;font-size:13px}.load-error button{display:block;margin-top:12px;font:inherit;font-size:13px;color:var(--accent);background:#fff;border:1px solid var(--line);border-radius:8px;padding:6px 14px;cursor:pointer;transition:background .15s}.load-error button:hover{background:#f4f6f8}.loc-bar{display:flex;align-items:center;justify-content:space-between;gap:10px 22px;flex-wrap:wrap;margin:2px 4px 12px}.loc-summary{font-size:13px;color:var(--sub)}.loc-summary b{color:var(--ink);font-variant-numeric:tabular-nums}.loc-summary .total{color:var(--muted);font-variant-numeric:tabular-nums}.loc-switches{display:flex;align-items:center;flex-wrap:wrap;gap:10px 20px;margin-left:auto}.sw-item{position:relative;display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--sub);cursor:pointer;-webkit-user-select:none;user-select:none;transition:color .15s}.sw-item input{position:absolute;opacity:0;width:0;height:0}.sw-item .track{position:relative;width:32px;height:18px;border-radius:999px;background:var(--track);transition:background .18s;flex-shrink:0}.sw-item .track:after{content:"";position:absolute;left:2px;top:2px;width:14px;height:14px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #10182833;transition:transform .18s}.sw-item input:checked+.track{background:var(--accent)}.sw-item input:checked+.track:after{transform:translate(14px)}.sw-item input:focus-visible+.track{outline:2px solid #b9cdf3;outline-offset:2px}.sw-item:has(input:checked){color:var(--ink)}.tm-card{padding:0;overflow:hidden}.tm-crumbs{display:flex;align-items:center;gap:1px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid #f0f2f5}.tm-crumbs .crumb{border:0;background:transparent;font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:12px;color:var(--accent);cursor:pointer;padding:2px 6px;border-radius:6px;transition:background .15s}.tm-crumbs button.crumb:hover{background:#eef3fd}.tm-crumbs .crumb.current{color:var(--ink);font-weight:600;cursor:default;padding:2px 4px}.tm-crumbs .crumb.preview{color:var(--muted);cursor:default;padding:2px 4px}.tm-crumbs .crumb-sep{color:var(--muted);font-size:12px}.tm-crumbs .crumbs-meta{margin-left:auto;color:var(--muted);font-size:12px;font-variant-numeric:tabular-nums}.tm-wrap{position:relative;height:min(70vh,720px);min-height:420px;background:#f4f6f8;overflow:hidden}.tm-stage{position:absolute;inset:0}.tm-stage.entering{transition:transform .3s cubic-bezier(.22,.61,.36,1),opacity .18s ease-out}.tm-stage.leaving{transition:opacity .24s ease-out;opacity:0;pointer-events:none}@media(prefers-reduced-motion:reduce){.tm-stage.entering,.tm-stage.leaving{transition:none}}.tm-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--muted)}.tm-node{position:absolute;overflow:hidden;border:1px solid rgba(255,255,255,.8);background:var(--c);color:var(--fg);cursor:pointer}.tm-node.is-file{cursor:default}.tm-node.hot{filter:brightness(1.14);z-index:12;box-shadow:0 0 0 1.5px #10182859 inset}.tm-node:focus-visible{outline:0;box-shadow:inset 0 0 0 2px #fff,inset 0 0 0 5px #0009;z-index:13}.tm-head{position:absolute;left:0;right:0;top:0;height:15px;display:flex;align-items:center;gap:6px;padding:0 4px;font-size:10.5px;line-height:15px;white-space:nowrap;overflow:hidden;pointer-events:none}.tm-name{overflow:hidden;text-overflow:ellipsis}.tm-val{margin-left:auto;flex-shrink:0;font-variant-numeric:tabular-nums}.tm-body{position:absolute;left:0;right:0;bottom:0}.tm-badge{position:absolute;left:5px;right:5px;bottom:4px;font-size:10.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none}.tm-legend{display:flex;align-items:center;flex-wrap:wrap;gap:8px 16px;padding:12px 18px;border-top:1px solid #f0f2f5}.tm-legend .lg{display:inline-flex;align-items:center;gap:7px;border:0;background:transparent;font-family:inherit;font-size:12px;color:var(--sub);cursor:pointer;padding:2px 6px;border-radius:6px;transition:background .15s,color .15s}.tm-legend .lg:hover{background:#f4f6f8}.tm-legend .lg.off{color:var(--muted)}.tm-legend .sq{width:11px;height:11px;border-radius:3px;background:var(--c);flex-shrink:0}.tm-legend .lg.off .sq{background:transparent;box-shadow:inset 0 0 0 2px var(--c)}.tm-legend .lg-val{color:var(--muted);font-variant-numeric:tabular-nums}
|