website

#astro#js#html#css

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

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


assets/css/repos.css
3c79a68 1
/* Repo browsing pages (readme/files/commits/blame). Restored from the
3c79a68 2
   original Astro-era RepoLayout/FileLayout/Commit/RecursiveList components
3c79a68 3
   (see git history at 7325531^), with the tab nav restyled as a bordered
3c79a68 4
   tab bar and a couple of small crates.io-inspired touches (clone command
3c79a68 5
   as its own code box, tags as pills) layered on top.
3c79a68 6
3c79a68 7
   Font sizes on this page follow one small scale instead of one-off
3c79a68 8
   values, so nothing reads as randomly bigger/smaller than its neighbour:
3c79a68 9
     0.85rem — small badges (tags, pull-right meta, file-size hints)
44991dd 10
     0.9rem  — code/monospace content, nav links and sub-headings alike
3931c4d 11
               (pre/code-view, file tree, blame, file title)
3c79a68 12
     0.95rem — box body text (.repo, .event)
ada6e53 13
     var(--fs-repo-title) — page-level heading (.repo h1), fluid 1.3–1.6rem
3c79a68 14
   All of them are rem-based, not em, so nesting depth can't change the
3c79a68 15
   rendered size — that's what made the old code-view/blame font sizes
44991dd 16
   inconsistent (em compounds with whatever ancestor happens to wrap it).
44991dd 17
44991dd 18
   Everything below uses plain CSS nesting rather than @scope: .repo,
44991dd 19
   .file-layout, .event, .file-explorer and table.blame are all real,
44991dd 20
   always-present classes/tags on these pages (none of them are wrapped
44991dd 21
   in a custom element anymore), so nesting under them scopes their
44991dd 22
   descendants exactly the way @scope would, without needing a scope root
44991dd 23
   selector that has to be kept in sync with whatever markup happens to
44991dd 24
   wrap it. */
