Compare commits
12 Commits
build_1.2.
...
v1.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
| ed5a976735 | |||
|
|
f4e7112d4c | ||
|
|
bc4155ce82 | ||
|
|
f1bda537b4 | ||
|
|
1b2f79f65c | ||
|
|
62383aaf9e | ||
|
|
dcc5654603 | ||
|
|
cf36b71990 | ||
|
|
9cda45f112 | ||
| ef7ef44033 | |||
| 3ca63f62fb | |||
| 4c255a2672 |
1008
package-lock.json
generated
1008
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,8 @@
|
||||
"build": "astro check && astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"prettier": "prettier --write ./src/**/*.{astro,ts,tsx,css}"
|
||||
"prettier": "prettier --write ./src/**/*.{astro,ts,tsx,css}",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
@@ -60,6 +61,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"patch-package": "^8.0.1",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-astro": "^0.13.0",
|
||||
"prettier-plugin-astro-organize-imports": "^0.4.11",
|
||||
|
||||
14
patches/@shikijs+transformers+1.24.4.patch
Normal file
14
patches/@shikijs+transformers+1.24.4.patch
Normal file
@@ -0,0 +1,14 @@
|
||||
diff --git a/node_modules/@shikijs/transformers/dist/index.mjs b/node_modules/@shikijs/transformers/dist/index.mjs
|
||||
index db4db63..c7f3ea2 100644
|
||||
--- a/node_modules/@shikijs/transformers/dist/index.mjs
|
||||
+++ b/node_modules/@shikijs/transformers/dist/index.mjs
|
||||
@@ -410,6 +410,9 @@ function transformerRenderWhitespace(options = {}) {
|
||||
return token;
|
||||
if (position === "trailing" && index !== last)
|
||||
return token;
|
||||
+ if (token.children.length === 0) {
|
||||
+ return token;
|
||||
+ }
|
||||
const node = token.children[0];
|
||||
if (node.type !== "text" || !node.value)
|
||||
return token;
|
||||
BIN
public/static/images/shortcuts-bg-mini.png
Normal file
BIN
public/static/images/shortcuts-bg-mini.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -63,15 +63,15 @@ import Link from './Link.astro'
|
||||
// ]
|
||||
const SHORTS_CUTS_CLASS_NAMES: string[] = [
|
||||
'z-10 max-w max-h mt-[2.6em] inline-block sm:mt-[2.35em]',
|
||||
'z-10 max-w max-h ml-[0.1em] mt-[-3.7em] inline-block sm:ml-[0em] sm:mt-[-3.4em]',
|
||||
'z-10 max-w max-h ml-[-0.1em] mt-[2.5em] inline-block sm:ml-[0em] sm:mt-[2.5em]',
|
||||
'z-10 max-w max-h mt-[-4em] inline-block sm:mt-[-3em]',
|
||||
'z-10 max-w max-h ml-[0.1em] mt-[-4.7em] inline-block sm:ml-[0em] sm:mt-[-3.4em]',
|
||||
'z-10 max-w max-h ml-[-0.2em] mt-[3.0em] inline-block sm:ml-[0em] sm:mt-[2.5em]',
|
||||
'z-10 max-w max-h mt-[-5.2em] inline-block sm:mt-[-3em]',
|
||||
'z-10 max-w max-h mt-[0.1em] inline-block sm:mt-[0.6em]',
|
||||
'z-10 max-w max-h ml-[0.1em] mt-[-6.5em] inline-block sm:ml-[0em] sm:mt-[-5.3em]',
|
||||
'z-10 max-w max-h mt-[0.2em] inline-block sm:mt-[0.6em]',
|
||||
'z-10 max-w max-h mt-[-6.5em] inline-block sm:mt-[-5.3em]',
|
||||
'z-10 max-w max-h mt-[1.49em] inline-block sm:mt-[1em]',
|
||||
'z-10 max-w max-h mt-[-5.2em] inline-block sm:mt-[-4.3em]'
|
||||
'z-10 max-w max-h ml-[0.15em] mt-[-7.5em] inline-block sm:ml-[0em] sm:mt-[-4.8em]',
|
||||
'z-10 max-w max-h ml-[-0.15em] mt-[0.2em] inline-block sm:mt-[0.9em] sm:ml-[0em]',
|
||||
'z-10 max-w max-h mt-[-7.5em] inline-block sm:mt-[-4.8em]',
|
||||
'z-10 max-w max-h mt-[1.88em] inline-block sm:mt-[1em]',
|
||||
'z-10 max-w max-h ml-[0.15em] mt-[-5.8em] inline-block sm:ml-[0em] sm:mt-[-4em]'
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
406
src/components/DevShortcutsHexagon.astro
Normal file
406
src/components/DevShortcutsHexagon.astro
Normal file
@@ -0,0 +1,406 @@
|
||||
---
|
||||
import { DEV_LINKS } from '@/consts'
|
||||
import { Icon } from 'astro-icon/components'
|
||||
|
||||
// ------------- Hex math helpers (ported from react-hexgrid) -------------
|
||||
type Point = { x: number; y: number }
|
||||
type HexCoord = { q: number; r: number; s: number }
|
||||
type Orientation = {
|
||||
f0: number
|
||||
f1: number
|
||||
f2: number
|
||||
f3: number
|
||||
b0: number
|
||||
b1: number
|
||||
b2: number
|
||||
b3: number
|
||||
startAngle: number
|
||||
}
|
||||
type LayoutDimension = {
|
||||
size: Point
|
||||
spacing: number
|
||||
origin: Point
|
||||
orientation: Orientation
|
||||
}
|
||||
|
||||
const SQRT3 = Math.sqrt(3)
|
||||
const ORIENTATION_FLAT: Orientation = {
|
||||
f0: 3 / 2,
|
||||
f1: 0,
|
||||
f2: SQRT3 / 2,
|
||||
f3: SQRT3,
|
||||
b0: 2 / 3,
|
||||
b1: 0,
|
||||
b2: -1 / 3,
|
||||
b3: SQRT3 / 3,
|
||||
startAngle: 0,
|
||||
}
|
||||
|
||||
const BASE_HEX_SIZE = 104
|
||||
const HEX_SIZE = 168
|
||||
const BORDER_WIDTH = 24
|
||||
|
||||
const layout: LayoutDimension = {
|
||||
size: { x: HEX_SIZE, y: HEX_SIZE },
|
||||
spacing: 1,
|
||||
origin: { x: 0, y: 0 },
|
||||
orientation: ORIENTATION_FLAT,
|
||||
}
|
||||
|
||||
const GRID_WIDTH = 5
|
||||
const GRID_HEIGHT = 5
|
||||
|
||||
function calculatePolygonPoints(size: number, flat = false) {
|
||||
const angleOffset = flat ? 0 : Math.PI / 6
|
||||
const corners: Point[] = []
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const angle = (2 * Math.PI * i) / 6 + angleOffset
|
||||
corners.push({
|
||||
x: size * Math.cos(angle),
|
||||
y: size * Math.sin(angle),
|
||||
})
|
||||
}
|
||||
|
||||
return corners.map((corner) => `${corner.x.toFixed(2)},${corner.y.toFixed(2)}`).join(' ')
|
||||
}
|
||||
|
||||
function coordKey(hex: HexCoord) {
|
||||
return `${hex.q},${hex.r},${hex.s}`
|
||||
}
|
||||
|
||||
function hexToPixel(hex: HexCoord, layout: LayoutDimension): Point {
|
||||
const { orientation: M, size, spacing, origin } = layout
|
||||
let x = (M.f0 * hex.q + M.f1 * hex.r) * size.x
|
||||
let y = (M.f2 * hex.q + M.f3 * hex.r) * size.y
|
||||
x *= spacing
|
||||
y *= spacing
|
||||
return { x: x + origin.x, y: y + origin.y }
|
||||
}
|
||||
|
||||
function rectangle(mapWidth: number, mapHeight: number): HexCoord[] {
|
||||
const cells: HexCoord[] = []
|
||||
for (let r = 0; r < mapHeight; r++) {
|
||||
const offset = Math.floor(r / 2)
|
||||
for (let q = -offset; q < mapWidth - offset; q++) {
|
||||
cells.push({ q, r, s: -q - r })
|
||||
}
|
||||
}
|
||||
return cells
|
||||
}
|
||||
|
||||
function hexagon(mapRadius: number): HexCoord[] {
|
||||
const cells: HexCoord[] = []
|
||||
for (let q = -mapRadius; q <= mapRadius; q++) {
|
||||
const r1 = Math.max(-mapRadius, -q - mapRadius)
|
||||
const r2 = Math.min(mapRadius, -q + mapRadius)
|
||||
for (let r = r1; r <= r2; r++) {
|
||||
cells.push({ q, r, s: -q - r })
|
||||
}
|
||||
}
|
||||
return cells
|
||||
}
|
||||
|
||||
// 根据cells的下标与对应值控制显示在哪些格子里
|
||||
const ACTIVE_COORDS: HexCoord[] = [
|
||||
// { q: -1, r: 0, s: 1 },
|
||||
// { q: 0, r: 0, s: 0 },
|
||||
// { q: 1, r: 0, s: -1 },
|
||||
{ q: 2, r: 0, s: -2 },
|
||||
{ q: 3, r: 0, s: -3 },
|
||||
{ q: 0, r: 1, s: -1 },
|
||||
{ q: 1, r: 1, s: -2 },
|
||||
{ q: 2, r: 1, s: -3 },
|
||||
{ q: 0, r: 2, s: -2 },
|
||||
{ q: 1, r: 2, s: -3 },
|
||||
{ q: 2, r: 2, s: -4 },
|
||||
{ q: 0, r: 3, s: -3 },
|
||||
{ q: 1, r: 3, s: -4 },
|
||||
{ q: 2, r: 3, s: -5 },
|
||||
]
|
||||
|
||||
const gridCoords = rectangle(GRID_WIDTH, GRID_HEIGHT)
|
||||
const gridKeyMap = new Map(gridCoords.map((hex) => [coordKey(hex), hex]))
|
||||
|
||||
const preferredCoords = ACTIVE_COORDS.map((hex) => gridKeyMap.get(coordKey(hex))).filter(
|
||||
Boolean,
|
||||
) as HexCoord[]
|
||||
|
||||
const preferredKeySet = new Set(preferredCoords.map((hex) => coordKey(hex)))
|
||||
const remainingGridCoords = gridCoords.filter((hex) => !preferredKeySet.has(coordKey(hex)))
|
||||
|
||||
let orderedActiveSlots = [...preferredCoords, ...remainingGridCoords]
|
||||
|
||||
if (DEV_LINKS.length > orderedActiveSlots.length) {
|
||||
const usedKeys = new Set(orderedActiveSlots.map((hex) => coordKey(hex)))
|
||||
let radius = Math.max(GRID_WIDTH, GRID_HEIGHT)
|
||||
|
||||
while (orderedActiveSlots.length < DEV_LINKS.length) {
|
||||
radius += 1
|
||||
for (const hex of hexagon(radius)) {
|
||||
const key = coordKey(hex)
|
||||
if (usedKeys.has(key)) continue
|
||||
orderedActiveSlots.push(hex)
|
||||
usedKeys.add(key)
|
||||
if (orderedActiveSlots.length === DEV_LINKS.length) break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const assignedCoords = orderedActiveSlots.slice(0, DEV_LINKS.length)
|
||||
const coordToLink = new Map<string, (typeof DEV_LINKS)[number]>()
|
||||
assignedCoords.forEach((hex, index) => {
|
||||
const link = DEV_LINKS[index]
|
||||
if (!hex || !link) return
|
||||
coordToLink.set(coordKey(hex), link)
|
||||
})
|
||||
|
||||
const renderCoordMap = new Map<string, HexCoord>()
|
||||
gridCoords.forEach((hex) => renderCoordMap.set(coordKey(hex), hex))
|
||||
assignedCoords.forEach((hex) => {
|
||||
if (!hex) return
|
||||
renderCoordMap.set(coordKey(hex), hex)
|
||||
})
|
||||
|
||||
const renderCoords = Array.from(renderCoordMap.values())
|
||||
|
||||
const hexagonPoints = calculatePolygonPoints(layout.size.x, true)
|
||||
// const circleRadius = layout.size.x * 0.84
|
||||
const circleRadius = 80
|
||||
|
||||
const baseCells = renderCoords.map((hex) => ({
|
||||
hex,
|
||||
link: coordToLink.get(coordKey(hex)) ?? null,
|
||||
center: hexToPixel(hex, layout),
|
||||
}))
|
||||
|
||||
const xs = baseCells.map((cell) => cell.center.x)
|
||||
const ys = baseCells.map((cell) => cell.center.y)
|
||||
const padding = layout.size.x * 0.45
|
||||
const minX = Math.min(...xs) - padding
|
||||
const maxX = Math.max(...xs) + padding
|
||||
const minY = Math.min(...ys) - padding
|
||||
const maxY = Math.max(...ys) + padding
|
||||
const sizeScale = HEX_SIZE / BASE_HEX_SIZE
|
||||
const rawWidth = maxX - minX
|
||||
const rawHeight = maxY - minY
|
||||
const centerX = (minX + maxX) / 2
|
||||
const centerY = (minY + maxY) / 2
|
||||
const viewWidth = rawWidth / sizeScale
|
||||
const viewHeight = rawHeight / sizeScale
|
||||
const viewMinX = centerX - viewWidth / 2
|
||||
const viewMinY = centerY - viewHeight / 2
|
||||
const viewBox = `${viewMinX} ${viewMinY} ${viewWidth} ${viewHeight}`
|
||||
const iconClipId = 'hexIconClip'
|
||||
|
||||
const cells = baseCells.map((cell) => ({
|
||||
...cell,
|
||||
showContent: Boolean(cell.link),
|
||||
}))
|
||||
---
|
||||
|
||||
<svg
|
||||
class="hex-grid hex-grid--polygons"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox={viewBox}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
>
|
||||
<g class="hex-grid__group">
|
||||
{cells.map(({ link, center }, index) => {
|
||||
const isGhost = !link
|
||||
return (
|
||||
<polygon
|
||||
class={`hexagon-bg ${isGhost ? 'hexagon-bg--ghost' : ''}`}
|
||||
points={hexagonPoints}
|
||||
fill="transparent"
|
||||
stroke="#252525"
|
||||
stroke-width={BORDER_WIDTH}
|
||||
transform={`translate(${center.x}, ${center.y})`}
|
||||
data-index={index}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
class="hex-grid hex-grid--icons"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox={viewBox}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
>
|
||||
<defs>
|
||||
<clipPath id={iconClipId} clipPathUnits="userSpaceOnUse">
|
||||
<circle cx="0" cy="0" r={circleRadius} />
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<g class="hex-grid__group">
|
||||
{cells.map(({ link, center, showContent }, index) => {
|
||||
if (!link || !showContent) return null
|
||||
return (
|
||||
<g
|
||||
class="hexagon-wrapper"
|
||||
transform={`translate(${center.x}, ${center.y})`}
|
||||
data-index={index}
|
||||
>
|
||||
<a
|
||||
href={link.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={link.label}
|
||||
>
|
||||
<title>{link.title}</title>
|
||||
<g class="icon-group">
|
||||
<circle
|
||||
r={circleRadius}
|
||||
fill="hsl(var(--secondary))"
|
||||
fill-opacity="0.45"
|
||||
stroke="hsl(var(--border))"
|
||||
stroke-width={Math.max(4, BORDER_WIDTH * 0.32)}
|
||||
class="icon-circle z-10"
|
||||
/>
|
||||
|
||||
{link.icon.startsWith('mdi:') ? (
|
||||
<foreignObject
|
||||
x={-(layout.size.x * 0.7)}
|
||||
y={-(layout.size.y * 0.7)}
|
||||
width={layout.size.x * 1.4}
|
||||
height={layout.size.y * 1.4}
|
||||
clip-path={`url(#${iconClipId})`}
|
||||
class="icon-foreign"
|
||||
>
|
||||
<div class="icon-container">
|
||||
<Icon
|
||||
name={link.icon}
|
||||
class="hexagon-icon"
|
||||
style="color: rgb(233, 211, 182);"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</foreignObject>
|
||||
) : (
|
||||
<image
|
||||
href={link.icon}
|
||||
x={-layout.size.x * 0.3}
|
||||
y={-layout.size.y * 0.3}
|
||||
width={layout.size.x * 0.6}
|
||||
height={layout.size.y * 0.6}
|
||||
clip-path={`url(#${iconClipId})`}
|
||||
class="hexagon-image"
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
</a>
|
||||
</g>
|
||||
)
|
||||
})}
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<style>
|
||||
.hex-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
.hex-grid--polygons {
|
||||
z-index: 1;
|
||||
}
|
||||
.hex-grid--icons {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.hex-grid__group {
|
||||
transform-origin: center;
|
||||
transform: translateX(-114px) translateY(60px) scale(1.08);
|
||||
}
|
||||
|
||||
.hexagon-wrapper {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.hexagon-wrapper a {
|
||||
outline: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hexagon-wrapper a title {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hexagon-bg,
|
||||
.icon-circle,
|
||||
.icon-group {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.icon-circle {
|
||||
stroke-width: 4px;
|
||||
transform-origin: center;
|
||||
transform-box: fill-box;
|
||||
}
|
||||
.icon-group {
|
||||
transform-origin: center;
|
||||
transform-box: fill-box;
|
||||
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.45));
|
||||
}
|
||||
|
||||
.hexagon-wrapper:hover .icon-circle {
|
||||
fill-opacity: 0.85;
|
||||
stroke: hsl(var(--primary));
|
||||
stroke-width: 4px;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.hexagon-wrapper:hover .icon-group {
|
||||
transform: rotate(12deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.hexagon-bg--ghost {
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.hexagon-icon {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
max-width: 88px;
|
||||
max-height: 88px;
|
||||
}
|
||||
|
||||
.hexagon-image {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.icon-foreign {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:global(.hexagon-icon) {
|
||||
color: rgb(233, 211, 182) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -15,8 +15,8 @@ import logo from '../../public/static/logo.webp'
|
||||
>
|
||||
<Container>
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 py-4 ">
|
||||
<Link href="/">
|
||||
<Image src={logo} alt="Logo" class="size-8 -scale-x-100 border-2 border-[color:rgba(241,140,110,0.4)] shadow-[4px_4px_0_rgba(0,0,0,0.2)]" />
|
||||
<Link href="/" class="logo-link">
|
||||
<Image src={logo} alt="Logo" class="size-8 -scale-x-100 border-2 border-[color:rgba(241,140,110,0.4)] shadow-[4px_4px_0_rgba(0,0,0,0.2)] transition-all duration-300" />
|
||||
</Link>
|
||||
<div class="flex items-center gap-2 sm:gap-4">
|
||||
<nav class="hidden items-center gap-2 text-base sm:flex sm:gap-3">
|
||||
@@ -38,3 +38,31 @@ import logo from '../../public/static/logo.webp'
|
||||
</div>
|
||||
</Container>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
.logo-link:hover img {
|
||||
border-color: hsl(var(--primary));
|
||||
box-shadow:
|
||||
0 0 12px rgba(241, 140, 110, 0.6),
|
||||
0 0 24px rgba(241, 140, 110, 0.4),
|
||||
0 0 36px rgba(241, 140, 110, 0.2),
|
||||
4px 4px 0 rgba(0, 0, 0, 0.2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
nav :global(.pixel-button:hover) {
|
||||
box-shadow:
|
||||
0 0 12px rgba(241, 140, 110, 0.5),
|
||||
0 0 24px rgba(241, 140, 110, 0.3),
|
||||
0 0 36px rgba(241, 140, 110, 0.15),
|
||||
4px 4px 0 rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.dark nav :global(.pixel-button:hover) {
|
||||
box-shadow:
|
||||
0 0 12px rgba(241, 140, 110, 0.5),
|
||||
0 0 24px rgba(241, 140, 110, 0.3),
|
||||
0 0 36px rgba(241, 140, 110, 0.15),
|
||||
4px 4px 0 rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import AvatarComponent from '@/components/ui/avatar'
|
||||
|
||||
const AuthorPresence = () => {
|
||||
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden sm:aspect-square">
|
||||
<div className="relative overflow-hidden sm:aspect-square select-none" style={{ cursor: 'default' }}>
|
||||
<div className="grid size-full grid-rows-4">
|
||||
<div className="bg-secondary/50"></div>
|
||||
<div className="row-span-3 flex flex-col gap-3 p-3">
|
||||
@@ -26,22 +26,22 @@ const AuthorPresence = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-1 rounded-xl bg-secondary/50 p-3">
|
||||
<span className="text-base leading-none">jimlee</span>
|
||||
<span className="text-xs leading-none text-muted-foreground">
|
||||
<div className="flex flex-col gap-y-1 rounded-xl bg-secondary/50 p-3 select-none">
|
||||
<span className="text-base leading-none select-none cursor-default">jimlee</span>
|
||||
<span className="text-xs leading-none text-muted-foreground select-none cursor-default">
|
||||
li@2ha.me
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex grow rounded-xl bg-secondary/50 px-3 py-2">
|
||||
<div className="flex size-full flex-col items-center justify-center gap-1">
|
||||
<div className="flex grow rounded-xl bg-secondary/50 px-3 py-2 select-none">
|
||||
<div className="flex size-full flex-col items-center justify-center gap-1 select-none">
|
||||
<img
|
||||
src="/static/images/lieflat.svg"
|
||||
alt="No Status Image"
|
||||
width={64}
|
||||
height={64}
|
||||
className="h-full rounded-lg"
|
||||
className="h-full rounded-lg select-none"
|
||||
/>
|
||||
<div className="text-[10px] text-muted-foreground">
|
||||
<div className="text-[10px] text-muted-foreground select-none cursor-default">
|
||||
但行好事,莫问前程。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,10 +54,10 @@ export const DEV_LINKS: DevLink[] = [
|
||||
icon: 'mdi:git',
|
||||
},
|
||||
{
|
||||
href: 'https://maven.2ha.me',
|
||||
href: 'https://img.2ha.me',
|
||||
label: 'Nexus',
|
||||
title: 'Maven仓库',
|
||||
icon: 'mdi:chart-doughnut-variant',
|
||||
title: '图床',
|
||||
icon: 'mdi:image-multiple',
|
||||
},
|
||||
{
|
||||
href: 'https://dms.2ha.me',
|
||||
@@ -69,13 +69,13 @@ export const DEV_LINKS: DevLink[] = [
|
||||
href: 'https://p.2ha.me',
|
||||
label: 'Zfile',
|
||||
title: '网盘',
|
||||
icon: 'mdi:cloud-arrow-up',
|
||||
icon: 'mdi:harddisk',
|
||||
},
|
||||
{
|
||||
href: 'https://photo.2ha.me',
|
||||
label: 'immich',
|
||||
title: '相册',
|
||||
icon: 'mdi:camera',
|
||||
href: 'https://tz.2ha.me',
|
||||
label: 'VPS Monitor',
|
||||
title: '探针',
|
||||
icon: 'mdi:chart-areaspline',
|
||||
},
|
||||
{
|
||||
href: 'https://f.2ha.me',
|
||||
@@ -83,7 +83,12 @@ export const DEV_LINKS: DevLink[] = [
|
||||
title: '文件服务器',
|
||||
icon: 'mdi:file-arrow-up-down-outline',
|
||||
},
|
||||
{ href: 'https://v.2ha.me', label: 'Emby', title: 'Emby', icon: 'mdi:emby' },
|
||||
{
|
||||
href: 'https://status.2ha.me',
|
||||
label: 'Domain Status',
|
||||
title: '站点检测',
|
||||
icon: 'mdi:cloud-check'
|
||||
},
|
||||
{
|
||||
href: 'https://in.2ha.me',
|
||||
label: '2ha.me statistics',
|
||||
@@ -95,6 +100,7 @@ export const DEV_LINKS: DevLink[] = [
|
||||
label: '妙妙屋',
|
||||
title: '个人Clash订阅管理工具',
|
||||
icon: '/static/mmw.svg',
|
||||
// icon: 'mdi:cat',
|
||||
},
|
||||
{
|
||||
href: 'https://1ms.cc',
|
||||
|
||||
@@ -30,6 +30,6 @@ export function transformerNotationSkip(
|
||||
return false
|
||||
},
|
||||
undefined, // remove empty lines
|
||||
)
|
||||
) as ShikiTransformer
|
||||
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a])
|
||||
<Layout title="Blog" description="Blog">
|
||||
<!-- Fixed Sidebar -->
|
||||
<aside
|
||||
class="hidden xl:block fixed left-4 top-32 w-[240px] px-4 py-3 rounded-sm bg-background/80 backdrop-blur-sm border-2 border-[color:rgba(241,140,110,0.22)] shadow-[4px_4px_0_rgba(0,0,0,0.22)] dark:shadow-[4px_4px_0_rgba(0,0,0,0.65)] z-10"
|
||||
class="hidden xl:block fixed top-1/2 -translate-y-1/2 w-[240px] px-4 py-3 rounded-sm bg-background/80 backdrop-blur-sm border-2 border-[color:rgba(241,140,110,0.22)] shadow-[4px_4px_0_rgba(0,0,0,0.22)] dark:shadow-[4px_4px_0_rgba(0,0,0,0.65)] z-10"
|
||||
>
|
||||
<Link
|
||||
href={`/blog`}
|
||||
@@ -127,3 +127,21 @@ const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a])
|
||||
/>
|
||||
</Container>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
ul li > :global(div:hover) {
|
||||
box-shadow:
|
||||
0 0 16px rgba(241, 140, 110, 0.5),
|
||||
0 0 32px rgba(241, 140, 110, 0.3),
|
||||
0 0 48px rgba(241, 140, 110, 0.15),
|
||||
6px 6px 0 rgba(0, 0, 0, 0.26) !important;
|
||||
}
|
||||
|
||||
.dark ul li > :global(div:hover) {
|
||||
box-shadow:
|
||||
0 0 16px rgba(241, 140, 110, 0.5),
|
||||
0 0 32px rgba(241, 140, 110, 0.3),
|
||||
0 0 48px rgba(241, 140, 110, 0.15),
|
||||
6px 6px 0 rgba(0, 0, 0, 0.75) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,7 +12,8 @@ import { getCollection } from 'astro:content'
|
||||
import GiteaCalendar from '@/components/custom/GiteaCalendar'
|
||||
import Music163Player from '@/components/custom/Music163Player'
|
||||
import RandomAnimeBackground from '@/components/custom/RandomAnimeBackgrounds'
|
||||
import DevShortCuts from '@/components/DevShortCuts.astro'
|
||||
// import DevShortCuts from '@/components/DevShortCuts.astro'
|
||||
import DevShortcutsHexagon from '@/components/DevShortcutsHexagon.astro'
|
||||
|
||||
const latestPost = await getCollection('blog').then((posts: any[]) =>
|
||||
posts
|
||||
@@ -28,7 +29,7 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
|
||||
|
||||
<Layout title="主页" description={SITE.DESCRIPTION}>
|
||||
<section
|
||||
class="mx-auto grid w-full max-w-[300px] grid-cols-1 gap-4 [grid-template-areas:'a'_'b'_'d'_'i'_'e'_'g'_'f'_'j'_'k'] [grid-template-rows:repeat(9,300px)] *:border-2 *:border-[color-mix(in_srgb,hsl(var(--primary))_22%,hsl(var(--border)))] *:bg-secondary/25 *:bg-cover *:bg-center *:bg-no-repeat *:overflow-hidden *:max-h-[300px] *:max-w-[300px] *:justify-self-center *:[box-shadow:4px_4px_0_rgba(0,0,0,0.22)] *:transition-all *:duration-200 sm:max-w-screen-sm sm:grid-cols-2 sm:px-4 sm:[grid-template-areas:'a_a'_'b_d'_'e_e'_'j_g'_'f_f'_'i_k'] sm:[grid-template-rows:repeat(6,300px)] sm:*:max-w-[615px] xl:max-w-screen-xl xl:grid-cols-4 xl:px-4 xl:[grid-template-areas:'a_a_b_g'_'d_e_e_i'_'k_j_f_f'] xl:[grid-template-rows:repeat(3,300px)] xl:*:max-w-none dark:*:[box-shadow:4px_4px_0_rgba(0,0,0,0.65)] xl:[&:hover:has(>.has-overlay:hover)>.first>.overlay]:opacity-100 xl:[&>*:not(.first):hover_.overlay]:opacity-100"
|
||||
class="mx-auto grid w-full grid-cols-1 gap-4 px-4 [grid-template-areas:'a'_'d'_'b'_'i'_'e'_'g'_'f'_'j'_'k'] [grid-template-rows:repeat(9,auto)] *:border-2 *:border-[color-mix(in_srgb,hsl(var(--primary))_22%,hsl(var(--border)))] *:bg-secondary/25 *:bg-cover *:bg-center *:bg-no-repeat *:overflow-hidden *:w-full *:[box-shadow:4px_4px_0_rgba(0,0,0,0.22)] *:transition-all *:duration-200 sm:max-w-screen-sm sm:grid-cols-2 sm:[grid-template-areas:'a_a'_'b_d'_'e_e'_'j_g'_'f_f'_'i_k'] sm:[grid-template-rows:repeat(6,300px)] sm:*:max-w-[615px] xl:max-w-screen-xl xl:grid-cols-4 xl:[grid-template-areas:'a_a_b_g'_'d_e_e_i'_'k_j_f_f'] xl:[grid-template-rows:repeat(3,300px)] xl:*:max-w-none dark:*:[box-shadow:4px_4px_0_rgba(0,0,0,0.65)] xl:[&:hover:has(>.has-overlay:hover)>.first>.overlay]:opacity-100 xl:[&>*:not(.first):hover_.overlay]:opacity-100"
|
||||
aria-label="Personal information and activity grid"
|
||||
>
|
||||
<div
|
||||
@@ -42,19 +43,20 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
|
||||
|
||||
<div
|
||||
class="has-overlay relative grid aspect-square grid-cols-4 grid-rows-3 items-center
|
||||
justify-center bg-[url('/static/honeycomb.webp')] [grid-area:b] short-cuts-template hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.26)] hover:-translate-y-1 hover:border-[color-mix(in_srgb,hsl(var(--primary))_40%,hsl(var(--border)))] dark:hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.75)]"
|
||||
justify-center [grid-area:b] short-cuts-template hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.26)] hover:-translate-y-1 hover:border-[color-mix(in_srgb,hsl(var(--primary))_40%,hsl(var(--border)))] dark:hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.75)]"
|
||||
style="grid-template-columns: 2.85fr 2.95fr 2.9fr 1.3fr;grid-template-rows: 4.7fr 3.4fr 2.8fr;"
|
||||
aria-label="Developer Stack Shortcuts"
|
||||
>
|
||||
<img
|
||||
class="overlay absolute no-repeat w-full max-w-fit justify-center object-cover z-9"
|
||||
src="/static/images/shortcuts-bg.png"
|
||||
class="overlay absolute bottom-0 right-0 no-repeat max-w-[28%] object-contain z-[100]"
|
||||
src="/static/images/shortcuts-bg-mini.png"
|
||||
/>
|
||||
<!-- <DevStackIconsCloud client:load/> -->
|
||||
<DevShortCuts />
|
||||
<!-- <DevShortCuts /> -->
|
||||
<DevShortcutsHexagon />
|
||||
</div>
|
||||
|
||||
<div class="relative overflow-hidden [grid-area:d] sm:aspect-square min-h-[300px] min-w-[300px] hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.26)] hover:-translate-y-1 hover:border-[color-mix(in_srgb,hsl(var(--primary))_40%,hsl(var(--border)))] dark:hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.75)]">
|
||||
<div class="relative overflow-hidden [grid-area:d] aspect-square hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.26)] hover:-translate-y-1 hover:border-[color-mix(in_srgb,hsl(var(--primary))_40%,hsl(var(--border)))] dark:hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.75)]">
|
||||
<AuthorPresence client:load />
|
||||
</div>
|
||||
|
||||
@@ -63,7 +65,7 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
|
||||
style="grid-template-rows: 9fr 1fr; "
|
||||
>
|
||||
<div
|
||||
class="overlay absolute inset-0 size-full rounded-3xl bg-[url('/static/images/lastblogbg-sm.webp')] bg-cover bg-center bg-no-repeat transition-opacity duration-200 sm:bg-[url('/static/images/lastblogbg.webp')] xl:opacity-100"
|
||||
class="overlay absolute inset-0 size-full rounded-3xl bg-[url('/static/images/lastblogbg-sm.webp')] bg-cover bg-no-repeat transition-opacity duration-200 sm:bg-[url('/static/images/lastblogbg.webp')] xl:opacity-100"
|
||||
>
|
||||
</div>
|
||||
{
|
||||
@@ -169,3 +171,21 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
section > div:hover {
|
||||
box-shadow:
|
||||
0 0 16px rgba(241, 140, 110, 0.5),
|
||||
0 0 32px rgba(241, 140, 110, 0.3),
|
||||
0 0 48px rgba(241, 140, 110, 0.15),
|
||||
6px 6px 0 rgba(0, 0, 0, 0.26) !important;
|
||||
}
|
||||
|
||||
.dark section > div:hover {
|
||||
box-shadow:
|
||||
0 0 16px rgba(241, 140, 110, 0.5),
|
||||
0 0 32px rgba(241, 140, 110, 0.3),
|
||||
0 0 48px rgba(241, 140, 110, 0.15),
|
||||
6px 6px 0 rgba(0, 0, 0, 0.75) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -37,3 +37,21 @@ const years = Object.keys(projectsByYear).sort(
|
||||
</div>
|
||||
</Container>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
ul li > :global(div:hover) {
|
||||
box-shadow:
|
||||
0 0 16px rgba(241, 140, 110, 0.5),
|
||||
0 0 32px rgba(241, 140, 110, 0.3),
|
||||
0 0 48px rgba(241, 140, 110, 0.15),
|
||||
6px 6px 0 rgba(0, 0, 0, 0.26) !important;
|
||||
}
|
||||
|
||||
.dark ul li > :global(div:hover) {
|
||||
box-shadow:
|
||||
0 0 16px rgba(241, 140, 110, 0.5),
|
||||
0 0 32px rgba(241, 140, 110, 0.3),
|
||||
0 0 48px rgba(241, 140, 110, 0.15),
|
||||
6px 6px 0 rgba(0, 0, 0, 0.75) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -486,9 +486,41 @@
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
/* Prevent text selection cursor on interactive elements */
|
||||
/* Prevent text selection cursor on all elements by default */
|
||||
* {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Allow text selection in content areas */
|
||||
article,
|
||||
article *,
|
||||
.prose,
|
||||
.prose *,
|
||||
input,
|
||||
textarea,
|
||||
[contenteditable="true"] {
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
/* Only inputs and textareas should have text cursor */
|
||||
input,
|
||||
textarea,
|
||||
[contenteditable="true"] {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Ensure proper cursor on interactive elements */
|
||||
button,
|
||||
a,
|
||||
div,
|
||||
img,
|
||||
svg,
|
||||
.pixel-button,
|
||||
[role="button"],
|
||||
[role="link"] {
|
||||
@@ -496,6 +528,42 @@
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Children of interactive elements should not have text cursor */
|
||||
button *,
|
||||
a *,
|
||||
.pixel-button *,
|
||||
[role="button"] *,
|
||||
[role="link"] * {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Images should never show text cursor */
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Allow text selection for content elements but keep default cursor */
|
||||
p,
|
||||
span,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
li {
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
Reference in New Issue
Block a user