This commit is contained in:
2026-01-19 17:41:22 +08:00
parent 6b5d6e6ca5
commit dce351a9da
49 changed files with 827 additions and 605 deletions

156
AGENTS.md Normal file
View File

@@ -0,0 +1,156 @@
# AGENTS.md
This file provides guidelines for agentic coding assistants working on this Astro blog repository.
## Build & Development Commands
```bash
# Development server (runs on port 1234)
bun run dev # or bun run start
# Production build
bun run build
# Preview production build
bun run preview
# Format code (uses Prettier with Astro plugins)
bun run prettier
# Install dependencies
bun install
# Apply patches after install (runs automatically via postinstall)
patch-package
```
**Testing**: No test framework is currently configured in this project.
## Tech Stack
- **Framework**: Astro 5.7.13 (SSR mode, output: 'server')
- **UI**: React 19.0.0 via @astrojs/react
- **Styling**: Tailwind CSS v4.1.7 via @tailwindcss/vite
- **TypeScript**: 5.8.3 with strict mode enabled
- **Content**: Notion API integration for blog posts
- **UI Components**: shadcn/ui (new-york style, neutral base color)
- **Code Blocks**: astro-expressive-code with collapsible sections
- **Math/Code**: KaTeX for math, rehype-pretty-code for syntax highlighting
## Code Style Guidelines
### Formatting (Prettier)
- Single quotes (`'`), no semicolons, trailing comma (es5 style)
- Plugins: `prettier-plugin-astro`, `prettier-plugin-tailwindcss`, `prettier-plugin-astro-organize-imports`
- Format with: `bun run prettier`
### TypeScript Configuration
- Strict mode enabled (`strictNullChecks: true`)
- Path aliases: `@/*``./src/*`
- JSX: `react-jsx` with React as import source
### Imports
- Use path alias `@/` for all internal imports:
- `@/components/*` → components
- `@/lib/*` → utility functions
- `@/styles/*` → stylesheets
- External imports: named imports preferred
- React components: `import { Button } from '@/components/ui/button'`
- Astro components: `import Header from '@/components/Header.astro'`
### Component Patterns
**Astro Components (.astro)**:
- Use for main layout and content components
- Props interface defined in frontmatter: `export interface Props { ... }`
- Use `class` prop for CSS classes (not `className`)
- Extract props: `const { class: className } = Astro.props`
**React Components (.tsx)**:
- Use for interactive UI components (shadcn/ui pattern)
- Use `className` for CSS classes
- Use `cn()` utility for conditional class merging
- Radix UI primitives for accessibility
- class-variance-authority (cva) for component variants
**Example:**
```tsx
import { cn } from '@/lib/utils'
import { cva } from 'class-variance-authority'
const variants = cva('base-classes', {
variants: { variant: { default: '...', outline: '...' } }
})
function Component({ className, variant, ...props }) {
return <div className={cn(variants({ variant }), className)} {...props} />
}
```
### Styling
**Tailwind CSS**:
- Primary styling framework (v4, no config file)
- Use `cn()` from `@/lib/utils` for merging conditional classes
- Dark mode via `data-theme` attribute (not media query)
- CSS variables defined in `src/styles/global.css` for theming
- OKLCH color space for all colors
**CSS Variables**:
- Light mode: `:root` selector
- Dark mode: `[data-theme='dark']` selector
- Semantic tokens: `--background`, `--foreground`, `--primary`, `--muted`, `--border`, `--ring`
- Special tokens: `--notion-surface`, `--notion-border`, `--fg`, `--anchor-border`
**Custom CSS**:
- `global.css`: Main stylesheet with Tailwind imports
- `notion-color.css`: Notion integration colors
- `typography.css`: Text styling
- `syntax-coloring.css`: Code highlighting styles
### Type Definitions
- All shared types in `src/lib/interfaces.ts`
- Notion API types: Block, Paragraph, Heading1-3, Image, Code, etc.
- Use explicit types for all props and function parameters
### Utility Functions
- `cn()` from `@/lib/utils`: Merge CSS classes (clsx + tailwind-merge)
- `formatDate()`, `readingTime()`, `calculateWordCountFromHtml()` in `@/lib/utils`
- Blog helpers in `@/lib/blog-helpers.ts`: URL parsing, slug generation, Notion image handling
- Style helpers in `@/lib/style-helpers.ts`: CSS class transformations
### Component Structure
- Layouts: `src/layouts/Layout.astro`
- Pages: `src/pages/*.astro` and `src/pages/**/*.astro`
- Components: `src/components/*.astro` and `src/components/ui/*.tsx`
- Notion block components: `src/components/notion/*.astro`
### Naming Conventions
- Components: PascalCase (`Header.astro`, `Button.tsx`)
- Files: PascalCase for components, kebab-case for utilities
- CSS classes: Tailwind utilities, custom classes in kebab-case
- Functions: camelCase (`cn()`, `formatDate()`)
- Constants: UPPER_SNAKE_CASE (`SITE`, `ENABLE_LIGHTBOX`)
### Error Handling
- Use try-catch for async operations
- Console errors for image URL parsing failures
- Graceful null/undefined checks for optional properties
- Return early for invalid data: `if (!block.Paragraph) return null`
### Inline Scripts
- Use `is:inline` for client-side scripts that need to execute immediately
- Use standard `<script>` tags for scoped client-side logic
- Listen to Astro events: `astro:after-swap` for navigation updates
### Content Pattern (Notion Integration)
- Blog posts fetched from Notion API
- Block-based rendering: `NotionBlocks.astro` recursively renders block arrays
- Each block type has a dedicated component (`Paragraph.astro`, `Heading1.astro`, etc.)
- Rich text rendering via `RichText.astro` for text formatting
- Children blocks supported for nested content
### Deployment
- Vercel adapter configured (`adapter: vercel()`)
- SSR output mode
- Server-side rendering for all pages