3c79a68 25
3c79a68 26
.repo {
3c79a68 27
  text-overflow: ellipsis;
3c79a68 28
  overflow: hidden;
44991dd 29
  padding: 0.5rem;
3c79a68 30
  background: var(--color-box-bg);
3c79a68 31
  color: var(--color-text);
3c79a68 32
  font-size: 0.95rem;
8be9c52 33
  padding: 1rem;
3c79a68 34
44991dd 35
  a {
44991dd 36
    color: var(--color-link);
3c79a68 37
  }
3c79a68 38
44991dd 39
  .header {
44991dd 40
    display: flex;
44991dd 41
    flex-direction: row;
44991dd 42
    align-items: baseline;
3c79a68 43
44991dd 44
    @media (max-width: 720px) {
44991dd 45
      flex-direction: column;
44991dd 46
      align-items: start;
44991dd 47
    }
44991dd 48
  }
3c79a68 49
44991dd 50
  h1 {
ada6e53 51
    font-size: var(--fs-repo-title);
44991dd 52
    font-weight: 500;
44991dd 53
    line-height: 1;
44991dd 54
    margin-bottom: 0.1rem;
44991dd 55
    flex: 1;
44991dd 56
  }
3c79a68 57
09c6a56 58
  .repo-version {
09c6a56 59
    font-size: 1.1rem;
09c6a56 60
    font-weight: 500;
09c6a56 61
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
09c6a56 62
    color: var(--color-text);
09c6a56 63
    margin-left: 0.5rem;
09c6a56 64
  }
09c6a56 65
44991dd 66
  .tags {
44991dd 67
    margin-top: 0.5rem;
44991dd 68
    display: flex;
44991dd 69
    flex-wrap: wrap;
44991dd 70
    gap: 0.25rem;
44991dd 71
  }
3c79a68 72
44991dd 73
  .tag {
44991dd 74
    color: var(--color-tag);
44991dd 75
    padding: 0.15rem 0.5rem 0.5rem 0;
44991dd 76
    border-radius: 4px;
44991dd 77
    font-size: 0.95rem;
44991dd 78
    font-weight: 500;
44991dd 79
  }
3c79a68 80
8be9c52 81
  h3.clone-cmd {
8be9c52 82
    display: flex;
8be9c52 83
    align-items: center;
8be9c52 84
    justify-content: space-between;
3931c4d 85
    font-weight: 400;
bf7618d 86
    font-size: var(--fs-repo-clone);
bf7618d 87
    padding: 0.4rem;
bf7618d 88
    background-color: var(--color-pre-bg);
8be9c52 89
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
3931c4d 90
    overflow-x: auto;
3931c4d 91
    white-space: pre;
8be9c52 92
    width: fit-content;
bf7618d 93
    margin-top: 0.2rem;
8be9c52 94
8be9c52 95
    .copy-btn {
8be9c52 96
      flex-shrink: 0;
8be9c52 97
      display: flex;
8be9c52 98
      align-items: center;
8be9c52 99
      justify-content: center;
8be9c52 100
      width: 1.6rem;
8be9c52 101
      height: 1.6rem;
8be9c52 102
      padding: 0;
8be9c52 103
      border: none;
8be9c52 104
      border-radius: 4px;
8be9c52 105
      background: none;
8be9c52 106
      color: var(--color-text);
8be9c52 107
      opacity: 0.6;
8be9c52 108
      cursor: pointer;
8be9c52 109
8be9c52 110
      &:hover {
8be9c52 111
        opacity: 1;
8be9c52 112
        background: var(--color-box-bg);
8be9c52 113
      }
8be9c52 114
8be9c52 115
      .icon-check {
8be9c52 116
        display: none;
8be9c52 117
      }
8be9c52 118
8be9c52 119
      &.copied {
8be9c52 120
        opacity: 1;
8be9c52 121
        color: var(--color-tag);
8be9c52 122
8be9c52 123
        .icon-copy {
8be9c52 124
          display: none;
8be9c52 125
        }
8be9c52 126
8be9c52 127
        .icon-check {
8be9c52 128
          display: block;
8be9c52 129
        }
8be9c52 130
      }
8be9c52 131
    }
44991dd 132
  }
3c79a68 133
44991dd 134
  .headerExtension h2 {
44991dd 135
    display: flex;
44991dd 136
    flex: 1;
44991dd 137
    padding-top: 0.3rem;
bf7618d 138
    font-size: var(--fs-repo-subtitle);
44991dd 139
    font-weight: 400;
44991dd 140
  }
3c79a68 141
44991dd 142
  hr {
44991dd 143
    margin-top: 0.5rem;
44991dd 144
    border: none;
44991dd 145
    border-top: 1px solid var(--color-text);
44991dd 146
  }
3c79a68 147
44991dd 148
  .nav {
44991dd 149
    display: flex;
44991dd 150
    align-items: center;
44991dd 151
    margin-top: 0.5rem;
44991dd 152
44991dd 153
    span {
9a404c0 154
      padding: 0 0.75rem;
3931c4d 155
      color: var(--color-text);
44991dd 156
    }
44991dd 157
44991dd 158
    a {
44991dd 159
      font-size: 1rem;
44991dd 160
      font-weight: 500;
44991dd 161
      cursor: pointer;
44991dd 162
      color: var(--color-link);
3931c4d 163
      text-decoration: none;
3931c4d 164
3931c4d 165
      &.active-tab {
3931c4d 166
        text-decoration: underline;
3931c4d 167
        text-underline-offset: 4px;
3931c4d 168
      }
44991dd 169
    }
44991dd 170
  }
3c79a68 171
}
3c79a68 172
3c79a68 173
#repo-content {
9a404c0 174
  margin-top: 0.75rem;
9a404c0 175
9a404c0 176
  /* README (repos/repo.njk) — same box treatment as the file explorer
9a404c0 177
     panes below, so it doesn't sit as bare unstyled text against the
9a404c0 178
     page background. Scoped to #repo-content so blog posts (which also
9a404c0 179
     use .markdown-body) are untouched. */
9a404c0 180
  .markdown-body {
9a404c0 181
    background: var(--color-box-bg);
9a404c0 182
    padding: 1rem 1.25rem;
9a404c0 183
  }
