website

#astro#js#html#css

git clone https://git.pyrossh.dev/website

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


3931c4dpyrossh 2026-08-04T21:50:17+05:30
improve stuff
assets/css/repos.css CHANGED
@@ -8,7 +8,7 @@
8
8
  values, so nothing reads as randomly bigger/smaller than its neighbour:
9
9
  0.85rem — small badges (tags, pull-right meta, file-size hints)
10
10
  0.9rem — code/monospace content, nav links and sub-headings alike
11
- (pre/code-view, file tree, blame, h3, file title)
11
+ (pre/code-view, file tree, blame, file title)
12
12
  0.95rem — box body text (.repo, .event)
13
13
  var(--fs-repo-title) — page-level heading (.repo h1), fluid 1.3–1.6rem
14
14
  All of them are rem-based, not em, so nesting depth can't change the
@@ -70,8 +70,15 @@
70
70
  }
71
71
 
72
72
  h3 {
73
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
73
- font-size: 1rem;
74
+ font-size: 0.85rem;
74
- font-weight: 500;
75
+ font-weight: 400;
76
+ margin-top: 0.5rem;
77
+ padding: 0.5rem 0.75rem;
78
+ background: var(--color-pre-bg);
79
+ border-radius: 4px;
80
+ overflow-x: auto;
81
+ white-space: pre;
75
82
  }
76
83
 
77
84
  .headerExtension h2 {
@@ -95,16 +102,22 @@
95
102
  margin-top: 0.5rem;
96
103
 
97
104
  span {
105
+ padding: 0 0.5rem;
106
+ color: var(--color-text);
98
- display: none;
107
+ opacity: 0.4;
99
108
  }
100
109
 
101
110
  a {
102
111
  font-size: 1rem;
103
112
  font-weight: 500;
104
- padding: 0 0.5rem 0 0;
105
113
  cursor: pointer;
106
114
  color: var(--color-link);
115
+ text-decoration: none;
116
+
117
+ &.active-tab {
107
- text-decoration: underline;
118
+ text-decoration: underline;
119
+ text-underline-offset: 4px;
120
+ }
108
121
  }
109
122
  }
110
123
  }