View File

@@ -1,13 +1,12 @@
<p class="text-xs uppercase text-muted-foreground">comment</p>
<h3 class="text-xl font-semibold mb-3">留言 / 评论</h3>
<p class="text-muted-foreground text-xs uppercase">comment</p>
<h3 class="mb-3 text-xl font-semibold">留言 / 评论</h3>
<div id="tcomment" class="space-y-2">
<p class="text-sm text-muted-foreground">
<p class="text-muted-foreground text-sm">
如果暂时没有看到评论,请点击下方按钮重新加载。
</p>
<button
type="button"
class="inline-flex items-center justify-center rounded-md border border-input bg-background px-3 py-1.5 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
class="border-input bg-background hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring inline-flex items-center justify-center rounded-md border px-3 py-1.5 text-sm font-medium shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
onclick="window.runTwikooComments && window.runTwikooComments()"
>
重新加载评论

View File

@@ -1,18 +1,19 @@
---
import type * as interfaces from '../lib/interfaces.ts'
import Bookmark from './notion/Bookmark.astro'
import BulletedListItems from './notion/BulletedListItems.astro'
import Callout from './notion/Callout.astro'
import Code from './notion/Code.astro'
import ColumnList from './notion/ColumnList.astro'
import Divider from './notion/Divider.astro'
import Bookmark from './notion/Bookmark.astro'
import Embed from './notion/Embed.astro'
import Equation from './notion/Equation.astro'
import File from './notion/File.astro'
import Heading1 from './notion/Heading1.astro'
import Heading2 from './notion/Heading2.astro'
import Heading3 from './notion/Heading3.astro'
import Image from './notion/Image.astro'
import LinkToPage from './notion/LinkToPage.astro'
import Equation from './notion/Equation.astro'
import NumberedListItems from './notion/NumberedListItems.astro'
import Paragraph from './notion/Paragraph.astro'
import Quote from './notion/Quote.astro'
@@ -22,7 +23,6 @@ import TableOfContents from './notion/TableOfContents.astro'
import ToDo from './notion/ToDo.astro'
import Toggle from './notion/Toggle.astro'
import Video from './notion/Video.astro'
import type * as interfaces from '../lib/interfaces.ts'
export interface Props {
blocks: interfaces.Block[]
@@ -52,19 +52,11 @@ const {
return <Heading3 block={block} headings={headings} />
case 'bulleted_list':
return (
<BulletedListItems
block={block}
level={level}
headings={headings}
/>
<BulletedListItems block={block} level={level} headings={headings} />
)
case 'numbered_list':
return (
<NumberedListItems
block={block}
level={level}
headings={headings}
/>
<NumberedListItems block={block} level={level} headings={headings} />
)
case 'to_do':
return <ToDo block={block} headings={headings} />

View File

@@ -32,12 +32,16 @@ const canonicalUrl = Astro.url
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImageUrl} />
<meta property="og:image:alt" content={title} />
{ogImageWidth && (
{
ogImageWidth && (
<meta property="og:image:width" content={String(ogImageWidth)} />
)}
{ogImageHeight && (
)
}
{
ogImageHeight && (
<meta property="og:image:height" content={String(ogImageHeight)} />
)}
)
}
{ogImageType && <meta property="og:image:type" content={ogImageType} />}
<meta property="og:type" content="website" />
<meta property="og:locale" content={SITE.locale} />

View File

@@ -36,19 +36,26 @@ const canonicalUrl = Astro.url
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImage.url} />
<meta property="og:image:alt" content={title} />
{ogImage.width && (
{
ogImage.width && (
<meta property="og:image:width" content={String(ogImage.width)} />
)}
{ogImage.height && (
)
}
{
ogImage.height && (
<meta property="og:image:height" content={String(ogImage.height)} />
)}
)
}
{ogImage.type && <meta property="og:image:type" content={ogImage.type} />}
<meta property="og:type" content="article" />
<meta property="og:locale" content={SITE.locale} />
<meta property="og:site_name" content={SITE.title} />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:author" content={author} />
<meta property="article:published_time" content={post.data.date.toISOString()} />
<meta
property="article:published_time"
content={post.data.date.toISOString()}
/>
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />

View File

@@ -1,7 +1,6 @@
---
import { ScrollArea } from '@/components/ui/scroll-area'
import {
getCombinedReadingTime,
getParentId,
getParentPost,
getPostById,

View File

@@ -2,7 +2,6 @@
import Link from '@/components/Link.astro'
import { ScrollArea } from '@/components/ui/scroll-area'
import {
getCombinedReadingTime,
getParentId,
getParentPost,
getPostById,

View File

@@ -78,8 +78,9 @@ const { headings } = Astro.props
>
<a
href={`#${heading.slug}`}
class="mobile-toc-item underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
class="mobile-toc-item truncate underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
data-heading-id={heading.slug}
title={heading.text}
>
{heading.text}
</a>

View File

@@ -63,8 +63,9 @@ const parentId = isCurrentSubpost ? getParentId(currentPostId) : currentPostId
? `/blog/${parentId}#${heading.slug}`
: `#${heading.slug}`
}
class="marker:text-foreground/30 list-none underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
class="marker:text-foreground/30 list-none truncate underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
data-heading-link={heading.slug}
title={heading.text}
>
{heading.text}
</a>
@@ -93,12 +94,13 @@ const parentId = isCurrentSubpost ? getParentId(currentPostId) : currentPostId
? '#'
: `/blog/${section.subpostId}`
}
class="marker:text-foreground/30 list-none underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
class="marker:text-foreground/30 list-none truncate underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
data-heading-link={
section.subpostId === currentPostId
? 'top'
: `${section.subpostId}-top`
}
title={section.title}
>
{section.title}
</a>
@@ -119,12 +121,13 @@ const parentId = isCurrentSubpost ? getParentId(currentPostId) : currentPostId
? `#${heading.slug}`
: `/blog/${section.subpostId}#${heading.slug}`
}
class="marker:text-foreground/30 hover:text-foreground/60 list-none underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
class="marker:text-foreground/30 hover:text-foreground/60 list-none truncate underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
data-heading-link={
section.subpostId === currentPostId
? heading.slug
: `${section.subpostId}-${heading.slug}`
}
title={heading.text}
>
{heading.text}
</a>

