@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.
@@ -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}
@@ -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-BIIwVDXB.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-B4yfgYFQ.css">
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.2",
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.en.md",
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
- ![Change calendar](docs/screenshots/en/calendar.png)
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
- ![Lines of code](docs/screenshots/en/loc.png)
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}