website

#astro#js#html#css

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

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


assets/css/shared.css
f612fe5 1
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
f612fe5 2
html { -webkit-text-size-adjust: 100%; }
44991dd 3
body { line-height: 1.5; }
f612fe5 4
img, picture, video, canvas, svg { display: block; max-width: 100%; }
f612fe5 5
input, button, textarea, select { font: inherit; }
f612fe5 6
p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
f612fe5 7
a { color: inherit; text-decoration: none; }
f612fe5 8
ul, ol { list-style: none; }
f612fe5 9
table { border-collapse: collapse; }
f612fe5 10
fe1b28f 11
@view-transition {
fe1b28f 12
  navigation: auto;
fe1b28f 13
}
fe1b28f 14
3c79a68 15
/* Cross-document view transitions (native browser MPA navigation, no JS).
3c79a68 16
   Astro's <ClientRouter /> does this via a client-side router; here the
3c79a68 17
   browser handles it directly off `@view-transition { navigation: auto }`
3c79a68 18
   above — this block just replaces the default cross-fade with a slightly
3c79a68 19
   nicer slide+fade, and keeps header/footer stable instead of fading with
44991dd 20
   the rest of the page. The named ::view-transition-group(...) pair below
44991dd 21
   operates on the browser's top-layer snapshot tree, not the regular DOM,
44991dd 22
   so unlike the view-transition-name declarations themselves (nested
44991dd 23
   inside header/footer below) it can't be scoped and has to stay global. */