View File

@@ -16,7 +16,7 @@ import { Icon } from 'astro-icon/components'
</Button>
<script is:inline data-astro-rerun>
(() => {
;(() => {
const theme = (() => {
const stored = localStorage?.getItem('theme') ?? ''
if (['dark', 'light'].includes(stored)) return stored
@@ -83,9 +83,8 @@ import { Icon } from 'astro-icon/components'
const handleToggleClick = (event) => {
const element = document.documentElement
const currentTheme = element.getAttribute('data-theme') === 'dark'
? 'dark'
: 'light'
const currentTheme =
element.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
const prefersReducedMotion = window.matchMedia(

View File

@@ -1,8 +1,8 @@
---
import type * as interfaces from '../../lib/interfaces.ts'
import { isAmazonURL, isGitHubURL } from '../../lib/blog-helpers.ts'
import GithubLinkPreview from './GitHubLinkPreview.astro'
import type * as interfaces from '../../lib/interfaces.ts'
import Caption from './Caption.astro'
import GithubLinkPreview from './GitHubLinkPreview.astro'
export interface Props {
block: interfaces.Block
@@ -50,7 +50,6 @@ try {
</div>
</div>
</a>
</div>
)}
</>
@@ -80,7 +79,9 @@ try {
display: flex;
overflow: hidden;
user-select: none;
transition: background-color 160ms ease, border-color 160ms ease;
transition:
background-color 160ms ease,
border-color 160ms ease;
}
.bookmark > a:hover {
background: var(--notion-surface-strong);

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block
@@ -21,7 +21,8 @@ const listTypes = ['disc', 'circle', 'square']
{
items
.filter(
(b: interfaces.Block) => b.Type === 'bulleted_list_item' && b.BulletedListItem,
(b: interfaces.Block) =>
b.Type === 'bulleted_list_item' && b.BulletedListItem,
)
.map((b: interfaces.Block) => {
const item = b.BulletedListItem

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -40,9 +40,9 @@ const displayLanguage =
<figure class="code mac">
<div class="chrome">
<div class="dots">
<span class="dot dot-red" />
<span class="dot dot-amber" />
<span class="dot dot-green" />
<span class="dot dot-red"></span>
<span class="dot dot-amber"></span>
<span class="dot dot-green"></span>
</div>
<div class="meta">
<span class="lang">{displayLanguage}</span>
@@ -76,7 +76,8 @@ const displayLanguage =
.then(() => {
const originalText = target.innerText
target.innerText = target.getAttribute('data-done-text') || target.innerText
target.innerText =
target.getAttribute('data-done-text') || target.innerText
setTimeout(() => {
target.innerText = originalText
@@ -100,7 +101,8 @@ const displayLanguage =
border-radius: 12px;
overflow: hidden;
border: 1px solid color-mix(in oklab, var(--border) 70%, transparent);
background: linear-gradient(
background:
linear-gradient(
180deg,
color-mix(in oklab, var(--muted) 12%, transparent),
transparent 30%
@@ -171,7 +173,9 @@ const displayLanguage =
line-height: 1.2rem;
cursor: pointer;
font-size: 0.75rem;
transition: background-color 120ms ease, transform 120ms ease;
transition:
background-color 120ms ease,
transform 120ms ease;
}
.code button.copy:hover {
@@ -199,7 +203,11 @@ const displayLanguage =
background: transparent;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
scrollbar-color: color-mix(in oklab, var(--muted-foreground) 60%, transparent)
scrollbar-color: color-mix(
in oklab,
var(--muted-foreground) 60%,
transparent
)
transparent;
}
@@ -240,6 +248,15 @@ const displayLanguage =
}
.lang {
font-family: var(--font-mono, 'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace);
font-family: var(
--font-mono,
'SFMono-Regular',
Menlo,
Monaco,
Consolas,
'Liberation Mono',
'Courier New',
monospace
);
}
</style>

View File

@@ -1,4 +1,5 @@
---
---
<hr class="divider" />

View File

@@ -1,20 +1,20 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import {
isTweetURL,
isTikTokURL,
isCircuitSimulatorAppletURL,
isCodePenURL,
isInstagramURL,
isPinterestURL,
isCodePenURL,
isCircuitSimulatorAppletURL,
isTikTokURL,
isTweetURL,
} from '../../lib/blog-helpers.ts'
import * as interfaces from '../../lib/interfaces.ts'
import Bookmark from './Bookmark.astro'
import TweetEmbed from './TweetEmbed.astro'
import TikTokEmbed from './TikTokEmbed.astro'
import CircuitSimulatorAppletEmbed from './CircuitSimulatorAppletEmbed.astro'
import CodePenEmbed from './CodePenEmbed.astro'
import InstagramEmbed from './InstagramEmbed.astro'
import PinterestEmbed from './PinterestEmbed.astro'
import CodePenEmbed from './CodePenEmbed.astro'
import CircuitSimulatorAppletEmbed from './CircuitSimulatorAppletEmbed.astro'
import TikTokEmbed from './TikTokEmbed.astro'
import TweetEmbed from './TweetEmbed.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,6 +1,6 @@
---
import * as interfaces from '../../lib/interfaces'
import { filePath } from '../../lib/blog-helpers'
import * as interfaces from '../../lib/interfaces'
import Caption from './Caption.astro'
export interface Props {
@@ -26,8 +26,7 @@ try {
}
const filename =
url &&
decodeURIComponent(url.pathname.split('/').slice(-1)[0] || '')
url && decodeURIComponent(url.pathname.split('/').slice(-1)[0] || '')
---
<div class="file">

View File

@@ -269,7 +269,7 @@ function buildEmbedQuery(params: EmbedParams) {
return Object.entries(params)
.filter(([, v]) => v !== undefined)
.map(
([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`
([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`,
)
.join('&')
}

View File

@@ -1,8 +1,8 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { buildHeadingId } from '../../lib/blog-helpers.ts'
import RichText from './RichText.astro'
import * as interfaces from '../../lib/interfaces.ts'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,8 +1,8 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { buildHeadingId } from '../../lib/blog-helpers.ts'
import RichText from './RichText.astro'
import * as interfaces from '../../lib/interfaces.ts'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,8 +1,8 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { buildHeadingId } from '../../lib/blog-helpers.ts'
import RichText from './RichText.astro'
import * as interfaces from '../../lib/interfaces.ts'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,9 +1,8 @@
---
import { ENABLE_LIGHTBOX } from '@/consts'
import * as interfaces from '../../lib/interfaces'
import { filePath } from '../../lib/blog-helpers'
import * as interfaces from '../../lib/interfaces'
import Caption from './Caption.astro'
import fslightbox from 'fslightbox'
export interface Props {
block: interfaces.Block
@@ -22,7 +21,8 @@ if (block.Image.External) {
image = filePath(new URL(block.Image.File.Url))
}
const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image'
const altText =
block.Image.Caption?.map((c) => c.Text.Content).join('') || 'Image'
---
<figure class="image">
@@ -31,7 +31,12 @@ const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image
<div class="image-wrapper">
<div class="image-container">
{ENABLE_LIGHTBOX ? (
<a data-fslightbox href={image} data-type="image" class="lightbox-link">
<a
data-fslightbox
href={image}
data-type="image"
class="lightbox-link"
>
<img
src={image}
alt={altText}
@@ -72,7 +77,9 @@ const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image
position: relative;
overflow: hidden;
border-radius: 0.5rem;
transition: transform 0.2s ease, box-shadow 0.2s ease;
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}
.image-container:hover {
@@ -94,8 +101,9 @@ const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image
transition: opacity 0.3s ease;
}
.image-content[loading="lazy"] {
background: linear-gradient(90deg,
.image-content[loading='lazy'] {
background: linear-gradient(
90deg,
hsl(var(--muted)) 0%,
hsl(var(--muted) / 0.8) 50%,
hsl(var(--muted)) 100%

View File

@@ -1,9 +1,9 @@
---
import arrow from '../../images/icon-arrow-link.svg'
import { getPostLink } from '../../lib/blog-helpers.ts'
import type { Post } from '../../lib/interfaces.ts'
import { getPostByPageId } from '../../lib/notion/client'
import { getPostLink } from '../../lib/blog-helpers.ts'
import '../../styles/notion-color.css'
import arrow from '../../images/icon-arrow-link.svg'
export interface Props {
pageId: string
@@ -22,18 +22,22 @@ if (pageId) {
<a href={getPostLink(post.Slug)} class="link">
<>
<span class="icon">
{post.Icon && post.Icon.Type === 'emoji'
? post.Icon.Emoji
: post.Icon && post.Icon.Type === 'external'
? (
{post.Icon && post.Icon.Type === 'emoji' ? (
post.Icon.Emoji
) : post.Icon && post.Icon.Type === 'external' ? (
<img
src={post.Icon.Url}
class="notion-icon"
alt="Post title icon in a page link"
/>
)
: '📄'}
<img src={arrow.src} class="icon-link" alt="Arrow icon of a page link" />
) : (
'📄'
)}
<img
src={arrow.src}
class="icon-link"
alt="Arrow icon of a page link"
/>
</span>
<span class="text">{post.Title}</span>
</>
@@ -42,7 +46,11 @@ if (pageId) {
<a class="link">
<span class="icon">
🚫
<img src={arrow.src} class="icon-link" alt="Arrow icon of a page link" />
<img
src={arrow.src}
class="icon-link"
alt="Arrow icon of a page link"
/>
</span>
<span class="text not-found">Post not found</span>
</a>

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block
@@ -21,7 +21,8 @@ const items = block.ListItems ?? []
{
items
.filter(
(b: interfaces.Block) => b.Type === 'numbered_list_item' && b.NumberedListItem,
(b: interfaces.Block) =>
b.Type === 'numbered_list_item' && b.NumberedListItem,
)
.map((b: interfaces.Block) => {
const item = b.NumberedListItem

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -11,5 +11,4 @@ const { url } = Astro.props
type="text/javascript"
async
defer
src="//assets.pinterest.com/js/pinit.js"
></script>
src="//assets.pinterest.com/js/pinit.js"></script>

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,13 +1,13 @@
---
import katex from 'katex'
import type { RichText } from '../../lib/interfaces.ts'
import Anchor from './annotations/Anchor.astro'
import Bold from './annotations/Bold.astro'
import Code from './annotations/Code.astro'
import Color from './annotations/Color.astro'
import Italic from './annotations/Italic.astro'
import Strikethrough from './annotations/Strikethrough.astro'
import Underline from './annotations/Underline.astro'
import Color from './annotations/Color.astro'
import Code from './annotations/Code.astro'
import Anchor from './annotations/Anchor.astro'
import Mention from './Mention.astro'
export interface Props {
@@ -43,13 +43,13 @@ const { richText } = Astro.props
{content}
</>
)
}
},
)}
{richText.Equation && (
<span
set:html={katex.renderToString(
richText.Equation.Expression,
{ throwOnError: false }
{ throwOnError: false },
)}
/>
)}

View File

@@ -1,6 +1,6 @@
---
import NotionBlocks from '../NotionBlocks.astro'
import type * as interfaces from '../../lib/interfaces'
import NotionBlocks from '../NotionBlocks.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,6 +1,6 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { buildHeadingId } from '../../lib/blog-helpers.ts'
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import '../../styles/notion-color.css'
@@ -39,7 +39,7 @@ if (!block.TableOfContents) {
)} ${indentClass}`}
>
{heading.RichTexts.map(
(richText: interfaces.RichText) => richText.PlainText
(richText: interfaces.RichText) => richText.PlainText,
).join('')}
</a>
)
@@ -52,6 +52,9 @@ if (!block.TableOfContents) {
}
.table-of-contents > a {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.8rem;
font-size: 0.9rem;
font-weight: 500;

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -17,7 +17,8 @@ const postURL =
</blockquote>
</div>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"
></script>
<style>
.tweet-embed {

View File

@@ -1,6 +1,6 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { isYouTubeURL, parseYouTubeVideoId } from '../../lib/blog-helpers.ts'
import * as interfaces from '../../lib/interfaces.ts'
import Caption from './Caption.astro'
export interface Props {

View File

@@ -14,10 +14,8 @@ const buttonVariants = cva(
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
muted:
'bg-muted text-foreground hover:bg-muted/80',
ghost:
'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50',
muted: 'bg-muted text-foreground hover:bg-muted/80',
ghost: 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {

View File

@@ -130,7 +130,8 @@ const PaginationComponent: React.FC<PaginationProps> = ({
baseUrl,
}) => {
const buildPages = () => {
if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i + 1)
if (totalPages <= 7)
return Array.from({ length: totalPages }, (_, i) => i + 1)
const pageSet = new Set<number>([
1,

View File

@@ -4,8 +4,7 @@ export const ENABLE_LIGHTBOX = true
export const SITE: Site = {
title: '溴化锂的笔记本',
description:
'Security, Programming, Life',
description: 'Security, Programming, Life',
href: 'https://nvme0n1p.dev',
author: 'libr',
locale: 'zh-CN',

View File

@@ -6,8 +6,8 @@ import Footer from '@/components/Footer.astro'
import Head from '@/components/Head.astro'
import Header from '@/components/Header.astro'
import { ENABLE_LIGHTBOX, SITE } from '@/consts'
import { cn } from '@/lib/utils'
import { getStaticFilePath } from '@/lib/blog-helpers'
import { cn } from '@/lib/utils'
interface Props {
class?: string
}
@@ -38,11 +38,6 @@ const { class: className } = Astro.props
</main>
<Footer />
{
ENABLE_LIGHTBOX && (
<script src={getStaticFilePath('/js/fslightbox.js')} />
)
}
{ENABLE_LIGHTBOX && <script src={getStaticFilePath('/js/fslightbox.js')} />}
</body>
</html>

View File

@@ -11,18 +11,19 @@ import { cn } from '@/lib/utils'
<PageHead slot="head" title="500" />
<Breadcrumbs items={[{ label: 'Error', icon: 'lucide:server-off' }]} />
<section class="flex flex-col items-center justify-center gap-y-4 text-center">
<section
class="flex flex-col items-center justify-center gap-y-4 text-center"
>
<div class="max-w-md">
<h1 class="mb-4 text-3xl font-medium">500: Something went wrong</h1>
<p class="prose">
服务器爆炸了!如果你能联系到博主的话提醒他一声吧!
</p>
<p class="prose">服务器爆炸了!如果你能联系到博主的话提醒他一声吧!</p>
</div>
<Link
href="/"
class={cn(buttonVariants({ variant: 'outline' }), 'flex gap-x-1.5 group')}
>
<span class="transition-transform group-hover:-translate-x-1">&larr;</span>
<span class="transition-transform group-hover:-translate-x-1">&larr;</span
>
Go to home page
</Link>
</section>

View File

@@ -1,9 +1,9 @@
---
import Breadcrumbs from '@/components/Breadcrumbs.astro'
import Comment from '@/components/Comment.astro'
import Link from '@/components/Link.astro'
import PageHead from '@/components/PageHead.astro'
import Layout from '@/layouts/Layout.astro'
import Comment from '@/components/Comment.astro'
---
<Layout class="max-w-3xl">
@@ -13,11 +13,11 @@ import Comment from '@/components/Comment.astro'
<section class="grid gap-6">
<div class="relative rounded-lg border p-6">
<div class="relative flex flex-col gap-4">
<p class="text-xs uppercase tracking-[0.35em] text-muted-foreground">
<p class="text-muted-foreground text-xs tracking-[0.35em] uppercase">
profile
</p>
<h1 class="text-3xl font-semibold">关于我</h1>
<p class="max-w-3xl text-muted-foreground">
<p class="text-muted-foreground max-w-3xl">
啥都写一点,但是啥都不精通。前端后端客户端,运维网安都会那么一点点,尝试写出点什么项目但总是失败。
</p>
</div>
@@ -26,19 +26,19 @@ import Comment from '@/components/Comment.astro'
<div class="grid gap-4 md:grid-cols-3">
<div class="rounded-lg border p-5">
<h3 class="text-lg font-semibold">安全</h3>
<p class="text-sm leading-relaxed text-muted-foreground">
<p class="text-muted-foreground text-sm leading-relaxed">
非典型 Misc啥都做点感觉更像是全栈
</p>
</div>
<div class="rounded-lg border p-5">
<h3 class="text-lg font-semibold">代码</h3>
<p class="text-sm leading-relaxed text-muted-foreground">
<p class="text-muted-foreground text-sm leading-relaxed">
近期在写 C 和 Swift之前写过 Golang、Python、JavaScript
</p>
</div>
<div class="rounded-lg border p-5">
<h3 class="text-lg font-semibold">生活</h3>
<p class="text-sm leading-relaxed text-muted-foreground">
<p class="text-muted-foreground text-sm leading-relaxed">
二次元、骑车、游泳、羽毛球、CS、摄影
</p>
</div>
@@ -47,41 +47,45 @@ import Comment from '@/components/Comment.astro'
<div class="rounded-lg border p-6">
<div class="flex items-center justify-between gap-4">
<div>
<p class="text-xs uppercase text-muted-foreground">setup</p>
<p class="text-muted-foreground text-xs uppercase">setup</p>
<h3 class="text-xl font-semibold">常用设备</h3>
</div>
</div>
<div class="mt-4 grid gap-3 md:grid-cols-2">
<div class="rounded-lg border p-4">
<p class="text-sm font-semibold">PC</p>
<p class="text-sm text-muted-foreground">
<p class="text-muted-foreground text-sm">
M2 Macbook Air · MSI Stealth 14 · iPad Air 5
</p>
</div>
<div class="rounded-lg border p-4">
<p class="text-sm font-semibold">手机</p>
<p class="text-sm text-muted-foreground">iPhone 17 · vivo x100 · iPhone 13 mini</p>
<p class="text-muted-foreground text-sm">
iPhone 17 · vivo x100 · iPhone 13 mini
</p>
</div>
<div class="rounded-lg border p-4 md:col-span-2">
<p class="text-sm font-semibold">影音与记录</p>
<p class="text-sm text-muted-foreground">Sony XM5 · Nikon Zfc</p>
<p class="text-muted-foreground text-sm">Sony XM5 · Nikon Zfc</p>
</div>
</div>
</div>
<section class="rounded-lg border p-6">
<p class="text-xs uppercase text-muted-foreground">site</p>
<p class="text-muted-foreground text-xs uppercase">site</p>
<h3 class="text-xl font-semibold">关于本站</h3>
<p class="mt-2 text-sm leading-relaxed text-muted-foreground">
<p class="text-muted-foreground mt-2 text-sm leading-relaxed">
栈在 <Link href="https://astro.build" external underline>Astro</Link>,
<Link href="https://tailwindcss.com" external underline>Tailwind</Link>,
<Link href="https://ui.shadcn.com" external underline>shadcn/ui</Link> 之上,开源在
<Link href="https://github.com/jktrn/astro-erudite" external underline>GitHub</Link>。
<Link href="https://github.com/jktrn/astro-erudite" external underline
>GitHub</Link
>。
</p>
<h4 class="text-l font-semibold mt-2">大事记</h4>
<h4 class="text-l mt-2 font-semibold">大事记</h4>
<div class="mt-3 overflow-x-auto rounded-lg border">
<table class="w-full text-sm">
<thead class="bg-muted/60 text-left text-muted-foreground">
<thead class="bg-muted/60 text-muted-foreground text-left">
<tr>
<th class="px-4 py-2 font-semibold">时间</th>
<th class="px-4 py-2 font-semibold">事件</th>
@@ -91,26 +95,30 @@ import Comment from '@/components/Comment.astro'
<tr>
<td class="px-4 py-2 whitespace-nowrap">2018</td>
<td class="px-4 py-2">
第一次建站,域名 <code>stevelbr.ga</code>,使用 hexo 和不知道有几种主题
第一次建站,域名 <code>stevelbr.ga</code>,使用 hexo
和不知道有几种主题
</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2020</td>
<td class="px-4 py-2">
购入腾讯云轻量服务器,购入域名 <code>stevelbr.top</code> ,使用 typecho
typecho-theme-handsome 建站,接入备案。
购入腾讯云轻量服务器,购入域名 <code>stevelbr.top</code> ,使用 typecho
typecho-theme-handsome 建站,接入备案。
</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2023</td>
<td class="px-4 py-2">
停止续费腾讯云轻量,购入域名 <code>nvme0n1p.dev</code>,使用 hugo+serverless service 建站。
停止续费腾讯云轻量,购入域名 <code>nvme0n1p.dev</code>,使用
hugo+serverless service 建站。
</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2024.1</td>
<td class="px-4 py-2">
使用 <Link href="http://cali.so" external underline>cali.so</Link> nextjs自建服务运行于 vercel。
使用 <Link href="http://cali.so" external underline
>cali.so</Link
> nextjs自建服务运行于 vercel。
</td>
</tr>
<tr>
@@ -128,18 +136,24 @@ import Comment from '@/components/Comment.astro'
<tr>
<td class="px-4 py-2 whitespace-nowrap">2024.10</td>
<td class="px-4 py-2">
优化 nuxtjs 代码,现在全站支持无刷新页面切换。<del>同时增加 algolia docsearch 魔改版作为站内搜索。</del>好像没开 Server Side Rendering ,炸了
优化 nuxtjs 代码,现在全站支持无刷新页面切换。<del
>同时增加 algolia docsearch 魔改版作为站内搜索。</del
>好像没开 Server Side Rendering ,炸了
</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2024.11</td>
<td class="px-4 py-2">
感谢<Link href="https://www.dkdun.cn/" external underline>林枫云</Link>对 ctfer 的支持,切换到了他家的香港服务器。
感谢<Link href="https://www.dkdun.cn/" external underline
>林枫云</Link
>对 ctfer 的支持,切换到了他家的香港服务器。
</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2025.2</td>
<td class="px-4 py-2">用nextjs重写了。优化了访问速度减少了了图片。</td>
<td class="px-4 py-2"
>用nextjs重写了。优化了访问速度减少了了图片。</td
>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2025.7</td>

View File

@@ -1,35 +1,34 @@
---
import Breadcrumbs from '@/components/Breadcrumbs.astro'
import Comment from '@/components/Comment.astro'
import Link from '@/components/Link.astro'
import NotionBlocks from '@/components/NotionBlocks.astro'
import PostHead from '@/components/PostHead.astro'
import PostNavigation from '@/components/PostNavigation.astro'
import SubpostsHeader from '@/components/SubpostsHeader.astro'
import SubpostsSidebar from '@/components/SubpostsSidebar.astro'
import TOCHeader from '@/components/TOCHeader.astro'
import TOCSidebar from '@/components/TOCSidebar.astro'
import NotionBlocks from '@/components/NotionBlocks.astro'
import { badgeVariants } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import Layout from '@/layouts/Layout.astro'
import type { TOCSection } from '@/lib/data-utils'
import {
fetchRemotePostContent,
getAdjacentPosts,
getAllPostsAndSubposts,
getParentId,
getParentPost,
getSubpostCount,
getTOCSections,
fetchRemotePostContent,
renderRemoteBlockMap,
hasSubposts,
isSubpost,
parseAuthors,
renderRemoteBlockMap,
} from '@/lib/data-utils'
import type { TOCSection } from '@/lib/data-utils'
import Comment from '@/components/Comment.astro'
import { formatDate } from '@/lib/utils'
import { Icon } from 'astro-icon/components'
import { Image } from 'astro:assets'
import { render } from 'astro:content'
export async function getStaticPaths() {
const posts = await getAllPostsAndSubposts()
@@ -71,9 +70,7 @@ try {
parentPost = isCurrentSubpost ? await getParentPost(currentPostId) : null
hasChildPosts = await hasSubposts(currentPostId)
subpostCount = !isCurrentSubpost
? await getSubpostCount(currentPostId)
: 0
subpostCount = !isCurrentSubpost ? await getSubpostCount(currentPostId) : 0
tocSections = remoteContent
? remoteContent.headings.length > 0
@@ -91,7 +88,7 @@ try {
console.error('blog page render failed:', error)
return Astro.rewrite('/500')
}
export const prerender = false;
export const prerender = false
---
<Layout>

View File

@@ -5,8 +5,7 @@ import PageHead from '@/components/PageHead.astro'
import PaginationComponent from '@/components/ui/pagination'
import { SITE } from '@/consts'
import Layout from '@/layouts/Layout.astro'
import { getAllPosts, groupPostsByYear, normalizePost } from '@/lib/data-utils'
import type { PaginateFunction } from 'astro'
import { groupPostsByYear, normalizePost } from '@/lib/data-utils'
// export async function getStaticPaths({
// paginate,
@@ -16,33 +15,37 @@ import type { PaginateFunction } from 'astro'
// const allPosts = await getAllPosts()
// return paginate(allPosts, { pageSize: SITE.postsPerPage })
// }
const nowPage = parseInt(new URL(Astro.request.url).searchParams.get("p") || "1");
const nowPage = parseInt(
new URL(Astro.request.url).searchParams.get('p') || '1',
)
// use url param
// const { page } = Astro.para
const page = await fetch(`https://notion-api.nvme0n1p.dev/v2/posts/?page=${nowPage}&length=${SITE.postsPerPage}`)
const page = await fetch(
`https://notion-api.nvme0n1p.dev/v2/posts/?page=${nowPage}&length=${SITE.postsPerPage}`,
)
.then((res) => {
if (!res.ok) throw new Error(`Failed to fetch posts: ${res.status}`);
return res.json();
if (!res.ok) throw new Error(`Failed to fetch posts: ${res.status}`)
return res.json()
})
.then((allPosts) => {
const totalPages = allPosts.length;
const currentPage = nowPage;
const totalPages = allPosts.length
const currentPage = nowPage
return {
data: allPosts.posts.map((post) => normalizePost(post)),
currentPage,
lastPage: totalPages,
};
});
}
})
// fetch page
if(page.lastPage < page.currentPage) {
console.log("redirect to 404")
return Astro.rewrite("/404")
if (page.lastPage < page.currentPage) {
console.log('redirect to 404')
return Astro.rewrite('/404')
}
const postsByYear = groupPostsByYear(page.data)
const years = Object.keys(postsByYear).sort((a, b) => parseInt(b) - parseInt(a))
export const prerender = false;
export const prerender = false
---
<Layout class="max-w-3xl">

View File

@@ -1,13 +1,10 @@
---
import Breadcrumbs from '@/components/Breadcrumbs.astro'
import Comment from '@/components/Comment.astro'
import Link from '@/components/Link.astro'
import PageHead from '@/components/PageHead.astro'
import Layout from '@/layouts/Layout.astro'
import {
getFriendLinks,
type LinkEntry,
} from '@/lib/data-utils'
import Comment from '@/components/Comment.astro'
import { getFriendLinks } from '@/lib/data-utils'
const placeholderAvatar = '/avatar-placeholder.svg'
const visibleLinks = await getFriendLinks()
---
@@ -19,19 +16,16 @@ const visibleLinks = await getFriendLinks()
<section class="rounded-lg border p-6">
<div class="flex flex-col gap-2">
<h1 class="text-2xl font-semibold">友链 / Friends</h1>
<p class="text-muted-foreground text-sm">
这里收集一些朋友和有趣的网站
</p>
<p class="text-muted-foreground text-sm">这里收集一些朋友和有趣的网站</p>
</div>
</section>
<section class="grid gap-4">
{
visibleLinks.map((friend) => (
<article class="rounded-lg border p-4 transition hover:border-primary/60">
<article class="hover:border-primary/60 rounded-lg border p-4 transition">
<div class="flex items-start gap-3">
{
friend.picLink ? (
{friend.picLink ? (
<img
src={friend.picLink}
alt={friend.name}
@@ -50,17 +44,16 @@ const visibleLinks = await getFriendLinks()
width="48"
height="48"
/>
)
}
)}
<div class="flex-1 space-y-1">
<div class="flex items-center justify-between gap-3">
<h2 class="text-lg font-semibold leading-tight">
<h2 class="text-lg leading-tight font-semibold">
<Link href={friend.links} external underline>
{friend.name}
</Link>
</h2>
</div>
<p class="text-sm text-muted-foreground">
<p class="text-muted-foreground text-sm">
{friend.Description || '你来到了知识的荒原'}
</p>
</div>
@@ -72,10 +65,11 @@ const visibleLinks = await getFriendLinks()
<section class="rounded-lg border p-6">
<h3 class="text-lg font-semibold">申请方式</h3>
<p class="text-sm text-muted-foreground mt-2">
<p class="text-muted-foreground mt-2 text-sm">
请提供站点名称、链接、一句话介绍。
</p>
<pre class="mt-3 whitespace-pre-wrap break-words rounded-md bg-muted/50 p-3 text-xs text-muted-foreground">
<pre
class="bg-muted/50 text-muted-foreground mt-3 rounded-md p-3 text-xs break-words whitespace-pre-wrap">
name: 溴化锂的笔记本
link: https://nvme0n1p.dev
avatar: https://gravatar.com/avatar/29d64df3ca2a9dac5a7fffa5372fb80fb3270ceb223de2af0c33cdc4b2cbe954?v=1687917579000&size=256&d=initials

View File

@@ -15,9 +15,7 @@ const blog = await getRecentPosts(SITE.featuredPostCount)
<section class="rounded-lg border">
<div class="flex flex-col space-y-1.5 p-6">
<h3 class="text-3xl leading-none font-medium">溴化锂</h3>
<p class="text-muted-foreground text-sm">
LiBr
</p>
<p class="text-muted-foreground text-sm">LiBr</p>
</div>
<div class="p-6 pt-0">
<p class="text-muted-foreground mb-2 text-sm">

View File

@@ -24,7 +24,7 @@ export async function getStaticPaths() {
}
const { tag, posts } = Astro.props
export const prerender = false;
export const prerender = false
---
<Layout class="max-w-3xl">

View File

@@ -8,7 +8,7 @@ import { getSortedTags } from '@/lib/data-utils'
import { Icon } from 'astro-icon/components'
const sortedTags = await getSortedTags()
export const prerender = false;
export const prerender = false
---
<Layout class="max-w-3xl">

View File

@@ -44,13 +44,37 @@
--notion-red: color-mix(in oklab, var(--foreground) 82%, #d44c47);
--notion-gray-background: color-mix(in oklab, var(--background) 82%, #f1f1ef);
--notion-brown-background: color-mix(in oklab, var(--background) 82%, #f4eeee);
--notion-orange-background: color-mix(in oklab, var(--background) 82%, #fbecdd);
--notion-yellow-background: color-mix(in oklab, var(--background) 82%, #fbf3db);
--notion-green-background: color-mix(in oklab, var(--background) 82%, #edf3ec);
--notion-brown-background: color-mix(
in oklab,
var(--background) 82%,
#f4eeee
);
--notion-orange-background: color-mix(
in oklab,
var(--background) 82%,
#fbecdd
);
--notion-yellow-background: color-mix(
in oklab,
var(--background) 82%,
#fbf3db
);
--notion-green-background: color-mix(
in oklab,
var(--background) 82%,
#edf3ec
);
--notion-blue-background: color-mix(in oklab, var(--background) 82%, #e7f3f8);
--notion-purple-background: color-mix(in oklab, var(--background) 82%, rgba(244, 240, 247, 0.8));
--notion-pink-background: color-mix(in oklab, var(--background) 82%, rgba(249, 238, 243, 0.8));
--notion-purple-background: color-mix(
in oklab,
var(--background) 82%,
rgba(244, 240, 247, 0.8)
);
--notion-pink-background: color-mix(
in oklab,
var(--background) 82%,
rgba(249, 238, 243, 0.8)
);
--notion-red-background: color-mix(in oklab, var(--background) 82%, #fdebec);
--notion-tag-foreground: var(--foreground);