3c79a68 184
}
3c79a68 185
44991dd 186
/* Commit list / commit detail header (Commit.astro) — repos/commit.njk,
44991dd 187
   commits.njk, file-history.njk. pre.commit is nested here too since
44991dd 188
   it's always .event's child, never used alone. */
3c79a68 189
.event {
3c79a68 190
  background-color: var(--color-box-bg);
3c79a68 191
  color: var(--color-text);
3c79a68 192
  text-overflow: ellipsis;
3c79a68 193
  overflow: hidden;
3c79a68 194
  padding: 0.6rem 0.75rem;
3c79a68 195
  margin: 0.5rem 0;
3c79a68 196
  font-size: 0.95rem;
3c79a68 197
44991dd 198
  a {
44991dd 199
    color: var(--color-link);
44991dd 200
  }
3c79a68 201
44991dd 202
  strong {
44991dd 203
    font-weight: 500;
44991dd 204
  }
3c79a68 205
44991dd 206
  .pull-right {
44991dd 207
    font-size: 0.85rem;
44991dd 208
    font-weight: 400;
44991dd 209
    float: right;
44991dd 210
  }
3c79a68 211
44991dd 212
  pre.commit {
44991dd 213
    font-family: inherit;
44991dd 214
    white-space: pre-wrap;
44991dd 215
    margin-top: 0.35rem;
44991dd 216
  }
3c79a68 217
}
3c79a68 218
3c79a68 219
/* Two-pane IDE-style file explorer: a persistent tree sidebar + a content
3c79a68 220
   pane, each its own rounded box with a gap between them rather than a
3c79a68 221
   single shared border — the gap is what visually separates them, so
3c79a68 222
   they don't also need to differ in colour. The tree lives outside
3c79a68 223
   #file-pane, so htmx swaps of #file-pane (clicking a file) or
3c79a68 224
   #file-content (Contents/History/Blame) never touch the sidebar — it
44991dd 225
   just stays where it is, already expanded, no reload.
44991dd 226
44991dd 227
   Fixed-height explorer: both panes stretch to the same height and scroll
3c79a68 228
   independently, so scrolling a long file never scrolls the tree out of
3c79a68 229
   view (or the whole page) — closer to a real IDE split view than a page
3c79a68 230
   that just keeps growing with the file's content. */