3c79a68 24
::view-transition-old(root),
3c79a68 25
::view-transition-new(root) {
3c79a68 26
  animation-duration: 220ms;
3c79a68 27
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
3c79a68 28
}
3c79a68 29
3c79a68 30
::view-transition-old(root) {
3c79a68 31
  animation-name: view-transition-fade-out, view-transition-slide-out;
3c79a68 32
}
3c79a68 33
3c79a68 34
::view-transition-new(root) {
3c79a68 35
  animation-name: view-transition-fade-in, view-transition-slide-in;
3c79a68 36
}
3c79a68 37
3c79a68 38
@keyframes view-transition-fade-out {
3c79a68 39
  to { opacity: 0; }
3c79a68 40
}
3c79a68 41
3c79a68 42
@keyframes view-transition-fade-in {
3c79a68 43
  from { opacity: 0; }
3c79a68 44
}
3c79a68 45
3c79a68 46
@keyframes view-transition-slide-out {
3c79a68 47
  to { transform: translateY(-8px); }
3c79a68 48
}
3c79a68 49
3c79a68 50
@keyframes view-transition-slide-in {
3c79a68 51
  from { transform: translateY(8px); }
3c79a68 52
}
3c79a68 53
3c79a68 54
::view-transition-group(site-header),
3c79a68 55
::view-transition-group(site-footer) {
3c79a68 56
  animation: none;
3c79a68 57
}
3c79a68 58
3c79a68 59
@media (prefers-reduced-motion: reduce) {
3c79a68 60
  ::view-transition-old(root),
3c79a68 61
  ::view-transition-new(root) {
3c79a68 62
    animation: none !important;
3c79a68 63
  }
3c79a68 64
}
3c79a68 65
fe1b28f 66
:root {
fe1b28f 67
  --color-black: light-dark(hsl(0, 0%, 0%), hsl(0, 0%, 0%));
fe1b28f 68
  --color-white: light-dark(hsl(0, 0%, 100%), hsl(0, 0%, 100%));
fe1b28f 69
  --color-accent: hsl(57.25deg 100% 70%);
fe1b28f 70
  --color-body-bg: light-dark(hsl(0, 0%, 100%), hsl(210, 13%, 15%));
fe1b28f 71
  --color-box-bg: light-dark(hsl(0, 0%, 95%), hsl(210, 13%, 9%));
fe1b28f 72
  --color-text: light-dark(hsl(0, 0%, 13%), hsl(210, 17%, 98%));
fe1b28f 73
  --color-link: light-dark(hsl(211, 100%, 45%), hsl(211, 100%, 60%));
fe1b28f 74
  --color-post-link: light-dark(hsl(0, 0%, 0%), hsl(211, 100%, 60%));
60e980e 75
  --color-highlight-bg: light-dark(hsl(210, 10%, 88%), hsl(210, 10%, 24%));
44991dd 76
  --color-code-fg: light-dark(hsl(348, 86%, 43%), hsl(348, 86%, 80%));
9a404c0 77
  --color-code-bg: light-dark(hsl(0, 0%, 89%), hsl(210, 13%, 6%));
9a404c0 78
  --color-pre-bg: light-dark(hsl(0, 0%, 89%), hsl(210, 13%, 6%));
fe1b28f 79
  --color-tag: light-dark(hsl(152, 96%, 35%), hsl(152, 96%, 44%));
3c79a68 80
  --token-comment: light-dark(hsl(220, 10%, 45%), hsl(220, 10%, 60%));
3c79a68 81
  --token-keyword: light-dark(hsl(310, 60%, 45%), hsl(310, 70%, 72%));
3c79a68 82
  --token-string: light-dark(hsl(95, 45%, 35%), hsl(95, 55%, 65%));
3c79a68 83
  --token-function: light-dark(hsl(210, 70%, 45%), hsl(210, 80%, 72%));
3c79a68 84
  --token-number: light-dark(hsl(25, 80%, 42%), hsl(25, 90%, 65%));
3c79a68 85
  --token-builtin: light-dark(hsl(175, 55%, 32%), hsl(175, 60%, 60%));
3c79a68 86
  --token-punctuation: light-dark(hsl(220, 8%, 40%), hsl(220, 8%, 70%));
3c79a68 87
  --token-tag: light-dark(hsl(0, 60%, 45%), hsl(0, 70%, 72%));
3c79a68 88
  --token-attr-name: light-dark(hsl(35, 70%, 38%), hsl(35, 80%, 65%));
3c79a68 89
  color-scheme: light dark;
fe1b28f 90
  --btn-light: none;
fe1b28f 91
  --btn-dark: block;
ada6e53 92
ada6e53 93
  /* Fluid type/space scale (utopia.fyi methodology): each token scales
ada6e53 94
     continuously between its min size at a 320px viewport and its max
ada6e53 95
     size at a 1280px viewport, replacing what used to be a hard jump at
ada6e53 96
     a single max-width:720px breakpoint. Structural changes (flex
ada6e53 97
     direction, grid column counts, sidebar widths) aren't continuous
ada6e53 98
     properties and stay as ordinary breakpoints elsewhere. */
ada6e53 99
  --fs-sm: clamp(1rem, calc(0.9667rem + 0.1667vw), 1.1rem);
ada6e53 100
  --fs-body: clamp(1.1rem, calc(1.05rem + 0.25vw), 1.25rem);
ada6e53 101
  --fs-md: clamp(1.15rem, calc(1.0667rem + 0.4167vw), 1.4rem);
ada6e53 102
  --fs-title-lg: clamp(1.6rem, calc(1.5083rem + 0.4583vw), 1.875rem);
ada6e53 103
  --fs-heading-md: clamp(1.2rem, calc(1.1rem + 0.5vw), 1.5rem);
ada6e53 104
  --fs-heading-sm: clamp(1rem, calc(0.9167rem + 0.4167vw), 1.25rem);
bf7618d 105
  --fs-repo-title: clamp(1.7rem, calc(1.6rem + 0.5vw), 2rem);
bf7618d 106
  --fs-repo-clone: clamp(0.7rem, calc(0.7rem + 0.5vw), 0.9rem);
bf7618d 107
  --fs-repo-subtitle: clamp(0.9rem, calc(0.9rem + 0.5vw), 1.1rem);
ada6e53 108
  --fs-hero-xl: clamp(1.65rem, calc(1.5333rem + 0.5833vw), 2rem);
ada6e53 109
  --fs-features: clamp(0.925rem, calc(0.8583rem + 0.3333vw), 1.125rem);
ada6e53 110
  --space-gap-fluid: clamp(0rem, calc(-0.6667rem + 3.3333vw), 2rem);
ada6e53 111
  --space-section-mb: clamp(0rem, calc(-0.3333rem + 1.6667vw), 1rem);
3c79a68 112
}
f612fe5 113
3c79a68 114
/* data-theme is tri-state: absent = follow OS (color-scheme: light dark
3c79a68 115
   above already does that), "light"/"dark" = explicit user override that
3c79a68 116
   must win even against an opposite OS preference. Every color below is
3c79a68 117
   driven by light-dark() reading this color-scheme value — no need to
3c79a68 118
   redeclare each variable per theme. */