@@ -161,7 +174,7 @@
161
174
  .file-explorer {
162
175
  display: flex;
163
176
  align-items: stretch;
164
- gap: 1.25rem;
177
+ gap: 0.5rem;
165
178
  height: 80vh;
166
179
 
167
180
  /* File tree: flat rows (no per-item card background), a rotating
@@ -177,7 +190,6 @@
177
190
  background: var(--color-box-bg);
178
191
  border-radius: 6px;
179
192
  font-size: 0.9rem;
180
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
181
193
 
182
194
  ul {
183
195
  list-style: none;
@@ -352,7 +364,6 @@
352
364
  min-width: 0;
353
365
  font-size: 0.9rem;
354
366
  font-weight: 500;
355
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
356
367
 
357
368
  svg {
358
369
  flex-shrink: 0;
assets/css/shared.css CHANGED
@@ -74,8 +74,8 @@ table { border-collapse: collapse; }
74
74
  --color-post-link: light-dark(hsl(0, 0%, 0%), hsl(211, 100%, 60%));
75
75
  --color-highlight-bg: light-dark(hsl(183, 80%, 88%), hsl(183, 80%, 18%));
76
76
  --color-code-fg: light-dark(hsl(348, 86%, 43%), hsl(348, 86%, 80%));
77
- --color-code-bg: light-dark(hsl(0, 0%, 95%), hsl(0, 0%, 0%));
77
+ --color-code-bg: light-dark(hsl(0, 0%, 95%), hsl(210, 13%, 6%));
78
- --color-pre-bg: light-dark(hsl(0, 0%, 95%), hsl(0, 0%, 0%));
78
+ --color-pre-bg: light-dark(hsl(0, 0%, 95%), hsl(210, 13%, 6%));
79
79
  --color-tag: light-dark(hsl(152, 96%, 35%), hsl(152, 96%, 44%));
80
80
  --token-comment: light-dark(hsl(220, 10%, 45%), hsl(220, 10%, 60%));
81
81
  --token-keyword: light-dark(hsl(310, 60%, 45%), hsl(310, 70%, 72%));
eleventy.config.js CHANGED
@@ -2,7 +2,7 @@ import { readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
4
4
  import pluginRss from "@11ty/eleventy-plugin-rss";
5
- import { fileIcon, folderIcon } from "./src/_helpers/fileIcons.js";
5
+ import { fileIcon, folderIcon, iconSpriteDefs } from "./src/_helpers/fileIcons.js";
6
6
 
7
7
  // theme-switcher (src/components/theme-switcher.js) used to be a Web
8
8
  // Component (via @elenajs/core + an @elenajs/ssr build-time transform
@@ -71,6 +71,15 @@ export default function (eleventyConfig) {
71
71
  );
72
72
  eleventyConfig.addFilter("fileIcon", fileIcon);
73
73
  eleventyConfig.addFilter("folderIcon", folderIcon);
74
+ eleventyConfig.addNunjucksGlobal("iconSpriteDefs", iconSpriteDefs);
75
+ // Nunjucks' built-in `selectattr` only checks truthiness of the named
76
+ // attribute — it has no Jinja2-style `equalto` test, so a third argument
77
+ // is silently ignored. These do the actual equality filtering in JS.
78
+ eleventyConfig.addFilter("filterByRepo", (entries, repoId) => entries.filter((e) => e.repoId === repoId));
79
+ eleventyConfig.addFilter("defaultFileEntry", (entries) => {
80
+ if (!entries.length) return null;
81
+ return entries.find((e) => e.file.path.toLowerCase() === "readme.md") ?? entries[entries.length - 1];
82
+ });
74
83
  // A folder should start open only when the active file lives inside it —
75
84
  // otherwise every directory in the tree defaults to expanded, which on a
76
85
  // repo of any size is just a wall of open folders with nothing collapsed.
src/_helpers/fileIcons.js CHANGED
@@ -67,17 +67,42 @@ const FOLDER_TO_ICON = {
67
67
  node_modules: "folder-type-node",
68
68
  };
69
69
 
70
+ // Every icon id this module can ever emit, deduped — used to build the
71
+ // sprite once rather than guessing which subset a given page needs.
72
+ const ICON_NAMES = new Set([
73
+ ...Object.values(FILENAME_TO_ICON),
74
+ ...Object.values(EXT_TO_ICON),
75
+ ...Object.values(FOLDER_TO_ICON),
76
+ "default-file",
77
+ "default-folder",
78
+ ]);
79
+
80
+ /**
81
+ * A hidden <svg><symbol> sprite holding every file/folder icon this repo
82
+ * browser can use, meant to be embedded once per page. fileIcon()/
83
+ * folderIcon() then emit a tiny `<use>` reference instead of repeating the
84
+ * full icon body on every row — for a repo with hundreds of files (e.g.
85
+ * many .rs files pulling in the same Rust icon), that's the difference
86
+ * between shipping one icon body and shipping it hundreds of times over.
87
+ */
70
- const svgFor = (name, fallback) => {
88
+ export const iconSpriteDefs = () => {
89
+ const symbols = [...ICON_NAMES]
90
+ .map((name) => {
71
- const icon = iconSet.icons[name] ?? iconSet.icons[fallback];
91
+ const icon = iconSet.icons[name];
92
+ return `<symbol id="icon-${name}" viewBox="0 0 ${iconSet.width} ${iconSet.height}">${icon.body}</symbol>`;
93
+ })
94
+ .join("");
72
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${iconSet.width} ${iconSet.height}" width="16" height="16">${icon.body}</svg>`;
95
+ return `<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">${symbols}</svg>`;
73
96
  };
74
97
 
98
+ const useIcon = (name) => `<svg width="16" height="16"><use href="#icon-${name}"></use></svg>`;
99
+
75
100
  export const fileIcon = (filename, ext) => {
76
- const key = FILENAME_TO_ICON[filename.toLowerCase()] ?? EXT_TO_ICON[ext] ?? null;
101
+ const key = FILENAME_TO_ICON[filename.toLowerCase()] ?? EXT_TO_ICON[ext] ?? "default-file";
77
- return svgFor(key, "default-file");
102
+ return useIcon(key);
78
103
  };
79
104
 
80
105
  export const folderIcon = (foldername) => {
81
- const key = FOLDER_TO_ICON[foldername.toLowerCase()] ?? null;
106
+ const key = FOLDER_TO_ICON[foldername.toLowerCase()] ?? "default-folder";
82
- return svgFor(key, "default-folder");
107
+ return useIcon(key);
83
108
  };
src/_includes/file-layout.njk CHANGED
@@ -11,3 +11,17 @@
11
11
  </div>
12
12
  </div>
13
13
  {% endmacro %}
14
+
15
+ {% macro fileContent(render, size, filepath) %}
16
+ <div id="file-content">
17
+ {% if render.kind == "binary" %}
18
+ <p>Binary file ({{ size }} bytes).</p>
19
+ {% elif render.kind == "large" %}
20
+ <p>File too large to display ({{ size }} bytes).</p>
21
+ {% elif render.kind == "image" %}
22
+ <img src="{{ render.src }}" alt="{{ filepath }}" />
23
+ {% else %}
24
+ <pre class="code-view" data-lang="{{ render.lang }}"><code class="language-{{ render.lang }}">{{ render.html | safe }}</code></pre>
25
+ {% endif %}
26
+ </div>
27
+ {% endmacro %}
src/_includes/repo-layout.njk CHANGED
@@ -1,7 +1,8 @@
1
1
  {% macro repoLayout(repo, title, desc, tags, tab) %}
2
+ {{ iconSpriteDefs() | safe }}
2
3
  <div class="repo">
3
4
  <div class="header">
4
- <h1><a href="/">~repos</a>/{{ title }}</h1>
5
+ <h1><a href="/">~repos</a>/<span style="view-transition-name:repo-title-{{ title }}">{{ title }}</span></h1>
5
6
  <div class="tags">{% for tag in tags %}<span class="tag">#{{ tag }}</span>{% endfor %}</div>
6
7
  </div>
7
8
  <h3>GIT_CONFIG_PARAMETERS="'http.version=HTTP/1.1'" git clone <a>https://git.pyrossh.dev/{{ title }}/.git</a> {{ title }}</h3>
src/index.njk CHANGED
@@ -51,7 +51,7 @@ permalink: /
51
51
  {% for repo in site.repos %}
52
52
  {% set local = localRepos | selectattr("id", "equalto", repo.title) | first %}
53
53
  <div class="repo">
54
- <h4>{% if local %}<a href="/repos/{{ repo.title }}/">{{ repo.title }}</a>{% else %}{{ repo.title }}{% endif %}</h4>
54
+ <h4>{% if local %}<a href="/repos/{{ repo.title }}/" style="view-transition-name:repo-title-{{ repo.title }}">{{ repo.title }}</a>{% else %}{{ repo.title }}{% endif %}</h4>
55
55
  <p>{{ repo.description }}</p>
56
56
  <div class="tags">{% for tag in repo.tags %}<span class="tag">#{{ tag }}</span>{% endfor %}</div>
57
57
  </div>
src/repos/file.njk CHANGED
@@ -9,7 +9,7 @@ eleventyComputed:
9
9
  title: "{{ item.file.name }} | {{ item.repo.data.title }}"
10
10
  ---
11
11
  {% from "repo-layout.njk" import repoLayout %}
12
- {% from "file-layout.njk" import fileLayout %}
12
+ {% from "file-layout.njk" import fileLayout, fileContent %}
13
13
  <div id="repo-page">
14
14
  {{ repoLayout(item.repo.id, item.repo.data.title, item.repo.data.description, item.repo.data.tags, "Files") }}
15
15
  <div id="repo-content">
@@ -20,17 +20,7 @@ eleventyComputed:
20
20
  </aside>
21
21
  <div id="file-pane" class="file-explorer-main">
22
22
  {{ fileLayout(item.repoId, item.file.path, item.file.name, item.file.ext, "Contents") }}
23
- <div id="file-content">
24
- {% if item.render.kind == "binary" %}
25
- <p>Binary file ({{ item.file.size }} bytes).</p>
23
+ {{ fileContent(item.render, item.file.size, item.file.path) }}
26
- {% elif item.render.kind == "large" %}
27
- <p>File too large to display ({{ item.file.size }} bytes).</p>
28
- {% elif item.render.kind == "image" %}
29
- <img src="{{ item.render.src }}" alt="{{ item.file.path }}" />
30
- {% else %}
31
- <pre class="code-view" data-lang="{{ item.render.lang }}"><code class="language-{{ item.render.lang }}">{{ item.render.html | safe }}</code></pre>
32
- {% endif %}
33
- </div>
34
24
  </div>
35
25
  </div>
36
26
  </div>
src/repos/files-index.njk CHANGED
@@ -9,16 +9,24 @@ eleventyComputed:
9
9
  title: "Files | {{ repo.data.title }}"
10
10
  ---
11
11
  {% from "repo-layout.njk" import repoLayout %}
12
+ {% from "file-layout.njk" import fileLayout, fileContent %}
13
+ {% set defaultEntry = repoFiles | filterByRepo(repo.id) | defaultFileEntry %}
14
+ {% set activePath = defaultEntry.file.path if defaultEntry else "" %}
12
15
  <div id="repo-page">
13
16
  {{ repoLayout(repo.id, repo.data.title, repo.data.description, repo.data.tags, "Files") }}
14
17
  <div id="repo-content">
15
18
  <div class="file-explorer">
16
19
  <aside class="file-explorer-tree">
17
20
  {% from "file-tree.njk" import renderNodes %}
18
- {{ renderNodes(repo.tree, repo.id) }}
21
+ {{ renderNodes(repo.tree, repo.id, activePath) }}
19
22
  </aside>
20
23
  <div id="file-pane" class="file-explorer-main">
24
+ {% if defaultEntry %}
25
+ {{ fileLayout(repo.id, defaultEntry.file.path, defaultEntry.file.name, defaultEntry.file.ext, "Contents") }}
26
+ {{ fileContent(defaultEntry.render, defaultEntry.file.size, defaultEntry.file.path) }}
27
+ {% else %}
21
- <p class="file-explorer-placeholder">Select a file to view its contents.</p>
28
+ <p class="file-explorer-placeholder">Select a file to view its contents.</p>
29
+ {% endif %}
22
30
  </div>
23
31
  </div>
24
32
  </div>