3c79a68 231
.file-explorer {
3c79a68 232
  display: flex;
3c79a68 233
  align-items: stretch;
3931c4d 234
  gap: 0.5rem;
3c79a68 235
  height: 80vh;
3c79a68 236
44991dd 237
  /* File tree: flat rows (no per-item card background), a rotating
44991dd 238
     triangle for expand/collapse instead of an emoji folder glyph, and a
44991dd 239
     left accent border + tint on the active file instead of a filled
44991dd 240
     pill. */
3c79a68 241
  .file-explorer-tree {
44991dd 242
    width: 260px;
44991dd 243
    flex-shrink: 0;
44991dd 244
    height: 100%;
44991dd 245
    overflow-y: auto;
44991dd 246
    padding: 0.6rem;
44991dd 247
    background: var(--color-box-bg);
44991dd 248
    border-radius: 6px;
44991dd 249
    font-size: 0.9rem;
44991dd 250
44991dd 251
    ul {
44991dd 252
      list-style: none;
44991dd 253
      margin: 0 0 0 1.1rem;
44991dd 254
      padding: 0;
44991dd 255
    }
44991dd 256
44991dd 257
    > ul {
44991dd 258
      margin-left: 0;
44991dd 259
    }
44991dd 260
44991dd 261
    li {
44991dd 262
      margin: 0;
44991dd 263
44991dd 264
      &.active-file,
44991dd 265
      & > a.active-file {
44991dd 266
        color: var(--color-link);
44991dd 267
      }
44991dd 268
    }
44991dd 269
44991dd 270
    summary,
44991dd 271
    li > a {
44991dd 272
      display: flex;
44991dd 273
      align-items: center;
44991dd 274
      gap: 0.4rem;
44991dd 275
      padding: 0.2rem 0.35rem;
44991dd 276
      border-radius: 4px;
44991dd 277
      border-left: 2px solid transparent;
44991dd 278
      cursor: pointer;
44991dd 279
      text-decoration: none;
44991dd 280
      color: var(--color-text);
44991dd 281
      line-height: 1.6;
44991dd 282
    }
44991dd 283
44991dd 284
    summary {
44991dd 285
      list-style: none;
44991dd 286
44991dd 287
      &::-webkit-details-marker {
44991dd 288
        display: none;
44991dd 289
      }
44991dd 290
44991dd 291
      &::before {
44991dd 292
        content: "";
44991dd 293
        flex-shrink: 0;
44991dd 294
        width: 0;
44991dd 295
        height: 0;
44991dd 296
        border-style: solid;
44991dd 297
        border-width: 0.3em 0 0.3em 0.4em;
44991dd 298
        border-color: transparent transparent transparent currentColor;
44991dd 299
        opacity: 0.5;
44991dd 300
        transition: transform 120ms ease;
44991dd 301
      }
44991dd 302
    }
44991dd 303
44991dd 304
    details[open] > summary::before {
44991dd 305
      transform: rotate(90deg);
44991dd 306
    }
44991dd 307
44991dd 308
    summary svg,
44991dd 309
    li > a svg {
44991dd 310
      flex-shrink: 0;
44991dd 311
      width: 1em;
44991dd 312
      height: 1em;
44991dd 313
    }
44991dd 314
44991dd 315
    summary:hover,
44991dd 316
    li > a:hover {
44991dd 317
      background-color: var(--color-pre-bg);
44991dd 318
    }
44991dd 319
44991dd 320
    li > a.active-file {
44991dd 321
      color: var(--color-link);
44991dd 322
      background-color: var(--color-highlight-bg);
44991dd 323
      border-left-color: var(--color-link);
44991dd 324
      font-weight: 500;
44991dd 325
    }
44991dd 326
44991dd 327
    .file-size {
44991dd 328
      margin-left: auto;
44991dd 329
      opacity: 0.5;
44991dd 330
      font-size: 0.85rem;
44991dd 331
    }
3c79a68 332
  }
3c79a68 333
3c79a68 334
  .file-explorer-main {
44991dd 335
    flex: 1;
44991dd 336
    min-width: 0;
44991dd 337
    height: 100%;
44991dd 338
    display: flex;
44991dd 339
    flex-direction: column;
44991dd 340
    border: 1px solid var(--color-box-bg);
44991dd 341
    border-radius: 6px;
44991dd 342
    overflow: hidden;
44991dd 343
44991dd 344
    &:has(.file-explorer-placeholder) {
44991dd 345
      align-items: center;
44991dd 346
      justify-content: center;
44991dd 347
    }
44991dd 348
44991dd 349
    /* #file-content padding/background: when it holds actual code (a
44991dd 350
       plain <pre>), let the box-bg colour fill the whole pane
44991dd 351
       edge-to-edge instead of floating as a small inset card — closer to
44991dd 352
       a real code viewer's continuous code surface. box-bg (not pre-bg)
44991dd 353
       so it reads as one calm surface with the file-layout header above
44991dd 354
       it, rather than the near-black pre-bg used for one-off code
44991dd 355
       snippets elsewhere on the site. */
44991dd 356
    #file-content {
44991dd 357
      flex: 1;
44991dd 358
      overflow-y: auto;
44991dd 359
      padding: 0.85rem;
44991dd 360
44991dd 361
      &:has(> pre) {
44991dd 362
        padding: 0;
44991dd 363
        background: var(--color-box-bg);
44991dd 364
44991dd 365
        pre {
44991dd 366
          margin: 0;
44991dd 367
          background: transparent;
44991dd 368
          border-radius: 0;
44991dd 369
        }
44991dd 370
      }
44991dd 371
    }
3c79a68 372
  }
3c79a68 373
44991dd 374
  .file-explorer-placeholder {
44991dd 375
    color: var(--color-text);
44991dd 376
    opacity: 0.6;
44991dd 377
    padding: 1rem 0;
44991dd 378
  }