3c79a68 119
:root[data-theme="light"] {
3c79a68 120
  color-scheme: light;
fe1b28f 121
}
b346b17 122
3c79a68 123
:root[data-theme="dark"] {
3c79a68 124
  color-scheme: dark;
3c79a68 125
}
b346b17 126
3c79a68 127
@media (prefers-color-scheme: dark) {
3c79a68 128
  :root:not([data-theme]) {
3c79a68 129
    --btn-light: block;
3c79a68 130
    --btn-dark: none;
ec165f1 131
  }
fe1b28f 132
}
b346b17 133
44991dd 134
:root[data-theme="dark"] {
44991dd 135
  --btn-light: block;
44991dd 136
  --btn-dark: none;
44991dd 137
}
44991dd 138
44991dd 139
:root[data-theme="light"] {
44991dd 140
  --btn-light: none;
44991dd 141
  --btn-dark: block;
44991dd 142
}
44991dd 143
fe1b28f 144
[data-tooltip] {
fe1b28f 145
  position: relative;
fe1b28f 146
  cursor: help;
fe1b28f 147
}
b346b17 148
fe1b28f 149
[data-tooltip]::after {
fe1b28f 150
  position: absolute;
fe1b28f 151
  opacity: 0;
fe1b28f 152
  pointer-events: none;
fe1b28f 153
  content: attr(data-tooltip);
fe1b28f 154
  left: 0;
fe1b28f 155
  top: calc(100% + 10px);
fe1b28f 156
  border-radius: 3px;
fe1b28f 157
  box-shadow: 0 0 1px 1px var(--color-text);
fe1b28f 158
  background-color: var(--color-box-bg);
fe1b28f 159
  z-index: 10;
fe1b28f 160
  padding: 8px;
fe1b28f 161
  width: 300px;
fe1b28f 162
  transform: translateY(-20px);
fe1b28f 163
  transition: all 150ms cubic-bezier(0.25, 0.8, 0.25, 1);
fe1b28f 164
}
b346b17 165
fe1b28f 166
[data-tooltip]:hover::after {
fe1b28f 167
  opacity: 1;
fe1b28f 168
  transform: translateY(0);
fe1b28f 169
  transition-duration: 300ms;
fe1b28f 170
}
fe1b28f 171
fe1b28f 172
body {
fe1b28f 173
  display: flex;
fe1b28f 174
  flex-direction: column;
fe1b28f 175
  flex: 1;
fe1b28f 176
  background-color: var(--color-body-bg);
fe1b28f 177
  color: var(--color-text);
fe1b28f 178
  font-family: system-ui;
fe1b28f 179
  text-rendering: optimizeLegibility;
fe1b28f 180
  font-variant-ligatures: common-ligatures;
fe1b28f 181
  font-kerning: normal;
ada6e53 182
  font-size: var(--fs-body);
fe1b28f 183
  line-height: 1.5;
fe1b28f 184
  min-height: 100vh;
fe1b28f 185
}
b346b17 186
fe1b28f 187
main {
fe1b28f 188
  margin-top: 1rem;
fe1b28f 189
  margin-bottom: 1rem;
fe1b28f 190
  padding-bottom: 1em;
fe1b28f 191
}
f612fe5 192
44991dd 193
.wrapper {
44991dd 194
  flex: 1;
44991dd 195
  width: 100%;
09c6a56 196
  max-width: 72ch;
44991dd 197
  margin-right: auto;
44991dd 198
  margin-left: auto;
09c6a56 199
  padding-left: 1rem;
09c6a56 200
  padding-right: 1rem;
fe1b28f 201
}
b346b17 202
44991dd 203
/* site-header.njk/site-footer.njk are plain includes (no wrapping custom
44991dd 204
   element), so these are just nested under the real <header>/<footer>
44991dd 205
   tag — plain CSS nesting scopes .logo/.links etc to it same as any other
44991dd 206
   descendant selector would, no @scope needed since there's only ever
44991dd 207
   one header and one footer on the page. */
3c79a68 208
header {
fe1b28f 209
  background: #131618;
44991dd 210
  view-transition-name: site-header;
b346b17 211
44991dd 212
  .logo {
ec165f1 213
    display: flex;
ec165f1 214
    font-size: 22px;
ec165f1 215
    line-height: 56px;
ec165f1 216
    font-weight: 400;
44991dd 217
    margin-bottom: 0;
44991dd 218
    float: left;
44991dd 219
    color: var(--color-accent);
44991dd 220
    margin-right: 8px;
ec165f1 221
ec165f1 222
    &.active,
ec165f1 223
    &:hover {
44991dd 224
      color: var(--color-accent);
ec165f1 225
      text-decoration: underline;
ec165f1 226
      text-decoration-color: var(--color-accent);
ec165f1 227
      text-underline-offset: 8px;
ec165f1 228
    }
ec165f1 229
  }
44991dd 230
44991dd 231
  .links {
44991dd 232
    display: flex;
44991dd 233
    flex-direction: row;
44991dd 234
    float: right;
44991dd 235
    font-size: 1.4rem;
44991dd 236
    line-height: 56px;
44991dd 237
    color: white;
44991dd 238
44991dd 239
    #theme-switch {
44991dd 240
      display: flex;
44991dd 241
      align-items: center;
44991dd 242
      font-size: 1.3rem;
44991dd 243
44991dd 244
      button {
44991dd 245
        background: none;
44991dd 246
        border: none;
44991dd 247
        padding: 0;
44991dd 248
        padding-right: 12px;
44991dd 249
        margin-bottom: -2px;
44991dd 250
        color: inherit;
44991dd 251
        cursor: pointer;
44991dd 252
44991dd 253
        &:hover,
44991dd 254
        &:focus,
44991dd 255
        &:active {
44991dd 256
          background: none;
44991dd 257
          border: none;
44991dd 258
          color: inherit;
44991dd 259
        }
44991dd 260
      }
44991dd 261
    }
44991dd 262
44991dd 263
    a {
44991dd 264
      display: inline-block;
44991dd 265
      text-decoration: none;
44991dd 266
      font-size: 22px;
44991dd 267
      line-height: 56px;
44991dd 268
      color: white;
44991dd 269
      font-weight: 400;
44991dd 270
      padding-left: 12px;
44991dd 271
      padding-right: 12px;
44991dd 272
44991dd 273
      &:last-child {
44991dd 274
        padding-right: 0px;
44991dd 275
      }
44991dd 276
44991dd 277
      &.active,
44991dd 278
      &:hover {
44991dd 279
        color: var(--color-gray-200);
44991dd 280
        text-decoration: underline;
44991dd 281
        text-decoration-color: var(--color-accent);
44991dd 282
        text-underline-offset: 8px;
44991dd 283
      }
44991dd 284
    }
44991dd 285
  }
44991dd 286
}
44991dd 287
fe1b28f 288
footer {
fe1b28f 289
  background: #131618;
44991dd 290
  view-transition-name: site-footer;
b346b17 291
44991dd 292
  .container {
44991dd 293
    display: flex;
44991dd 294
    flex-direction: row;
44991dd 295
    flex: 1;
44991dd 296
    padding: 16px 0px;
44991dd 297
  }
b346b17 298
44991dd 299
  .spacer {
44991dd 300
    flex: 1;
44991dd 301
  }
b346b17 302
44991dd 303
  .copyright-container {
44991dd 304
    display: flex;
44991dd 305
    align-items: center;
44991dd 306
    justify-content: center;
44991dd 307
    margin-left: 0.5rem;
44991dd 308
    margin-right: 0.5rem;
44991dd 309
    color: var(--color-white);
44991dd 310
    font-size: 1rem;
ec165f1 311
  }
f612fe5 312
44991dd 313
  .link {
44991dd 314
    font-size: 1rem;
44991dd 315
    line-height: 1.5rem;
44991dd 316
    margin-left: 0.5rem;
44991dd 317
    color: var(--color-white);
44991dd 318
    font-weight: 500;
44991dd 319
    text-underline-offset: 2px;
44991dd 320
    text-decoration-thickness: 2px;
44991dd 321
    text-decoration-line: underline;
44991dd 322
    text-decoration-color: white;
3c79a68 323
44991dd 324
    &:hover {
44991dd 325
      color: var(--color-gray-200);
44991dd 326
      text-decoration-color: var(--color-gray-200);
44991dd 327
    }
44991dd 328
  }
3c79a68 329
}
3c79a68 330
44991dd 331
/* Syntax highlighting theme: shared by code-view's own file-viewer pre and
44991dd 332
   by fenced code blocks inside markdown-view's article content (both
44991dd 333
   Prism-highlighted blog-post code and plain markdown-it README code
44991dd 334
   fences). It's genuinely cross-cutting rather than owned by one
44991dd 335
   component — scoping it to either would mean duplicating the whole token
60e980e 336
   palette in the other — so it stays global.
60e980e 337
60e980e 338
   Background lives on pre.code-view/.markdown-body pre below, not here:
60e980e 339
   code[class*="language-"] is inline and wraps a whole multi-line file, so
60e980e 340
   giving IT a background paints one box per line box (only as wide as that
60e980e 341
   line's text) instead of one clean rectangle behind the block. */