3c79a68 379
44991dd 380
  @media (max-width: 720px) {
44991dd 381
    flex-direction: column;
44991dd 382
    height: 85vh;
3c79a68 383
44991dd 384
    .file-explorer-tree {
44991dd 385
      width: 100%;
44991dd 386
      height: auto;
44991dd 387
      flex: 0 0 35%;
44991dd 388
    }
44991dd 389
44991dd 390
    .file-explorer-main {
44991dd 391
      width: 100%;
44991dd 392
      height: auto;
44991dd 393
      flex: 1 1 auto;
44991dd 394
    }
44991dd 395
  }
3c79a68 396
}
3c79a68 397
3c79a68 398
/* File view header — a compact icon+filename strip with the
3c79a68 399
   Contents/History/Blame tabs, sitting as the card's title bar rather
3c79a68 400
   than a full-width heading. */
3c79a68 401
.file-layout {
3c79a68 402
  display: flex;
3c79a68 403
  align-items: center;
3c79a68 404
  justify-content: flex-start;
3c79a68 405
  flex-shrink: 0;
3c79a68 406
  gap: 0.5rem;
3c79a68 407
  padding: 0.6rem 0.85rem;
3c79a68 408
  background: var(--color-box-bg);
3c79a68 409
  border-bottom: 1px solid var(--color-pre-bg);
3c79a68 410
44991dd 411
  .title {
44991dd 412
    display: flex;
44991dd 413
    align-items: center;
44991dd 414
    gap: 0.4rem;
44991dd 415
    /* flex:1 (not the default 0 1 auto) so it's the one that gives up
44991dd 416
       space when the row is tight — without this, a long filename could
44991dd 417
       force the row to wrap .nav onto its own line instead of shrinking,
44991dd 418
       since the wrap decision is made off each item's unshrunk
44991dd 419
       hypothetical width, not its post-shrink width. */
44991dd 420
    flex: 1 1 auto;
44991dd 421
    min-width: 0;
44991dd 422
    font-size: 0.9rem;
44991dd 423
    font-weight: 500;
9a404c0 424
    margin-bottom: -4px;
44991dd 425
44991dd 426
    svg {
44991dd 427
      flex-shrink: 0;
9a404c0 428
      width: 1rem;
9a404c0 429
      height: 1rem;
44991dd 430
    }
44991dd 431
44991dd 432
    span {
44991dd 433
      overflow: hidden;
44991dd 434
      text-overflow: ellipsis;
44991dd 435
      white-space: nowrap;
44991dd 436
    }
44991dd 437
  }
3c79a68 438
44991dd 439
  .nav {
44991dd 440
    display: flex;
44991dd 441
    align-items: center;
44991dd 442
    gap: 0.25rem;
44991dd 443
    flex-shrink: 0;
44991dd 444
44991dd 445
    div:not(:has(a)) {
44991dd 446
      display: none;
44991dd 447
    }
44991dd 448
44991dd 449
    a {
44991dd 450
      font-size: 0.9rem;
44991dd 451
      font-weight: 500;
44991dd 452
      padding: 0.3rem 0.1rem;
44991dd 453
      margin-right: 0.75rem;
44991dd 454
      cursor: pointer;
44991dd 455
      color: var(--color-text);
44991dd 456
      border-bottom: 2px solid transparent;
44991dd 457
44991dd 458
      &:hover {
44991dd 459
        color: var(--color-link);
44991dd 460
      }
44991dd 461
44991dd 462
      &[aria-current="true"] {
44991dd 463
        color: var(--color-link);
44991dd 464
        border-bottom-color: var(--color-link);
44991dd 465
      }
44991dd 466
    }
44991dd 467
  }
3c79a68 468
}
3c79a68 469
44991dd 470
/* Blame table — repos/file-blame.njk. */
3c79a68 471
table.blame {
3c79a68 472
  width: 100%;
3c79a68 473
  border-collapse: collapse;
44991dd 474
  font-size: 0.9rem;
3c79a68 475
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
3c79a68 476
  background: var(--color-pre-bg);
3c79a68 477
  border-radius: 4px;
3c79a68 478
  overflow: hidden;
3c79a68 479
44991dd 480
  td {
44991dd 481
    vertical-align: top;
44991dd 482
    padding: 0.1rem 0.5rem;
44991dd 483
    border-bottom: 1px solid var(--color-box-bg);
44991dd 484
  }
3c79a68 485
44991dd 486
  .blame-meta {
44991dd 487
    white-space: nowrap;
44991dd 488
    color: var(--color-link);
44991dd 489
    width: 1%;
44991dd 490
  }
3c79a68 491
44991dd 492
  .blame-line-no {
44991dd 493
    white-space: nowrap;
44991dd 494
    opacity: 0.5;
44991dd 495
    text-align: right;
44991dd 496
    width: 1%;
44991dd 497
  }
3c79a68 498
44991dd 499
  .blame-content pre {
44991dd 500
    margin: 0;
44991dd 501
    white-space: pre-wrap;
44991dd 502
  }
3c79a68 503
}
3c79a68 504
3c79a68 505
/* diff2html ships its own light/dark token sets as plain CSS custom
3c79a68 506
   properties on :root (see node_modules/diff2html/bundles/css) but only
3c79a68 507
   applies the dark set under a `.d2h-dark-color-scheme` class we never
3c79a68 508
   add. Remapping the base tokens it actually reads to their `--d2h-dark-*`
3c79a68 509
   counterparts under our own [data-theme="dark"] gets the same result
44991dd 510
   without needing that class or a second copy of every diff2html rule.
44991dd 511
   This re-themes a third-party library's own classes rather than styling
44991dd 512
   one of our own, so it's left flat/global rather than nested. */