3c79a68 342
pre[class*="language-"],
3c79a68 343
code[class*="language-"] {
3c79a68 344
  color: var(--color-text);
3c79a68 345
}
3c79a68 346
3c79a68 347
pre[class*="language-"],
3c79a68 348
pre.code-view,
3c79a68 349
.markdown-body pre {
3c79a68 350
  padding: 1rem;
3c79a68 351
  margin: 1rem 0;
3c79a68 352
  overflow-x: auto;
3c79a68 353
  border-radius: 4px;
3c79a68 354
  line-height: 1.5;
3c79a68 355
}
3c79a68 356
44991dd 357
/* File-viewer code (pre.code-view) matches the rest of that layout's
44991dd 358
   0.9rem scale (see repos.css); blog/README fenced code blocks stay at
44991dd 359
   0.8rem — they're a different context and weren't asked to change. */
44991dd 360
pre.code-view {
44991dd 361
  font-size: 0.9rem;
44991dd 362
}
44991dd 363
44991dd 364
pre[class*="language-"],
44991dd 365
.markdown-body pre {
44991dd 366
  font-size: 0.8rem;
44991dd 367
}
44991dd 368
3c79a68 369
pre.code-view,
3c79a68 370
.markdown-body pre {
3c79a68 371
  background: var(--color-pre-bg);
3c79a68 372
  color: var(--color-text);
3c79a68 373
}
3c79a68 374
3c79a68 375
code[class*="language-"] {
3c79a68 376
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
3c79a68 377
}
3c79a68 378
3c79a68 379
.token.comment,
3c79a68 380
.token.prolog,
3c79a68 381
.token.doctype,
3c79a68 382
.token.cdata {
3c79a68 383
  color: var(--token-comment);
3c79a68 384
  font-style: italic;
3c79a68 385
}
3c79a68 386
3c79a68 387
.token.keyword,
3c79a68 388
.token.selector,
3c79a68 389
.token.important,
3c79a68 390
.token.atrule {
3c79a68 391
  color: var(--token-keyword);
3c79a68 392
}
3c79a68 393
3c79a68 394
.token.string,
3c79a68 395
.token.char,
3c79a68 396
.token.attr-value,
3c79a68 397
.token.regex {
3c79a68 398
  color: var(--token-string);
3c79a68 399
}
3c79a68 400
3c79a68 401
.token.function,
3c79a68 402
.token.class-name {
3c79a68 403
  color: var(--token-function);
3c79a68 404
}
3c79a68 405
3c79a68 406
.token.number,
3c79a68 407
.token.boolean,
3c79a68 408
.token.constant {
3c79a68 409
  color: var(--token-number);
3c79a68 410
}
3c79a68 411
3c79a68 412
.token.builtin,
3c79a68 413
.token.deleted {
3c79a68 414
  color: var(--token-builtin);
3c79a68 415
}
3c79a68 416
3c79a68 417
.token.punctuation,
3c79a68 418
.token.operator {
3c79a68 419
  color: var(--token-punctuation);
3c79a68 420
}
3c79a68 421
3c79a68 422
.token.tag {
3c79a68 423
  color: var(--token-tag);
3c79a68 424
}
3c79a68 425
3c79a68 426
.token.attr-name,
3c79a68 427
.token.property {
3c79a68 428
  color: var(--token-attr-name);
3c79a68 429
}