3c79a68 513
:root[data-theme="dark"] {
3c79a68 514
  --d2h-bg-color: var(--d2h-dark-bg-color);
3c79a68 515
  --d2h-border-color: var(--d2h-dark-border-color);
3c79a68 516
  --d2h-dim-color: var(--d2h-dark-dim-color);
3c79a68 517
  --d2h-line-border-color: var(--d2h-dark-line-border-color);
3c79a68 518
  --d2h-file-header-bg-color: var(--d2h-dark-file-header-bg-color);
3c79a68 519
  --d2h-file-header-border-color: var(--d2h-dark-file-header-border-color);
3c79a68 520
  --d2h-empty-placeholder-bg-color: var(--d2h-dark-empty-placeholder-bg-color);
3c79a68 521
  --d2h-empty-placeholder-border-color: var(--d2h-dark-empty-placeholder-border-color);
3c79a68 522
  --d2h-selected-color: var(--d2h-dark-selected-color);
3c79a68 523
  --d2h-ins-bg-color: var(--d2h-dark-ins-bg-color);
3c79a68 524
  --d2h-ins-border-color: var(--d2h-dark-ins-border-color);
3c79a68 525
  --d2h-ins-highlight-bg-color: var(--d2h-dark-ins-highlight-bg-color);
3c79a68 526
  --d2h-ins-label-color: var(--d2h-dark-ins-label-color);
3c79a68 527
  --d2h-del-bg-color: var(--d2h-dark-del-bg-color);
3c79a68 528
  --d2h-del-border-color: var(--d2h-dark-del-border-color);
3c79a68 529
  --d2h-del-highlight-bg-color: var(--d2h-dark-del-highlight-bg-color);
3c79a68 530
  --d2h-del-label-color: var(--d2h-dark-del-label-color);
3c79a68 531
  --d2h-change-del-color: var(--d2h-dark-change-del-color);
3c79a68 532
  --d2h-change-ins-color: var(--d2h-dark-change-ins-color);
3c79a68 533
  --d2h-info-bg-color: var(--d2h-dark-info-bg-color);
3c79a68 534
  --d2h-info-border-color: var(--d2h-dark-info-border-color);
3c79a68 535
  --d2h-change-label-color: var(--d2h-dark-change-label-color);
3c79a68 536
  --d2h-moved-label-color: var(--d2h-dark-moved-label-color);
3c79a68 537
}
3c79a68 538
3c79a68 539
.d2h-wrapper {
3c79a68 540
  color: var(--color-text);
3c79a68 541
}