feat: 迁移之前的博客组件
@ -25,6 +25,7 @@
|
||||
"astro-icon": "^1.1.5",
|
||||
"astro-robots-txt": "^1.0.0",
|
||||
"astro-webmanifest": "^1.0.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cssnano": "^7.0.6",
|
||||
"hastscript": "^9.0.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
@ -39,6 +40,7 @@
|
||||
"satori": "0.12.1",
|
||||
"satori-html": "^0.3.2",
|
||||
"sharp": "^0.33.5",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
|
11
pnpm-lock.yaml
generated
@ -38,6 +38,9 @@ importers:
|
||||
astro-webmanifest:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
cssnano:
|
||||
specifier: ^7.0.6
|
||||
version: 7.0.6(postcss@8.4.49)
|
||||
@ -80,6 +83,9 @@ importers:
|
||||
sharp:
|
||||
specifier: ^0.33.5
|
||||
version: 0.33.5
|
||||
tailwind-merge:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
unified:
|
||||
specifier: ^11.0.5
|
||||
version: 11.0.5
|
||||
@ -3182,6 +3188,9 @@ packages:
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
|
||||
tailwind-merge@3.0.1:
|
||||
resolution: {integrity: sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==}
|
||||
|
||||
tailwindcss@4.0.0:
|
||||
resolution: {integrity: sha512-ULRPI3A+e39T7pSaf1xoi58AqqJxVCLg8F/uM5A3FadUbnyDTgltVnXJvdkTjwCOGA6NazqHVcwPJC5h2vRYVQ==}
|
||||
|
||||
@ -7299,6 +7308,8 @@ snapshots:
|
||||
csso: 5.0.5
|
||||
picocolors: 1.1.1
|
||||
|
||||
tailwind-merge@3.0.1: {}
|
||||
|
||||
tailwindcss@4.0.0: {}
|
||||
|
||||
tailwindcss@4.0.0-beta.8: {}
|
||||
|
@ -27,6 +27,16 @@ const socialLinks: {
|
||||
link: "https://x.com/kazoottt",
|
||||
name: "mdi:twitter",
|
||||
},
|
||||
{
|
||||
friendlyName: "Photo",
|
||||
link: "https://unsplash.com/@kazoottt",
|
||||
name: "mdi:camera",
|
||||
},
|
||||
{
|
||||
friendlyName: "汇总",
|
||||
link: "https://bento.me/KazooTTT",
|
||||
name: "mdi:link",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
|
124
src/components/componentsBefore/BaseHead.astro
Normal file
@ -0,0 +1,124 @@
|
||||
---
|
||||
// Import the global.css file here so that it is included on
|
||||
// all pages through the use of the <BaseHead /> component.
|
||||
import '../styles/app.css'
|
||||
|
||||
import type { SiteMeta } from '@/types'
|
||||
import { siteConfig } from '@/site-config'
|
||||
|
||||
type Props = SiteMeta
|
||||
import { ViewTransitions } from 'astro:transitions'
|
||||
const { articleDate, description, banner, title } = Astro.props
|
||||
|
||||
const titleSeparator = '•'
|
||||
const siteTitle = `${title} ${titleSeparator} ${siteConfig.title}`
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site)
|
||||
const socialImageURL = new URL(banner ? banner : '/social-card.png', Astro.url).href
|
||||
---
|
||||
|
||||
<meta charset='utf-8' />
|
||||
<meta content='width=device-width, initial-scale=1.0, shrink-to-fit=no' name='viewport' />
|
||||
<meta content='IE=edge' http-equiv='X-UA-Compatible' />
|
||||
<meta name='baidu-site-verification' content='codeva-ZWUil8ENc0' />
|
||||
<meta name='google-adsense-account' content='ca-pub-6184816340945344' />
|
||||
<script
|
||||
async
|
||||
src='https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-6184816340945344'
|
||||
crossorigin='anonymous'></script>
|
||||
|
||||
<script type='text/javascript'>
|
||||
;(function (c, l, a, r, i, t, y) {
|
||||
c[a] =
|
||||
c[a] ||
|
||||
function () {
|
||||
;(c[a].q = c[a].q || []).push(arguments)
|
||||
}
|
||||
t = l.createElement(r)
|
||||
t.async = 1
|
||||
t.src = 'https://www.clarity.ms/tag/' + i
|
||||
y = l.getElementsByTagName(r)[0]
|
||||
y.parentNode.insertBefore(t, y)
|
||||
})(window, document, 'clarity', 'script', 'kvbyuhu6d2')
|
||||
</script>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src='https://www.googletagmanager.com/gtag/js?id=G-F4KLD4XCDB'></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || []
|
||||
function gtag() {
|
||||
dataLayer.push(arguments)
|
||||
}
|
||||
gtag('js', new Date())
|
||||
|
||||
gtag('config', 'G-F4KLD4XCDB')
|
||||
</script>
|
||||
|
||||
<title>{siteTitle}</title>
|
||||
|
||||
{/* Icons / Favicon */}
|
||||
<!-- <link href="favicon/favicon.ico" rel="icon" sizes="any" />
|
||||
<link href="favicon/icon.svg" rel="icon" type="image/svg+xml" />
|
||||
<link href="/apple-touch-icon.png" rel="apple-touch-icon" />
|
||||
<link href="/manifest.webmanifest" rel="manifest" /> -->
|
||||
|
||||
<link rel='apple-touch-icon' sizes='180x180' href='/favicon/apple-touch-icon.png' />
|
||||
<link rel='icon' type='image/png' sizes='32x32' href='/favicon/favicon-32x32.png' />
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='/favicon/favicon-16x16.png' />
|
||||
<link rel='manifest' href='/favicon/site.webmanifest' />
|
||||
|
||||
{/* Font preloads */}
|
||||
<!-- <link rel='preload' href='/fonts/Satoshi-Variable.ttf' as='font' type='font/ttf' crossorigin />
|
||||
<link
|
||||
rel='preload'
|
||||
href='/fonts/Satoshi-VariableItalic.ttf'
|
||||
as='font'
|
||||
type='font/ttf'
|
||||
crossorigin
|
||||
/>
|
||||
<link rel='preload' href='/fonts/ClashDisplay-Variable.ttf' as='font' type='font/ttf' crossorigin /> -->
|
||||
|
||||
{/* Canonical URL */}
|
||||
<link rel='canonical' href={canonicalURL} />
|
||||
|
||||
{/* Primary Meta Tags */}
|
||||
<meta content={siteTitle} name='title' />
|
||||
<meta content={description} name='description' />
|
||||
<meta content={siteConfig.author} name='author' />
|
||||
|
||||
{/* Theme Colour */}
|
||||
<meta content='' name='theme-color' />
|
||||
|
||||
{/* Open Graph / Facebook */}
|
||||
<meta content={articleDate ? 'article' : 'website'} property='og:type' />
|
||||
<meta content={title} property='og:title' />
|
||||
<meta content={description} property='og:description' />
|
||||
<meta content={canonicalURL} property='og:url' />
|
||||
<meta content={siteConfig.title} property='og:site_name' />
|
||||
<meta content={siteConfig.ogLocale} property='og:locale' />
|
||||
<meta content={socialImageURL} property='og:image' />
|
||||
<meta content='1200' property='og:image:width' />
|
||||
<meta content='630' property='og:image:height' />
|
||||
{
|
||||
articleDate && (
|
||||
<>
|
||||
<meta content={siteConfig.author} property='article:author' />
|
||||
<meta content={articleDate} property='article:published_time' />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{/* Twitter */}
|
||||
<meta content='summary_large_image' property='twitter:card' />
|
||||
<meta content={canonicalURL} property='twitter:url' />
|
||||
<meta content={title} property='twitter:title' />
|
||||
<meta content={description} property='twitter:description' />
|
||||
<meta content={socialImageURL} property='twitter:image' />
|
||||
|
||||
{/* Sitemap */}
|
||||
<link href='/sitemap-index.xml' rel='sitemap' />
|
||||
|
||||
{/* RSS auto-discovery */}
|
||||
<link href='/rss.xml' rel='alternate' title={siteConfig.title} type='application/rss+xml' />
|
||||
|
||||
<meta content={Astro.generator} name='generator' />
|
||||
|
||||
<ViewTransitions />
|
19
src/components/componentsBefore/Button.astro
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
import { cn } from "@/utils/tailwind";
|
||||
const { as: Tag = "a", class: className, title, href, style = "button" } = Astro.props;
|
||||
---
|
||||
|
||||
<Tag
|
||||
class={cn(
|
||||
className,
|
||||
"inline-flex items-center gap-x-1 rounded-lg bg-primary-foreground border border-border px-2 py-1 text-sm transition-all hover:bg-input",
|
||||
!href && "cursor-default",
|
||||
style === "pill" && "rounded-xl"
|
||||
)}
|
||||
href={href}
|
||||
data-astro-prefetch
|
||||
>
|
||||
<slot name="icon-before" />
|
||||
<p>{title}</p>
|
||||
<slot name="icon-after" />
|
||||
</Tag>
|
57
src/components/componentsBefore/Card.astro
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
import { cn } from '@/utils'
|
||||
import type { ImageMetadata } from 'astro'
|
||||
import { Image } from 'astro:assets'
|
||||
|
||||
const {
|
||||
as: Tag = 'div',
|
||||
class: className,
|
||||
href,
|
||||
target,
|
||||
heading,
|
||||
subheading,
|
||||
date,
|
||||
imagePath,
|
||||
altText,
|
||||
imageClass
|
||||
} = Astro.props
|
||||
|
||||
// If href is provided, use 'a' tag instead of the default or provided tag
|
||||
const Component = href ? 'a' : Tag
|
||||
|
||||
const images = import.meta.glob<{ default: ImageMetadata }>('/src/assets/*.{jpeg,jpg,png,gif}')
|
||||
|
||||
if (imagePath) {
|
||||
if (!images[imagePath])
|
||||
throw new Error(`"${imagePath}" does not exist in glob: "src/assets/*.{jpeg,jpg,png,gif}"`)
|
||||
}
|
||||
---
|
||||
|
||||
<Component
|
||||
class={cn(
|
||||
className,
|
||||
'relative rounded-2xl border border-border bg-primary-foreground px-5 py-3',
|
||||
href && 'transition-all hover:border-foreground/25 hover:shadow-sm'
|
||||
)}
|
||||
href={href}
|
||||
target={target}
|
||||
>
|
||||
{
|
||||
imagePath && (
|
||||
<Image
|
||||
src={images[imagePath]()}
|
||||
alt={altText}
|
||||
class={cn('mb-3 md:absolute md:mb-0', imageClass)}
|
||||
loading='eager'
|
||||
/>
|
||||
)
|
||||
}
|
||||
<div class='flex flex-col gap-y-1.5'>
|
||||
<div class='flex flex-col gap-y-0.5'>
|
||||
<h1 class='text-lg font-medium'>{heading}</h1>
|
||||
{subheading && <h2 class='text-muted-foreground'>{subheading}</h2>}
|
||||
{date && <h2 class='text-muted-foreground'>{date}</h2>}
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</Component>
|
113
src/components/componentsBefore/CategoriesView.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import type { CategoryHierarchy } from '@/types'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
interface CategoriesViewProps {
|
||||
allCategories: [string, number][]
|
||||
allCategoriesHierarchy: CategoryHierarchy[]
|
||||
defaultViewMode?: 'tree' | 'list'
|
||||
}
|
||||
|
||||
const CategoriesView: React.FC<CategoriesViewProps> = ({
|
||||
allCategories,
|
||||
allCategoriesHierarchy,
|
||||
defaultViewMode = 'tree'
|
||||
}) => {
|
||||
const [viewMode, setViewMode] = useState<'tree' | 'list'>(defaultViewMode)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mb-6 mt-5 flex items-center justify-between'>
|
||||
<h1 className='text-2xl font-bold'>Categories</h1>
|
||||
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<span className='text-sm'>View:</span>
|
||||
<div className='flex overflow-hidden rounded-md border'>
|
||||
<button
|
||||
onClick={() => setViewMode('tree')}
|
||||
className={`view-mode-btn px-2 py-1 text-sm
|
||||
${viewMode === 'tree' ? 'bg-primary text-white' : 'bg-white text-gray-700'}
|
||||
border-r`}
|
||||
>
|
||||
Tree
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`view-mode-btn px-2 py-1 text-sm
|
||||
${viewMode === 'list' ? 'bg-primary text-white' : 'bg-white text-gray-700'}
|
||||
border-l`}
|
||||
>
|
||||
List
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{allCategoriesHierarchy.length === 0 && <p>No posts yet.</p>}
|
||||
|
||||
{/* List View */}
|
||||
{viewMode === 'list' && (
|
||||
<div className='flex flex-col gap-y-3'>
|
||||
{allCategories.map(([category, count]) => (
|
||||
<div key={category} className='flex items-center gap-x-2'>
|
||||
<a
|
||||
className='inline-block underline underline-offset-4 hover:text-foreground/75'
|
||||
href={`/categories/${category}/`}
|
||||
title={`View posts of the Category: ${category}`}
|
||||
>
|
||||
{category}
|
||||
</a>
|
||||
<span className='inline-block'>
|
||||
- {count} post{count > 1 && 's'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tree View */}
|
||||
{viewMode === 'tree' && (
|
||||
<div className='flex flex-col gap-y-3'>
|
||||
{allCategoriesHierarchy.map((category) => (
|
||||
<div key={category.fullCategory}>
|
||||
{/* Render root-level categories */}
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<a
|
||||
className='inline-block underline underline-offset-4 hover:text-foreground/75'
|
||||
href={`/categories/${category.fullCategory}/`}
|
||||
title={`View posts of the Category: ${category.fullCategory}`}
|
||||
>
|
||||
{category.category}
|
||||
</a>
|
||||
<span className='inline-block'>
|
||||
- {category.count} post{category.count > 1 && 's'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Render nested categories with indentation */}
|
||||
{category.children && Object.values(category.children).length > 0 && (
|
||||
<div className='pl-8'>
|
||||
{Object.values(category.children).map((childCategory) => (
|
||||
<div key={childCategory.fullCategory} className='flex items-center gap-x-2'>
|
||||
<a
|
||||
className='inline-block underline underline-offset-4 hover:text-foreground/75'
|
||||
href={`/categories/${childCategory.fullCategory}/`}
|
||||
title={`View posts of the Category: ${childCategory.fullCategory}`}
|
||||
>
|
||||
{childCategory.category}
|
||||
</a>
|
||||
<span className='inline-block'>
|
||||
- {childCategory.count} post{childCategory.count > 1 && 's'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CategoriesView
|
39
src/components/componentsBefore/CategorySection.astro
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
import ToolSection from './ToolSection.astro'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
categories: {
|
||||
title: string
|
||||
sections: {
|
||||
[key: string]: {
|
||||
title: string
|
||||
tools: {
|
||||
name: string
|
||||
description: string
|
||||
href?: string
|
||||
iconPath?: string
|
||||
}[]
|
||||
}
|
||||
}
|
||||
}[]
|
||||
}
|
||||
|
||||
const { title, categories } = Astro.props
|
||||
---
|
||||
|
||||
<div>
|
||||
<h2 class='mb-6 text-xl font-bold'>{title}</h2>
|
||||
{
|
||||
categories.map((category) => (
|
||||
<div class='mb-8'>
|
||||
<h3 class='mb-4 text-lg font-semibold'>{category.title}</h3>
|
||||
{Object.values(category.sections).map((section) => (
|
||||
<div class='mb-6'>
|
||||
<ToolSection title={section.title} tools={section.tools} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
15
src/components/componentsBefore/FormattedDate.astro
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
import type { HTMLAttributes } from 'astro/types'
|
||||
|
||||
interface Props extends HTMLAttributes<'time'> {
|
||||
date: Date
|
||||
}
|
||||
|
||||
const { date, ...attrs } = Astro.props
|
||||
|
||||
const formattedDate = date.toISOString().slice(0, 10).replace(/-/g, '')
|
||||
---
|
||||
|
||||
<time datetime={date.toISOString()} {...attrs}>
|
||||
{formattedDate}
|
||||
</time>
|
17
src/components/componentsBefore/FreeTemplate.astro
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<a
|
||||
href='https://github.com/srleom/astro-theme-resume.git'
|
||||
class='mt-16 inline-flex flex-row items-center gap-x-3 rounded-3xl border border-input px-4 py-2 text-sm shadow-sm transition-all hover:shadow-md'
|
||||
>
|
||||
<span class='relative flex items-center justify-center'>
|
||||
<span
|
||||
class='absolute inline-flex h-2 w-2 animate-ping rounded-full border border-green-400 bg-green-400 opacity-75'
|
||||
></span>
|
||||
<span class='relative inline-flex h-2 w-2 rounded-full bg-green-400'></span>
|
||||
</span>
|
||||
|
||||
<p class='font-medium'>Get free template</p>
|
||||
</a>
|
62
src/components/componentsBefore/GiscusComment.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import Giscus from '@giscus/react'
|
||||
|
||||
const id = 'inject-comments'
|
||||
|
||||
const GiscusComment = () => {
|
||||
const [theme, setTheme] = React.useState('preferred_color_scheme')
|
||||
|
||||
React.useEffect(() => {
|
||||
// Initial theme setup
|
||||
const savedTheme = localStorage.getItem('theme')
|
||||
if (savedTheme) {
|
||||
setTheme(savedTheme)
|
||||
}
|
||||
|
||||
// Listen for theme changes
|
||||
const handleThemeChange = (e: CustomEvent<{ theme: string }>) => {
|
||||
setTheme(e.detail.theme)
|
||||
}
|
||||
|
||||
document.addEventListener('theme-change', handleThemeChange as EventListener)
|
||||
|
||||
// Listen for astro:after-swap events (view transitions)
|
||||
const handleAfterSwap = () => {
|
||||
const currentTheme = localStorage.getItem('theme')
|
||||
if (currentTheme) {
|
||||
setTheme(currentTheme)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('astro:after-swap', handleAfterSwap)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('theme-change', handleThemeChange as EventListener)
|
||||
document.removeEventListener('astro:after-swap', handleAfterSwap)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div id={id} className='mt-8 w-full'>
|
||||
<Giscus
|
||||
id={id}
|
||||
repo='KazooTTT/kazoottt-blog'
|
||||
repoId='R_kgDOMa4jRQ'
|
||||
category='Announcements'
|
||||
categoryId='DIC_kwDOMa4jRc4CjRFe'
|
||||
mapping='pathname'
|
||||
strict='0'
|
||||
reactionsEnabled='1'
|
||||
emitMetadata='0'
|
||||
inputPosition='bottom'
|
||||
theme={theme}
|
||||
lang='zh-CN'
|
||||
loading='eager'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GiscusComment
|
6
src/components/componentsBefore/GithubHotLine.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import GitHubCalendar from 'react-github-calendar'
|
||||
const GithubHotLine = () => {
|
||||
return <GitHubCalendar username='kazoottt' />
|
||||
}
|
||||
|
||||
export default GithubHotLine
|
18
src/components/componentsBefore/Label.astro
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
import { cn } from '@/utils'
|
||||
|
||||
const { class: className, as: Tag = 'div', title, href, ...props } = Astro.props
|
||||
---
|
||||
|
||||
<Tag
|
||||
class={cn(
|
||||
className,
|
||||
'flex flex-row items-center justify-center gap-x-2',
|
||||
href && 'hover:opacity-75 transition-all'
|
||||
)}
|
||||
href={href}
|
||||
{...props}
|
||||
>
|
||||
<slot name='icon' />
|
||||
<p>{title}</p>
|
||||
</Tag>
|
36
src/components/componentsBefore/PageViews.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
interface PageViewsProps {
|
||||
slug: string
|
||||
}
|
||||
|
||||
export default function PageViews({ slug }: PageViewsProps) {
|
||||
const [views, setViews] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
console.log('PageViews component mounted with slug:', slug)
|
||||
async function updatePageView() {
|
||||
try {
|
||||
// First increment the view count
|
||||
const postResponse = await fetch(`/api/pageview/${slug}`, {
|
||||
method: 'POST'
|
||||
})
|
||||
if (!postResponse.ok) throw new Error('Failed to increment view count')
|
||||
|
||||
// Then get the updated count
|
||||
const { views } = await postResponse.json()
|
||||
console.log('Updated views:', views)
|
||||
setViews(views)
|
||||
} catch (error) {
|
||||
console.error('Error updating page views:', error)
|
||||
setViews(null)
|
||||
}
|
||||
}
|
||||
|
||||
updatePageView()
|
||||
}, [slug])
|
||||
|
||||
return <span className='text-sm text-gray-500'>Views: {views === null ? '-' : views}</span>
|
||||
}
|
29
src/components/componentsBefore/Paginator.astro
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
import type { PaginationLink } from '@/types'
|
||||
|
||||
interface Props {
|
||||
nextUrl?: PaginationLink
|
||||
prevUrl?: PaginationLink
|
||||
}
|
||||
|
||||
const { nextUrl, prevUrl } = Astro.props
|
||||
---
|
||||
|
||||
{
|
||||
(prevUrl || nextUrl) && (
|
||||
<nav class='mt-8 flex items-center gap-x-4'>
|
||||
{prevUrl && (
|
||||
<a class='me-auto py-2' data-astro-prefetch href={prevUrl.url}>
|
||||
{prevUrl.srLabel && <span class='sr-only'>{prevUrl.srLabel}</span>}
|
||||
{prevUrl.text ? prevUrl.text : 'Previous'}
|
||||
</a>
|
||||
)}
|
||||
{nextUrl && (
|
||||
<a class='ms-auto py-2' data-astro-prefetch href={nextUrl.url}>
|
||||
{nextUrl.srLabel && <span class='sr-only'>{nextUrl.srLabel}</span>}
|
||||
{nextUrl.text ? nextUrl.text : 'Next'}
|
||||
</a>
|
||||
)}
|
||||
</nav>
|
||||
)
|
||||
}
|
52
src/components/componentsBefore/ProjectCard.astro
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
import { Image } from 'astro:assets'
|
||||
import type { ImageMetadata } from 'astro'
|
||||
import { cn } from '@/utils'
|
||||
|
||||
const {
|
||||
as: Tag = 'a',
|
||||
class: className,
|
||||
href,
|
||||
heading,
|
||||
subheading,
|
||||
imagePath,
|
||||
altText,
|
||||
target
|
||||
} = Astro.props
|
||||
|
||||
let imageComponent = null
|
||||
if (imagePath) {
|
||||
const images = import.meta.glob<{ default: ImageMetadata }>('/src/assets/*.{jpeg,jpg,png,gif}')
|
||||
if (images[imagePath]) {
|
||||
imageComponent = images[imagePath]
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
<Tag
|
||||
class={cn(
|
||||
className,
|
||||
'flex flex-col rounded-2xl border border-border bg-primary-foreground h-[240px]',
|
||||
'justify-center items-center',
|
||||
href && 'transition-all hover:border-foreground/25 hover:shadow-sm'
|
||||
)}
|
||||
href={href}
|
||||
target={target}
|
||||
>
|
||||
{
|
||||
imageComponent && (
|
||||
<Image
|
||||
src={imageComponent()}
|
||||
alt={altText}
|
||||
class='h-32 w-full rounded-2xl rounded-bl-none rounded-br-none object-cover'
|
||||
loading='eager'
|
||||
/>
|
||||
)
|
||||
}
|
||||
<div class='flex w-full flex-1 flex-col justify-center gap-y-2 px-5 py-4 text-center'>
|
||||
<h1 class='text-lg font-medium'>{heading}</h1>
|
||||
<h2 class='line-clamp-2 text-sm text-muted-foreground'>{subheading}</h2>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</Tag>
|
15
src/components/componentsBefore/Section.astro
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
import { cn } from '@/utils'
|
||||
|
||||
const { class: className, title, subtitle } = Astro.props
|
||||
---
|
||||
|
||||
<section class={cn(className, 'flex flex-col gap-y-5 md:flex-row md:gap-y-0')}>
|
||||
<div class='md:w-1/3'>
|
||||
<h2 class='font-semibol text-xl'>{title}</h2>
|
||||
<h3 class='text-muted-foreground'>{subtitle}</h3>
|
||||
</div>
|
||||
<div class='flex flex-col gap-y-3 md:w-2/3'>
|
||||
<slot />
|
||||
</div>
|
||||
</section>
|
11
src/components/componentsBefore/SkillLayout.astro
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
import Button from './Button.astro'
|
||||
const { title, skills } = Astro.props
|
||||
---
|
||||
|
||||
<div class='flex flex-col gap-y-2 md:flex-row md:gap-x-5 md:gap-y-0'>
|
||||
<h3 class='w-1/5 font-medium'>{title}</h3>
|
||||
<div class='flex w-4/5 flex-row flex-wrap gap-x-4 gap-y-2'>
|
||||
{skills.map((skill: string[]) => <Button as='button' title={skill} style='pill' />)}
|
||||
</div>
|
||||
</div>
|
42
src/components/componentsBefore/ThemeProvider.astro
Normal file
@ -0,0 +1,42 @@
|
||||
<script is:inline>
|
||||
const lightModePref = window.matchMedia('(prefers-color-scheme: light)')
|
||||
|
||||
// Get user preference from local storage or from browser preference
|
||||
function getUserPref() {
|
||||
const storedTheme = localStorage.getItem('theme') ?? undefined
|
||||
return storedTheme || (lightModePref.matches ? 'light' : 'dark')
|
||||
}
|
||||
|
||||
function setTheme(newTheme) {
|
||||
if (newTheme !== 'light' && newTheme !== 'dark') {
|
||||
return console.log(`Invalid theme value '${newTheme}' received. Expected 'light' or 'dark'.`)
|
||||
}
|
||||
|
||||
localStorage.setItem('theme', newTheme)
|
||||
|
||||
const root = document.documentElement
|
||||
|
||||
// if current dark theme and new theme is dark, return
|
||||
if (newTheme === 'dark' && root.classList.contains('dark')) {
|
||||
return
|
||||
} else if (newTheme === 'light' && !root.classList.contains('dark')) {
|
||||
return
|
||||
}
|
||||
|
||||
root.classList.toggle('dark')
|
||||
}
|
||||
|
||||
// Initial Setup
|
||||
setTheme(getUserPref())
|
||||
|
||||
// View Transitions hook to restore theme
|
||||
document.addEventListener('astro:after-swap', () => setTheme(getUserPref()))
|
||||
|
||||
// Listen for theme-change custom event
|
||||
document.addEventListener('theme-change', (e) => {
|
||||
setTheme(e.detail.theme)
|
||||
})
|
||||
|
||||
// Listen for prefers-color-scheme change
|
||||
lightModePref.addEventListener('change', (e) => setTheme(e.matches ? 'light' : 'dark'))
|
||||
</script>
|
48
src/components/componentsBefore/ToolSection.astro
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
import { cn } from "@/utils/tailwind";
|
||||
import { Icon } from "astro-icon/components";
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
title: string;
|
||||
tools: {
|
||||
name: string;
|
||||
description: string;
|
||||
href?: string;
|
||||
iconPath?: string;
|
||||
iconBgColour?: string;
|
||||
}[];
|
||||
}
|
||||
const { class: className, title, tools, ...props } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
class={cn(
|
||||
className,
|
||||
"flex flex-col rounded-xl border border-border py-5 px-3 gap-y-4 sm:gap-y-6"
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<h2 class="px-2 text-lg font-medium">{title}</h2>
|
||||
<div class="grid grid-cols-1 gap-x-2 gap-y-3 sm:grid-cols-2 sm:gap-y-4">
|
||||
{
|
||||
tools.map((tool) => (
|
||||
<a
|
||||
class="group relative hover:bg-transparent"
|
||||
href={tool.href}
|
||||
id={tool.name}
|
||||
target="_blank"
|
||||
>
|
||||
<div class="relative flex flex-row items-center gap-x-4 px-2 py-0.5 transition-all">
|
||||
<div class="bg-muted absolute -inset-0 z-10 rounded-lg opacity-0 transition-all group-hover:opacity-50" />
|
||||
<Icon name={tool.iconPath ?? "tool"} class="bg-muted z-20 h-10 w-10 rounded-lg p-2" />
|
||||
<div class="z-20 flex flex-col">
|
||||
<h3 class="font-medium">{tool.name}</h3>
|
||||
<p class="text-muted-foreground">{tool.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
197
src/components/componentsBefore/blog/Calendar.tsx
Normal file
@ -0,0 +1,197 @@
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
type Post = CollectionEntry<'post'>
|
||||
interface Props {
|
||||
posts: Post[]
|
||||
currentDate?: Date
|
||||
}
|
||||
|
||||
interface DateTimeFormatOptions {
|
||||
year?: 'numeric' | '2-digit'
|
||||
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow'
|
||||
day?: 'numeric' | '2-digit'
|
||||
}
|
||||
|
||||
const weekDays = ['一', '二', '三', '四', '五', '六', '日']
|
||||
|
||||
const getFormattedDate = (date: Date, options: DateTimeFormatOptions): string => {
|
||||
return new Intl.DateTimeFormat('zh-CN', options).format(date)
|
||||
}
|
||||
|
||||
export const Calendar: React.FC<Props> = ({ posts, currentDate: initialDate = new Date() }) => {
|
||||
const [currentDate, setCurrentDate] = useState(new Date(initialDate))
|
||||
|
||||
// 将日记按日期分组
|
||||
const postsByDate = new Map<string, Post[]>()
|
||||
posts.forEach((post) => {
|
||||
const date = getFormattedDate(new Date(post.data.date), {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})
|
||||
if (!postsByDate.has(date)) {
|
||||
postsByDate.set(date, [])
|
||||
}
|
||||
postsByDate.get(date)?.push(post)
|
||||
})
|
||||
|
||||
// 获取所有有日记的月份
|
||||
const months = posts.map((post) => {
|
||||
const date = new Date(post.data.date)
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
|
||||
})
|
||||
const uniqueMonths = Array.from(new Set(months)).sort()
|
||||
|
||||
const getCurrentMonthStr = (): string => {
|
||||
return `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
const canNavigateToMonth = (direction: 'prev' | 'next'): boolean => {
|
||||
const currentMonthStr = getCurrentMonthStr()
|
||||
if (direction === 'prev') {
|
||||
return uniqueMonths.some((m) => m < currentMonthStr)
|
||||
} else {
|
||||
const now = new Date()
|
||||
const currentMonth = new Date(currentDate.getFullYear(), currentDate.getMonth())
|
||||
const thisMonth = new Date(now.getFullYear(), now.getMonth())
|
||||
return currentMonth < thisMonth
|
||||
}
|
||||
}
|
||||
|
||||
const renderCalendarDays = () => {
|
||||
// 获取当月的第一天和最后一天
|
||||
const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1)
|
||||
const lastDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0)
|
||||
|
||||
// 获取当月第一天是星期几
|
||||
const firstDayWeekday = firstDayOfMonth.getDay()
|
||||
const adjustedFirstDayWeekday = firstDayWeekday === 0 ? 7 : firstDayWeekday
|
||||
|
||||
// 计算日历表格需要显示的天数
|
||||
const daysInPrevMonth = adjustedFirstDayWeekday - 1
|
||||
const daysInCurrentMonth = lastDayOfMonth.getDate()
|
||||
const totalDays = Math.ceil((daysInPrevMonth + daysInCurrentMonth) / 7) * 7
|
||||
|
||||
// 获取上个月的最后几天
|
||||
const lastDayOfPrevMonth = new Date(
|
||||
currentDate.getFullYear(),
|
||||
currentDate.getMonth(),
|
||||
0
|
||||
).getDate()
|
||||
|
||||
const days = []
|
||||
for (let i = 0; i < totalDays; i++) {
|
||||
const dayNumber = i - daysInPrevMonth + 1
|
||||
const isCurrentMonth = dayNumber > 0 && dayNumber <= daysInCurrentMonth
|
||||
const displayDay = isCurrentMonth
|
||||
? dayNumber
|
||||
: dayNumber <= 0
|
||||
? lastDayOfPrevMonth + dayNumber
|
||||
: dayNumber - daysInCurrentMonth
|
||||
|
||||
const date = new Date(
|
||||
currentDate.getFullYear(),
|
||||
isCurrentMonth
|
||||
? currentDate.getMonth()
|
||||
: dayNumber <= 0
|
||||
? currentDate.getMonth() - 1
|
||||
: currentDate.getMonth() + 1,
|
||||
displayDay
|
||||
)
|
||||
|
||||
const formattedDate = getFormattedDate(date, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})
|
||||
|
||||
const postsForDay = Array.from(postsByDate.get(formattedDate) || [])
|
||||
const hasPost = postsForDay.length > 0
|
||||
|
||||
days.push(
|
||||
<div
|
||||
key={i}
|
||||
className={`calendar-day min-h-[100px] rounded-lg border p-2 ${
|
||||
isCurrentMonth ? 'bg-primary-foreground' : 'bg-muted/50'
|
||||
} ${hasPost ? 'border-green-400/50' : 'border-border'}`}
|
||||
>
|
||||
<div className='mb-1 text-right'>
|
||||
<span
|
||||
className={`inline-block h-7 w-7 rounded-full text-center leading-7 ${
|
||||
!isCurrentMonth ? 'text-muted-foreground' : ''
|
||||
} ${hasPost ? 'bg-green-400/20' : ''}`}
|
||||
>
|
||||
{displayDay}
|
||||
</span>
|
||||
</div>
|
||||
{hasPost && (
|
||||
<div className='space-y-1 text-xs'>
|
||||
{postsForDay.map((post: Post) => (
|
||||
<a
|
||||
key={post.slug}
|
||||
href={`/diary/${post.slug}/`}
|
||||
className='line-clamp-2 block transition-colors hover:text-green-400'
|
||||
title={post.data.title}
|
||||
>
|
||||
{post.data.title}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return days
|
||||
}
|
||||
|
||||
const handlePrevMonth = () => {
|
||||
if (canNavigateToMonth('prev')) {
|
||||
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1))
|
||||
}
|
||||
}
|
||||
|
||||
const handleNextMonth = () => {
|
||||
if (canNavigateToMonth('next')) {
|
||||
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mx-auto w-full max-w-4xl'>
|
||||
<div className='mb-4 flex items-center justify-between'>
|
||||
<button
|
||||
onClick={handlePrevMonth}
|
||||
disabled={!canNavigateToMonth('prev')}
|
||||
className={`rounded-lg border border-border px-4 py-2 hover:border-green-400/50 ${
|
||||
!canNavigateToMonth('prev') ? 'cursor-not-allowed opacity-50' : ''
|
||||
}`}
|
||||
>
|
||||
上个月
|
||||
</button>
|
||||
<h2 className='text-xl font-bold'>
|
||||
{currentDate.getFullYear()}年{currentDate.getMonth() + 1}月
|
||||
</h2>
|
||||
<button
|
||||
onClick={handleNextMonth}
|
||||
disabled={!canNavigateToMonth('next')}
|
||||
className={`rounded-lg border border-border px-4 py-2 hover:border-green-400/50 ${
|
||||
!canNavigateToMonth('next') ? 'cursor-not-allowed opacity-50' : ''
|
||||
}`}
|
||||
>
|
||||
下个月
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-7 gap-1'>
|
||||
{weekDays.map((day) => (
|
||||
<div key={day} className='py-2 text-center font-medium'>
|
||||
{day}
|
||||
</div>
|
||||
))}
|
||||
{renderCalendarDays()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
102
src/components/componentsBefore/blog/Hero.astro
Normal file
@ -0,0 +1,102 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
import Card from '../Card.astro'
|
||||
import FormattedDate from '../FormattedDate.astro'
|
||||
import Label from '../Label.astro'
|
||||
|
||||
interface Props {
|
||||
content: CollectionEntry<'post'>
|
||||
simple?: boolean
|
||||
socialImage?: string
|
||||
}
|
||||
|
||||
const {
|
||||
content: { data, render },
|
||||
simple = false,
|
||||
socialImage
|
||||
} = Astro.props
|
||||
const { remarkPluginFrontmatter } = await render()
|
||||
---
|
||||
|
||||
{
|
||||
socialImage && (
|
||||
<div class='mb-6'>
|
||||
<img src={socialImage} class='rounded-2xl object-cover' loading='eager' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div class='flex flex-wrap items-center gap-x-3 gap-y-2'>
|
||||
<p class='text-xs'>
|
||||
<FormattedDate date={data.date} /> /{' '}
|
||||
{remarkPluginFrontmatter.minutesRead}
|
||||
</p>
|
||||
|
||||
{
|
||||
!simple && (
|
||||
<>
|
||||
{data.date_modified ? (
|
||||
<div class='flex flex-row items-center gap-x-2 text-xs'>
|
||||
<p class='text-muted-foreground'>最新更新:</p>
|
||||
<FormattedDate date={data.date_modified} />
|
||||
</div>
|
||||
) : null}
|
||||
<Label
|
||||
title={data.category ?? '未分类'}
|
||||
as='a'
|
||||
href={`/categories/${data.category ?? '未分类'}/`}
|
||||
>
|
||||
<Icon name='category' slot='icon' />
|
||||
</Label>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<h1 class='mt-2 text-3xl font-medium sm:mb-1'>
|
||||
{data.title}
|
||||
</h1>
|
||||
|
||||
{
|
||||
!simple && !!data.tags?.length && (
|
||||
<div class='mt-3 flex flex-row items-center gap-x-1'>
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
class='me-1 inline-block h-6 w-6'
|
||||
fill='none'
|
||||
focusable='false'
|
||||
stroke='currentColor'
|
||||
stroke-linecap='round'
|
||||
stroke-linejoin='round'
|
||||
stroke-width='1.5'
|
||||
viewBox='0 0 24 24'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path d='M0 0h24v24H0z' fill='none' stroke='none' />
|
||||
<path d='M7.859 6h-2.834a2.025 2.025 0 0 0 -2.025 2.025v2.834c0 .537 .213 1.052 .593 1.432l6.116 6.116a2.025 2.025 0 0 0 2.864 0l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-6.117 -6.116a2.025 2.025 0 0 0 -1.431 -.593z' />
|
||||
<path d='M17.573 18.407l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-7.117 -7.116' />
|
||||
<path d='M6 9h-.01' />
|
||||
</svg>
|
||||
<div class='space-x-1'>
|
||||
{data.tags?.map((tag) => (
|
||||
<a
|
||||
aria-label={`View more blogs with the tag ${tag}`}
|
||||
class="inline-block before:content-['#'] hover:underline hover:underline-offset-4"
|
||||
data-pagefind-filter='tag'
|
||||
href={`/tags/${tag}/`}
|
||||
>
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
data.description && data.description.trim().length > 0 && (
|
||||
<Card heading='摘要(由ai生成)' altText='摘要' class='my-4 w-full'>
|
||||
<div class='ml-4 text-muted-foreground'>{data.description}</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
39
src/components/componentsBefore/blog/PostPreview.astro
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
import type { HTMLTag, Polymorphic } from 'astro/types'
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
|
||||
import FormattedDate from '../FormattedDate.astro'
|
||||
|
||||
type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
|
||||
post: CollectionEntry<'post'>
|
||||
prefix?: string
|
||||
withDesc?: boolean
|
||||
showYear?: boolean
|
||||
}
|
||||
|
||||
const { as: Tag = 'div', post, prefix = '/blog/', withDesc = false, showYear = false } = Astro.props
|
||||
const postDate = post.data.date
|
||||
const year = postDate.getFullYear()
|
||||
const coverImage = post.data.coverImage
|
||||
---
|
||||
|
||||
{showYear && <h2 class='my-2 text-xl font-semibold'>{year}</h2>}
|
||||
<li class='flex flex-col gap-2 sm:flex-row sm:gap-x-4 [&_q]:basis-full'>
|
||||
<FormattedDate class='min-w-[80px]' date={postDate} />
|
||||
|
||||
<Tag>
|
||||
{post.data.draft && <span class='text-red-500'>(Draft) </span>}
|
||||
<a
|
||||
data-astro-prefetch
|
||||
href={`${prefix}${post.slug}/`}
|
||||
class='transition-all hover:text-muted-foreground'
|
||||
>
|
||||
{post.data.title}
|
||||
</a>
|
||||
{
|
||||
withDesc && (
|
||||
<p class='line-clamp-2 text-sm italic text-muted-foreground'>{post.data.description}</p>
|
||||
)
|
||||
}
|
||||
</Tag>
|
||||
</li>
|
31
src/components/componentsBefore/blog/TOC.astro
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
import type { MarkdownHeading } from 'astro'
|
||||
import { generateToc } from '@/utils'
|
||||
|
||||
import TOCHeading from './TOCHeading.astro'
|
||||
|
||||
interface Props {
|
||||
headings: MarkdownHeading[]
|
||||
}
|
||||
|
||||
const { headings } = Astro.props
|
||||
|
||||
const toc = generateToc(headings)
|
||||
---
|
||||
|
||||
<aside
|
||||
class='sticky top-20 order-2 -me-28 hidden h-[calc(100vh-6rem)] basis-60 lg:flex lg:flex-col'
|
||||
>
|
||||
{
|
||||
toc.length > 0 && (
|
||||
<>
|
||||
<h2 class='mb-4 font-semibold'>TABLE OF CONTENTS</h2>
|
||||
<ul class='max-h-[calc(100vh-10rem)] overflow-y-auto pr-4 text-card-foreground'>
|
||||
{toc.map((heading) => (
|
||||
<TOCHeading heading={heading} />
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</aside>
|
152
src/components/componentsBefore/blog/TOCHeading.astro
Normal file
@ -0,0 +1,152 @@
|
||||
---
|
||||
import type { TocItem } from '@/utils'
|
||||
|
||||
interface Props {
|
||||
heading: TocItem
|
||||
}
|
||||
|
||||
const {
|
||||
heading: { depth, slug, subheadings, text }
|
||||
} = Astro.props
|
||||
---
|
||||
|
||||
<li>
|
||||
<div class='flex items-center gap-1'>
|
||||
{
|
||||
subheadings.length > 0 && (
|
||||
<button
|
||||
class='collapse-button flex h-4 w-4 items-center justify-center rounded-sm hover:bg-accent/50'
|
||||
aria-label='Toggle section'
|
||||
data-slug={slug}
|
||||
>
|
||||
<svg
|
||||
class='h-3 w-3 transform transition-transform duration-200'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
stroke-width='2'
|
||||
stroke-linecap='round'
|
||||
stroke-linejoin='round'
|
||||
>
|
||||
<polyline points='6 9 12 15 18 9' />
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
<a
|
||||
aria-label={`Scroll to section: ${text}`}
|
||||
class={`line-clamp-2 hover:text-foreground toc-link flex-1
|
||||
${depth === 1 ? 'text-base font-semibold' : depth === 2 ? 'text-base' : 'text-sm'}
|
||||
text-foreground/75 transition-all`}
|
||||
href={`#${slug}`}
|
||||
data-slug={slug}>{text}</a
|
||||
>
|
||||
</div>
|
||||
{
|
||||
!!subheadings.length && (
|
||||
<ul
|
||||
class='toc-list ms-6 overflow-hidden transition-all duration-300 ease-in-out'
|
||||
style='max-height: none;'
|
||||
>
|
||||
{subheadings.map((subheading) => (
|
||||
<Astro.self heading={subheading} />
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</li>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Collapse/Expand functionality
|
||||
const buttons = document.querySelectorAll('.collapse-button')
|
||||
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
const btn = e.currentTarget as HTMLElement
|
||||
const li = btn.closest('li')
|
||||
const ul = li?.querySelector('.toc-list') as HTMLElement
|
||||
const svg = btn.querySelector('svg')
|
||||
|
||||
if (ul && svg) {
|
||||
if (ul.style.maxHeight === '0px') {
|
||||
// Expand
|
||||
ul.style.maxHeight = ul.scrollHeight + 'px'
|
||||
svg.style.transform = ''
|
||||
} else {
|
||||
// Collapse
|
||||
ul.style.maxHeight = '0px'
|
||||
svg.style.transform = 'rotate(-180deg)'
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Smooth scrolling with offset
|
||||
const tocLinks = document.querySelectorAll('.toc-link')
|
||||
tocLinks.forEach((link) => {
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault()
|
||||
const slug = link.getAttribute('data-slug')
|
||||
if (slug) {
|
||||
const element = document.getElementById(slug)
|
||||
if (element) {
|
||||
const offset = 80 // Adjust this value based on header height
|
||||
const elementPosition = element.getBoundingClientRect().top
|
||||
const offsetPosition = elementPosition + window.pageYOffset - offset
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Active menu highlighting
|
||||
const observerOptions = {
|
||||
rootMargin: '-80px 0px -40% 0px',
|
||||
threshold: 1.0
|
||||
}
|
||||
|
||||
const headings = Array.from(
|
||||
document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]')
|
||||
)
|
||||
const headingElements = new Map()
|
||||
|
||||
headings.forEach((heading) => {
|
||||
const id = heading.getAttribute('id')!
|
||||
const tocLink = document.querySelector(`.toc-link[data-slug="${id}"]`)
|
||||
if (tocLink) {
|
||||
headingElements.set(heading, tocLink)
|
||||
}
|
||||
})
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
const tocLink = headingElements.get(entry.target)
|
||||
if (tocLink) {
|
||||
if (entry.isIntersecting) {
|
||||
// Remove active class from all links
|
||||
document.querySelectorAll('.toc-link').forEach((link) => {
|
||||
link.classList.remove('active', 'text-foreground')
|
||||
})
|
||||
// Add active class to current link
|
||||
tocLink.classList.add('active', 'text-foreground')
|
||||
}
|
||||
}
|
||||
})
|
||||
}, observerOptions)
|
||||
|
||||
headings.forEach((heading) => observer.observe(heading))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.toc-link.active {
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
72
src/components/componentsBefore/layout/Footer.astro
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import PageViews from '../PageViews'
|
||||
|
||||
const pathname = new URL(Astro.request.url).pathname
|
||||
const slug = pathname === '/' ? 'home' : pathname.replace(/^\/|\/$/g, '')
|
||||
---
|
||||
|
||||
<footer class='mx-auto mt-24 w-full'>
|
||||
<div class='border-t border-border pt-5'>
|
||||
<div
|
||||
class='flex flex-col items-center gap-y-3 sm:flex sm:flex-row sm:items-center sm:justify-between sm:gap-y-0'
|
||||
>
|
||||
<div class='flex gap-x-4 text-sm'>
|
||||
<p class=''>2024 kazoottt. All rights reserved.</p>
|
||||
</div>
|
||||
|
||||
<div class='flex items-center justify-between'>
|
||||
<!-- Social Brands -->
|
||||
<div class='flex items-center gap-x-4'>
|
||||
<div class='text-center'>
|
||||
<PageViews client:load slug={slug} />
|
||||
</div>
|
||||
<!-- Linkedin -->
|
||||
<a
|
||||
class='inline-block text-muted-foreground transition-all hover:text-muted-foreground/75'
|
||||
href='mailto:work@kazoottt.top'
|
||||
aria-label='Email'
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='20'
|
||||
height='20'
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
stroke-width='2'
|
||||
stroke-linecap='round'
|
||||
stroke-linejoin='round'
|
||||
class='h-5 w-5'
|
||||
>
|
||||
<rect width='20' height='16' x='2' y='4' rx='2'></rect>
|
||||
<path d='m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7'></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class='inline-block text-muted-foreground transition-all hover:text-muted-foreground/75'
|
||||
href='https://github.com/kazoottt'
|
||||
aria-label='GitHub'
|
||||
>
|
||||
<Icon name='github' class='h-5 w-5' />
|
||||
</a>
|
||||
<a
|
||||
class='inline-block text-muted-foreground transition-all hover:text-muted-foreground/75'
|
||||
href='https://x.com/kazoottt'
|
||||
aria-label='Twitter'
|
||||
>
|
||||
<Icon name='twitter' class='h-5 w-5' />
|
||||
</a>
|
||||
<a
|
||||
class='inline-block text-muted-foreground transition-all hover:text-muted-foreground/75'
|
||||
href='/rss.xml'
|
||||
aria-label='RSS Feed'
|
||||
>
|
||||
<Icon name='rss' class='h-5 w-5' />
|
||||
</a>
|
||||
</div>
|
||||
<!-- End Social Brands -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
277
src/components/componentsBefore/layout/Header.astro
Normal file
@ -0,0 +1,277 @@
|
||||
---
|
||||
import kazootttAvatar from '../../assets/kazoottt-avatar.jpeg'
|
||||
import { Image } from 'astro:assets'
|
||||
---
|
||||
|
||||
<header class='fixed left-0 right-0 top-0 z-50 bg-white shadow-sm dark:bg-gray-800'>
|
||||
<nav
|
||||
class='mx-auto flex w-full items-center justify-between px-4 py-3 lg:flex lg:w-3/5 lg:w-4/5 lg:items-center'
|
||||
aria-label='global'
|
||||
>
|
||||
<a class='flex items-center' href='/'>
|
||||
<Image
|
||||
src={kazootttAvatar}
|
||||
alt='profile photo'
|
||||
class='mr-2 h-8 w-auto rounded-full lg:hidden'
|
||||
loading='eager'
|
||||
/>
|
||||
<div class='hidden flex-none text-xl font-semibold lg:block' aria-label='Brand'>声控烤箱</div>
|
||||
</a>
|
||||
|
||||
<!-- Mobile menu button -->
|
||||
<button
|
||||
id='mobileMenuButton'
|
||||
class='rounded-md p-2 hover:bg-border lg:hidden'
|
||||
aria-label='Toggle mobile menu'
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='0 0 24 24'
|
||||
class='h-6 w-6'
|
||||
>
|
||||
<path fill='currentColor' d='M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z'></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class='hidden flex-row items-center justify-center gap-x-7 lg:flex'>
|
||||
<div class='relative'>
|
||||
<a
|
||||
href='/blog'
|
||||
class={`peer flex items-center text-[1.05rem] font-medium ${Astro.url.pathname.startsWith('/blog') ? 'text-green-400' : ''}`}
|
||||
aria-label='Blog Menu'
|
||||
>
|
||||
Blog
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='20'
|
||||
height='20'
|
||||
viewBox='0 0 24 24'
|
||||
class='ml-1 transform transition-transform duration-200 peer-hover:rotate-180'
|
||||
>
|
||||
<path fill='currentColor' d='m12 15l-5-5h10z'></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div
|
||||
class='invisible absolute left-0 mt-2 w-48 rounded-md bg-white py-2 opacity-0 shadow-lg transition-all duration-200 hover:visible hover:opacity-100 peer-hover:visible peer-hover:opacity-100 dark:bg-gray-800'
|
||||
>
|
||||
<a
|
||||
href='/categories'
|
||||
class={`block px-6 py-3 text-[1.05rem] font-medium transition-colors hover:bg-gray-100 ${Astro.url.pathname.startsWith('/categories') ? 'text-green-400' : ''} dark:hover:bg-gray-700`}
|
||||
aria-label='Categories Page'
|
||||
>
|
||||
Categories
|
||||
</a>
|
||||
<a
|
||||
href='/tags'
|
||||
class={`block px-6 py-3 text-[1.05rem] font-medium transition-colors hover:bg-gray-100 ${Astro.url.pathname.startsWith('/tags') ? 'text-green-400' : ''} dark:hover:bg-gray-700`}
|
||||
aria-label='Tags Page'
|
||||
>
|
||||
Tags
|
||||
</a>
|
||||
<a
|
||||
href='/diary'
|
||||
class={`block px-6 py-3 text-[1.05rem] font-medium transition-colors hover:bg-gray-100 ${Astro.url.pathname.startsWith('/diary') ? 'text-green-400' : ''} dark:hover:bg-gray-700`}
|
||||
aria-label='Diary Page'
|
||||
>
|
||||
Diary
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href='/tools'
|
||||
class={`flex-none text-[1.05rem] font-medium ${Astro.url.pathname.startsWith('/tools') ? 'text-green-400' : ''}`}
|
||||
aria-label='Nav Menu Item'
|
||||
>Tools
|
||||
</a>
|
||||
<div class='relative'>
|
||||
<button class='peer flex items-center text-[1.05rem] font-medium' aria-label='More Menu'>
|
||||
More
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='20'
|
||||
height='20'
|
||||
viewBox='0 0 24 24'
|
||||
class='ml-1 transform transition-transform duration-200 peer-hover:rotate-180'
|
||||
>
|
||||
<path fill='currentColor' d='m12 15l-5-5h10z'></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class='invisible absolute right-0 mt-2 w-48 rounded-md bg-white py-2 opacity-0 shadow-lg transition-all duration-200 hover:visible hover:opacity-100 peer-hover:visible peer-hover:opacity-100 dark:bg-gray-800'
|
||||
>
|
||||
<a
|
||||
href='/friends'
|
||||
class={`block px-6 py-3 text-[1.05rem] font-medium transition-colors hover:bg-gray-100 ${Astro.url.pathname.startsWith('/friends') ? 'text-green-400' : ''} dark:hover:bg-gray-700`}
|
||||
aria-label='Friends Page'
|
||||
>
|
||||
Friends
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
id='toggleDarkMode'
|
||||
class='relative rounded-md border border-border p-1.5 transition-all hover:bg-border'
|
||||
>
|
||||
<span class='sr-only'>Dark Theme</span>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='32'
|
||||
height='32'
|
||||
viewBox='0 0 24 24'
|
||||
class='h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:hidden dark:-rotate-90 dark:scale-0'
|
||||
><path
|
||||
fill='currentColor'
|
||||
d='M12 15q1.25 0 2.125-.875T15 12q0-1.25-.875-2.125T12 9q-1.25 0-2.125.875T9 12q0 1.25.875 2.125T12 15m0 1q-1.671 0-2.836-1.164T8 12q0-1.671 1.164-2.836T12 8q1.671 0 2.836 1.164T16 12q0 1.671-1.164 2.836T12 16m-7-3.5H1.5v-1H5zm17.5 0H19v-1h3.5zM11.5 5V1.5h1V5zm0 17.5V19h1v3.5zM6.746 7.404l-2.16-2.098l.695-.744l2.111 2.134zM18.72 19.438l-2.117-2.14l.652-.702l2.16 2.098zM16.596 6.746l2.098-2.16l.744.695l-2.134 2.111zM4.562 18.72l2.14-2.117l.663.652l-2.078 2.179zM12 12'
|
||||
></path></svg
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='32'
|
||||
height='32'
|
||||
viewBox='0 0 24 24'
|
||||
class='hidden h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:block dark:rotate-0 dark:scale-100'
|
||||
><path
|
||||
fill='currentColor'
|
||||
d='M12.058 20q-3.334 0-5.667-2.333Q4.058 15.333 4.058 12q0-3.038 1.98-5.27Q8.02 4.5 10.942 4.097q.081 0 .159.006t.153.017q-.506.706-.801 1.57q-.295.865-.295 1.811q0 2.667 1.866 4.533q1.867 1.867 4.534 1.867q.952 0 1.813-.295q.862-.295 1.548-.801q.012.075.018.153q.005.078.005.158q-.384 2.923-2.615 4.904T12.057 20'
|
||||
></path></svg
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Navigation -->
|
||||
<div
|
||||
id='mobileMenu'
|
||||
class='fixed inset-x-0 top-[72px] z-50 hidden max-h-[calc(100vh-72px)] overflow-y-auto rounded-b-lg bg-white shadow-lg dark:bg-gray-800 lg:hidden'
|
||||
>
|
||||
<div class='space-y-2 px-4 py-2'>
|
||||
<a
|
||||
href='/blog'
|
||||
class={`block py-2 text-[1.05rem] font-medium ${Astro.url.pathname.startsWith('/blog') ? 'text-green-400' : ''}`}
|
||||
>
|
||||
Blog
|
||||
</a>
|
||||
<a
|
||||
href='/categories'
|
||||
class={`block py-2 text-[1.05rem] font-medium ${Astro.url.pathname.startsWith('/categories') ? 'text-green-400' : ''}`}
|
||||
>
|
||||
Categories
|
||||
</a>
|
||||
<a
|
||||
href='/tags'
|
||||
class={`block py-2 text-[1.05rem] font-medium ${Astro.url.pathname.startsWith('/tags') ? 'text-green-400' : ''}`}
|
||||
>
|
||||
Tags
|
||||
</a>
|
||||
<a
|
||||
href='/diary'
|
||||
class={`block py-2 text-[1.05rem] font-medium ${Astro.url.pathname.startsWith('/diary') ? 'text-green-400' : ''}`}
|
||||
>
|
||||
Diary
|
||||
</a>
|
||||
<a
|
||||
href='/tools'
|
||||
class={`block py-2 text-[1.05rem] font-medium ${Astro.url.pathname.startsWith('/tools') ? 'text-green-400' : ''}`}
|
||||
>
|
||||
Tools
|
||||
</a>
|
||||
<a
|
||||
href='/friends'
|
||||
class={`block py-2 text-[1.05rem] font-medium ${Astro.url.pathname.startsWith('/friends') ? 'text-green-400' : ''}`}
|
||||
>
|
||||
Friends
|
||||
</a>
|
||||
<button
|
||||
id='mobileToggleDarkMode'
|
||||
class='mt-2 flex w-full items-center py-2 text-[1.05rem] font-medium'
|
||||
>
|
||||
<span>Theme</span>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='32'
|
||||
height='32'
|
||||
viewBox='0 0 24 24'
|
||||
class='ml-2 h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:hidden dark:-rotate-90 dark:scale-0'
|
||||
><path
|
||||
fill='currentColor'
|
||||
d='M12 15q1.25 0 2.125-.875T15 12q0-1.25-.875-2.125T12 9q-1.25 0-2.125.875T9 12q0 1.25.875 2.125T12 15m0 1q-1.671 0-2.836-1.164T8 12q0-1.671 1.164-2.836T12 8q1.671 0 2.836 1.164T16 12q0 1.671-1.164 2.836T12 16m-7-3.5H1.5v-1H5zm17.5 0H19v-1h3.5zM11.5 5V1.5h1V5zm0 17.5V19h1v3.5zM6.746 7.404l-2.16-2.098l.695-.744l2.111 2.134zM18.72 19.438l-2.117-2.14l.652-.702l2.16 2.098zM16.596 6.746l2.098-2.16l.744.695l-2.134 2.111zM4.562 18.72l2.14-2.117l.663.652l-2.078 2.179zM12 12'
|
||||
></path></svg
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='32'
|
||||
height='32'
|
||||
viewBox='0 0 24 24'
|
||||
class='ml-2 hidden h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:block dark:rotate-0 dark:scale-100'
|
||||
><path
|
||||
fill='currentColor'
|
||||
d='M12.058 20q-3.334 0-5.667-2.333Q4.058 15.333 4.058 12q0-3.038 1.98-5.27Q8.02 4.5 10.942 4.097q.081 0 .159.006t.153.017q-.506.706-.801 1.57q-.295.865-.295 1.811q0 2.667 1.866 4.533q1.867 1.867 4.534 1.867q.952 0 1.813-.295q.862-.295 1.548-.801q.012.075.018.153q.005.078.005.158q-.384 2.923-2.615 4.904T12.057 20'
|
||||
></path></svg
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<script>
|
||||
function getCurrentTheme() {
|
||||
return localStorage.getItem('theme')
|
||||
}
|
||||
|
||||
function setupDarkModeToggle() {
|
||||
const toggleDarkModeButton = document.getElementById('toggleDarkMode')
|
||||
const mobileToggleDarkMode = document.getElementById('mobileToggleDarkMode')
|
||||
|
||||
function toggleTheme() {
|
||||
const currentTheme = getCurrentTheme()
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
|
||||
localStorage.setItem('theme', newTheme)
|
||||
document.documentElement.classList.toggle('dark')
|
||||
|
||||
// Dispatch theme change event
|
||||
window.dispatchEvent(new CustomEvent('theme-change', { detail: { theme: newTheme } }))
|
||||
}
|
||||
|
||||
if (toggleDarkModeButton) {
|
||||
toggleDarkModeButton.addEventListener('click', toggleTheme)
|
||||
}
|
||||
|
||||
if (mobileToggleDarkMode) {
|
||||
mobileToggleDarkMode.addEventListener('click', toggleTheme)
|
||||
}
|
||||
}
|
||||
|
||||
function setupMobileMenu() {
|
||||
const mobileMenuButton = document.getElementById('mobileMenuButton')
|
||||
const mobileMenu = document.getElementById('mobileMenu')
|
||||
if (!mobileMenuButton || !mobileMenu) return
|
||||
|
||||
mobileMenuButton.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden')
|
||||
})
|
||||
|
||||
// Close mobile menu when clicking outside
|
||||
document.addEventListener('click', (event) => {
|
||||
if (
|
||||
!mobileMenuButton.contains(event.target as Node) &&
|
||||
!mobileMenu.contains(event.target as Node)
|
||||
) {
|
||||
mobileMenu.classList.add('hidden')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Setup initial functionality
|
||||
setupDarkModeToggle()
|
||||
setupMobileMenu()
|
||||
|
||||
// Re-setup functionality after view transitions
|
||||
document.addEventListener('astro:after-swap', () => {
|
||||
setupDarkModeToggle()
|
||||
setupMobileMenu()
|
||||
})
|
||||
</script>
|
384
src/components/tools/index.astro
Normal file
@ -0,0 +1,384 @@
|
||||
---
|
||||
import CategorySection from "@/components/componentsBefore/CategorySection.astro";
|
||||
|
||||
const SOFTWARE_TOOLS = {
|
||||
development: {
|
||||
title: "Development",
|
||||
sections: {
|
||||
ide: {
|
||||
title: "IDE & Editors",
|
||||
tools: [
|
||||
{
|
||||
name: "Cursor",
|
||||
description: "AI-Powered Code Editor",
|
||||
href: "https://cursor.sh/",
|
||||
iconPath: "cursor",
|
||||
},
|
||||
{
|
||||
name: "VS Code",
|
||||
description: "Code Editor",
|
||||
href: "https://code.visualstudio.com/",
|
||||
iconPath: "vscode",
|
||||
},
|
||||
{
|
||||
name: "JetBrains Suite",
|
||||
description: "Professional IDEs",
|
||||
href: "https://www.jetbrains.com/",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
design: {
|
||||
title: "Design & Creative",
|
||||
sections: {
|
||||
design: {
|
||||
title: "Design Software",
|
||||
tools: [
|
||||
{
|
||||
name: "Figma",
|
||||
description: "Design Tool",
|
||||
href: "https://www.figma.com/",
|
||||
iconPath: "figma",
|
||||
},
|
||||
{
|
||||
name: "Canva",
|
||||
description: "Design Tool",
|
||||
href: "https://www.canva.com/",
|
||||
iconPath: "canva",
|
||||
},
|
||||
],
|
||||
},
|
||||
recording: {
|
||||
title: "Screen Recording & Screenshots",
|
||||
tools: [
|
||||
{
|
||||
name: "CleanShot X",
|
||||
description: "Screenshot & Recording",
|
||||
href: "https://cleanshot.com/",
|
||||
},
|
||||
{
|
||||
name: "OBS Studio",
|
||||
description: "Streaming & Recording",
|
||||
href: "https://obsproject.com/",
|
||||
},
|
||||
{
|
||||
name: "Picsew",
|
||||
description: "Screenshot Tool",
|
||||
href: "https://apps.apple.com/app/picsew-screenshot-stitching/id1208145167",
|
||||
},
|
||||
{
|
||||
name: "shottr",
|
||||
description: "Screenshot Tool",
|
||||
href: "https://shottr.cc/",
|
||||
},
|
||||
{
|
||||
name: "QuickRecorder",
|
||||
description: "Screen Recording",
|
||||
href: "https://lihaoyun6.github.io/quickrecorder/",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
productivity: {
|
||||
title: "Productivity",
|
||||
sections: {
|
||||
browser: {
|
||||
title: "Browsers",
|
||||
tools: [
|
||||
{
|
||||
name: "Arc Browser",
|
||||
description: "Modern Browser",
|
||||
href: "https://arc.net/",
|
||||
iconPath: "arc",
|
||||
},
|
||||
{
|
||||
name: "Google Chrome",
|
||||
description: "Web Browser",
|
||||
href: "https://www.google.com/chrome/",
|
||||
},
|
||||
{
|
||||
name: "Microsoft Edge",
|
||||
description: "Web Browser",
|
||||
href: "https://www.microsoft.com/edge",
|
||||
},
|
||||
],
|
||||
},
|
||||
notes: {
|
||||
title: "Note Taking",
|
||||
tools: [
|
||||
{
|
||||
name: "Obsidian",
|
||||
description: "Note Taking",
|
||||
href: "https://obsidian.md/",
|
||||
iconPath: "obsidian",
|
||||
},
|
||||
{
|
||||
name: "Notion",
|
||||
description: "Note Taking",
|
||||
href: "https://notion.so/",
|
||||
iconPath: "notion",
|
||||
},
|
||||
{
|
||||
name: "Flomo",
|
||||
description: "Quick Notes",
|
||||
href: "https://flomoapp.com/",
|
||||
},
|
||||
],
|
||||
},
|
||||
tools: {
|
||||
title: "Productivity Tools",
|
||||
tools: [
|
||||
{
|
||||
name: "1Password",
|
||||
description: "Password Manager",
|
||||
href: "https://1password.com/",
|
||||
},
|
||||
{
|
||||
name: "Raindrop.io",
|
||||
description: "Bookmark Manager",
|
||||
href: "https://raindrop.io/",
|
||||
},
|
||||
{
|
||||
name: "n8n",
|
||||
description: "Workflow Automation",
|
||||
href: "https://n8n.io/",
|
||||
},
|
||||
{
|
||||
name: "Follow",
|
||||
description: "RSS Reader",
|
||||
href: "https://app.follow.is/",
|
||||
},
|
||||
{
|
||||
name: "滴答清单",
|
||||
description: "Task Management",
|
||||
href: "https://dida365.com/",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
ai: {
|
||||
title: "AI Tools",
|
||||
sections: {
|
||||
assistants: {
|
||||
title: "AI Assistants",
|
||||
tools: [
|
||||
{
|
||||
name: "ChatGPT",
|
||||
description: "AI Assistant",
|
||||
href: "https://chat.openai.com/",
|
||||
iconPath: "chatgpt",
|
||||
},
|
||||
{
|
||||
name: "Claude",
|
||||
description: "AI Assistant",
|
||||
href: "https://claude.ai/",
|
||||
},
|
||||
{
|
||||
name: "Poe",
|
||||
description: "AI Platform",
|
||||
href: "https://poe.com/",
|
||||
},
|
||||
{
|
||||
name: "Google Notebook LLM",
|
||||
description: "AI Assistant",
|
||||
href: "https://notebooklm.google.com/",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
media: {
|
||||
title: "Media & Entertainment",
|
||||
sections: {
|
||||
music: {
|
||||
title: "Music",
|
||||
tools: [
|
||||
{
|
||||
name: "Apple Music",
|
||||
description: "Music Streaming",
|
||||
href: "https://music.apple.com/",
|
||||
},
|
||||
{
|
||||
name: "网易云音乐",
|
||||
description: "Music Platform",
|
||||
href: "https://music.163.com/",
|
||||
},
|
||||
],
|
||||
},
|
||||
reading: {
|
||||
title: "Reading & Writing",
|
||||
tools: [
|
||||
{
|
||||
name: "微信读书",
|
||||
description: "Reading Platform",
|
||||
href: "https://weread.qq.com/",
|
||||
},
|
||||
{
|
||||
name: "Personal Blog",
|
||||
description: "Built with Astro",
|
||||
href: "/",
|
||||
},
|
||||
{
|
||||
name: "Hashnode",
|
||||
description: "Blog Platform",
|
||||
href: "https://hashnode.com/",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
health: {
|
||||
title: "Health & Fitness",
|
||||
sections: {
|
||||
tracking: {
|
||||
title: "Health Tracking",
|
||||
tools: [
|
||||
{
|
||||
name: "AutoSleep",
|
||||
description: "Sleep Tracking",
|
||||
href: "https://autosleep.app/",
|
||||
},
|
||||
{
|
||||
name: "Grow",
|
||||
description: "Health Tracking",
|
||||
href: "https://apps.apple.com/cn/app/grow-%E4%BD%A0%E7%9A%84%E5%81%A5%E5%BA%B7%E8%B4%B4%E5%BF%83%E5%A5%BD%E4%BC%99%E4%BC%B4/id1560604814",
|
||||
},
|
||||
{
|
||||
name: "Keep",
|
||||
description: "Fitness App",
|
||||
href: "https://www.gotokeep.com/",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const DEVICES = {
|
||||
computing: {
|
||||
title: "Computing Devices",
|
||||
sections: {
|
||||
devices: {
|
||||
title: "Devices",
|
||||
tools: [
|
||||
{
|
||||
name: "Mac Mini M2 Pro",
|
||||
description: "主力机 (32GB, 512GB)",
|
||||
iconPath: "apple",
|
||||
},
|
||||
{
|
||||
name: "MacBook Air M1",
|
||||
description: "移动办公 (16GB, 256GB)",
|
||||
iconPath: "apple",
|
||||
},
|
||||
{
|
||||
name: "机械师整机",
|
||||
description: "台式机 (i5, 32GB, 512GB)",
|
||||
iconPath: "windows",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
mobile: {
|
||||
title: "Mobile Devices",
|
||||
sections: {
|
||||
phone: {
|
||||
title: "Phone",
|
||||
tools: [
|
||||
{
|
||||
name: "iPhone 13",
|
||||
description: "手机 (256GB)",
|
||||
iconPath: "apple",
|
||||
},
|
||||
],
|
||||
},
|
||||
tablet: {
|
||||
title: "Tablet",
|
||||
tools: [
|
||||
{
|
||||
name: "iPad Mini 5",
|
||||
description: "平板",
|
||||
iconPath: "apple",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
wearables: {
|
||||
title: "Smart Wearables",
|
||||
sections: {
|
||||
watch: {
|
||||
title: "Smart Watch",
|
||||
tools: [
|
||||
{
|
||||
name: "Apple Watch S9",
|
||||
description: "智能手表",
|
||||
iconPath: "apple",
|
||||
},
|
||||
],
|
||||
},
|
||||
audio: {
|
||||
title: "Audio Devices",
|
||||
tools: [
|
||||
{
|
||||
name: "AirPods Pro 2",
|
||||
description: "无线耳机",
|
||||
iconPath: "apple",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
imaging: {
|
||||
title: "Imaging Equipment",
|
||||
sections: {
|
||||
cameras: {
|
||||
title: "Camera System",
|
||||
tools: [
|
||||
{
|
||||
name: "松下 GX9",
|
||||
description: "相机",
|
||||
iconPath: "round-photo",
|
||||
},
|
||||
{
|
||||
name: "Panasonic 14-140mm",
|
||||
description: "变焦镜头",
|
||||
iconPath: "tool",
|
||||
},
|
||||
{
|
||||
name: "Panasonic 25mm",
|
||||
description: "定焦镜头",
|
||||
iconPath: "tool",
|
||||
},
|
||||
{
|
||||
name: "Panasonic 100-300mm",
|
||||
description: "长焦镜头",
|
||||
iconPath: "tool",
|
||||
},
|
||||
{
|
||||
name: "DJI Action 5 Pro",
|
||||
description: "运动相机",
|
||||
iconPath: "round-photo",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
---
|
||||
|
||||
<div class="w-full">
|
||||
<div class="mt-5 flex w-full flex-col gap-y-10">
|
||||
<div>
|
||||
<h1 class="mb-1 text-2xl font-bold">Tools & Devices</h1>
|
||||
<p>Tools, software, and devices I use daily</p>
|
||||
</div>
|
||||
|
||||
<CategorySection title="Software Tools" categories={Object.values(SOFTWARE_TOOLS)} />
|
||||
<CategorySection title="Hardware Devices" categories={Object.values(DEVICES)} />
|
||||
</div>
|
||||
</div>
|
@ -3,7 +3,7 @@ title: 2023 bw汇报
|
||||
date: 2024-01-07
|
||||
author: KazooTTT
|
||||
tags:
|
||||
- '2023'
|
||||
- "2023"
|
||||
- bw
|
||||
- hanser
|
||||
published: true
|
||||
|
1
src/icons/apple.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M17.05 20.28c-.98.95-2.05.8-3.08.35c-1.09-.46-2.09-.48-3.24 0c-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8c1.18-.24 2.31-.93 3.57-.84c1.51.12 2.65.72 3.4 1.8c-3.12 1.87-2.38 5.98.48 7.13c-.57 1.5-1.31 2.99-2.54 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25c.29 2.58-2.34 4.5-3.74 4.25"/></svg>
|
After Width: | Height: | Size: 440 B |
1
src/icons/arc.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="37.41" height="32" viewBox="0 0 256 219"><path fill="#FFF" d="M123.632.012c13.836.398 26.332 8.52 32.32 21.089l23.761 49.984l.382-.966a58.846 58.846 0 0 0 2.315-7.64l.332-1.548c4.004-20.02 23.463-32.977 43.52-29.016a36.982 36.982 0 0 1 29.018 43.526c-5.337 26.652-19.095 51.387-38.829 70.983l-.625.607l8.33 17.514c9.668 20.33-.349 44.903-21.4 51.799l-.95.297l-.725.219a36.691 36.691 0 0 1-9.897 1.373a37.012 37.012 0 0 1-33.42-21.102l-6.43-13.518l-1.622.402c-8.692 2.054-17.508 3.192-26.328 3.367l-2.405.024c-8.488 0-17.116-.987-25.736-2.9l-1.7-.396l-6.177 12.987a36.972 36.972 0 0 1-20.713 18.852l-1.1.382a36.963 36.963 0 0 1-28.96-2.484c-17.56-9.334-24.256-31.186-15.688-49.235l7.67-16.129l-.67-.65C17.39 137.46 9.054 125.67 3.524 112.996l-.737-1.733l-.106-.281C-4.93 92.058 4.21 70.517 23.122 62.86c14.834-6.005 31.278-1.693 41.39 9.578l.19.218l24.446-51.422A36.858 36.858 0 0 1 121.535.01L122.57 0z"/><path fill="#1A007F" d="m87.118 170.045l21.896-46.068c-16.724-3.552-33.551-13.897-43.068-26.482L43.05 145.63c12.723 10.793 27.999 19.276 44.068 24.414"/><path fill="#4E000A" d="M178.495 96.115c-11 13.483-26.275 23.483-42.62 27.379l21.827 45.93c15.931-5.38 30.827-14.069 43.69-25.206z"/><path fill="#1A007F" d="M43.05 145.631L31.602 169.7c-5.828 12.241-1.449 27.31 10.551 33.689c12.724 6.758 28.379 1.483 34.517-11.38l10.448-21.964A130.635 130.635 0 0 1 43.05 145.63"/><path fill="#FF9396" d="M223.942 43.565a25.137 25.137 0 0 0-29.585 19.723c-2.414 12.07-8.069 23.31-15.862 32.862l22.862 48.137c21.103-18.31 36.688-43.24 42.275-71.137c2.724-13.655-6.104-26.896-19.69-29.585"/><path fill="#002DC8" d="M135.875 123.494c-4.896 1.172-9.896 1.793-14.896 1.793c-3.896 0-7.93-.448-11.965-1.31c-16.724-3.552-33.551-13.897-43.068-26.482c-2.38-3.138-4.31-6.414-5.655-9.759c-5.207-12.862-19.862-19.068-32.724-13.896C14.705 79.047 8.5 93.702 13.671 106.563c5.896 14.62 16.31 28.034 29.379 39.068a130.48 130.48 0 0 0 44.033 24.414c11.069 3.551 22.551 5.517 33.862 5.517c12.551 0 24.93-2.173 36.723-6.138z"/><path fill="#FF536A" d="m213.425 169.596l-12.068-25.378l-22.862-48.103l-.034.035s0-.035.034-.035l-33.24-69.93a25.144 25.144 0 0 0-22.69-14.344c-9.69 0-18.517 5.586-22.689 14.345L65.98 97.495c9.517 12.585 26.344 22.93 43.068 26.482l10.965-23.034c1.035-2.173 4.138-2.173 5.173 0l10.724 22.551h.069h-.07l21.828 45.93l10.724 22.551a25.103 25.103 0 0 0 22.723 14.345c2.242 0 4.483-.31 6.69-.931c15.138-4.173 22.31-21.586 15.551-35.793"/></svg>
|
After Width: | Height: | Size: 2.4 KiB |
5
src/icons/arrow-right.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.75 6.75L19.25 12L13.75 17.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19 12H4.75" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 391 B |
1
src/icons/canva.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><g fill="#00c4cc"><path d="M59.39.152c-.484.051-1.995.23-3.328.387c-5.374.613-11.468 2.227-16.816 4.48C19.891 13.106 5.324 30.849 1.305 51.2C.359 56.04.129 58.418.129 64c0 7.195.715 12.16 2.61 18.434c6.195 20.53 22.323 36.632 42.906 42.851c6.195 1.871 11.187 2.586 18.355 2.586c7.195 0 12.16-.715 18.434-2.61c20.53-6.195 36.632-22.323 42.851-42.906c1.871-6.195 2.586-11.187 2.586-18.355c0-3.047-.152-6.527-.332-7.809c-2.074-14.796-8.168-27.238-18.328-37.402C99.07 8.703 86.68 2.586 72.19.512c-1.996-.282-11.238-.54-12.8-.36m-20.863 40.32c1.36.41 1.996.794 2.918 1.715c1.793 1.82 2.203 2.817 2.203 5.555c0 2.051-.078 2.434-.691 3.508c-1.18 1.996-3.918 3.84-5.812 3.89c-1.333.028-1.278-.562.18-2.097c1.945-2.023 2.226-2.79 2.226-5.813c-.024-2.917-.383-3.914-1.739-4.734c-1.128-.691-2.355-.64-4.148.203c-4.66 2.23-9.703 9.653-11.672 17.258c-2.613 10.137 2.02 18.25 9.649 16.867c2.226-.41 6.425-2.558 8.246-4.25c1.508-1.379 1.508-1.406 1.66-3.12c.336-3.587 2.867-7.169 6.25-8.833c1.558-.77 1.945-.844 4.043-.844c1.996 0 2.457.102 3.43.637c3.097 1.77 2.457 5.89-.895 5.89c-1.945 0-2.945-1-1.535-1.534c1.383-.512.867-2.434-.742-2.868c-1.895-.488-4.047.793-5.403 3.25c-1.64 2.97-1.715 6.504-.156 8.114c1.512 1.613 3.406.336 4.867-3.329c.766-1.867 1.867-2.867 3.149-2.867c1.125 0 1.332.692.843 2.793c-.718 3.25-.23 4.094 1.793 3.098c.664-.309 1.766-1.023 2.43-1.535l1.254-1l.848-4.43c.922-4.965 1.277-5.633 3.172-5.988c1.82-.336 2.23.562 1.562 3.402l-.36 1.59l1.333-1.36c3.148-3.226 7.015-4.812 8.347-3.48c.715.715.637 1.613-.386 4.785c-.485 1.512-1.153 3.895-1.457 5.25c-.461 2.047-.489 2.535-.23 2.868c.82.972 3.327-.028 5.554-2.204l1.305-1.277l.156-2.844c.152-3.277.457-4.453 1.328-5.504c.82-.972 2.305-1.687 3.098-1.484c.793.207.793.973.078 3.227c-1 3.097-.895 10.238.129 10.238c.41 0 2.507-2.2 3.84-4.043l.996-1.36l-.793-.816c-1.383-1.46-1.715-2.406-1.715-4.789c0-1.738.129-2.379.562-3.227c.719-1.328 1.844-2.3 3.176-2.687c1.406-.434 3.148.281 3.863 1.562c.719 1.305.54 4.223-.383 6.223l-.664 1.457h.895c1.23 0 1.715-.305 3.918-2.379c1.152-1.101 2.484-2.05 3.48-2.511c3.918-1.84 8.528-.895 9.293 1.921c.64 2.254-.765 3.84-3.226 3.66c-1.766-.128-2.098-.59-1.074-1.456c1.843-1.54 0-3.508-2.637-2.793c-1.434.386-3.047 1.996-3.89 3.867c-1.692 3.738-.794 8.14 1.636 8.14c.973 0 2.691-1.921 3.355-3.789c.793-2.152 2.457-3.507 3.711-3.02c.692.255.743.946.309 3.122c-.488 2.383-.563 4.61-.18 5.633c.153.382.614 1.101 1.051 1.586c.816.921.844 1.254.152 1.691c-.332.23-.77.129-1.843-.46c-1.485-.77-2.766-2.153-3.227-3.458l-.281-.766l-1.024.766c-.59.41-1.511.871-2.047 1.023c-2.125.563-4.738-.894-5.964-3.351c-.489-.95-.641-3.738-.282-4.813c.204-.59.204-.59-.617-.18c-.433.231-1.355.485-2.07.563c-1.18.13-1.36.258-2.535 1.742c-1.664 2.07-4.61 4.864-5.813 5.454c-2.558 1.277-3.402.918-4.07-1.72l-.461-1.765l-1.102.973c-1.406 1.23-4.222 2.715-5.836 3.074c-1.535.332-3.175-.156-3.84-1.18c-.995-1.535-.663-4.785.922-9.164c1.176-3.25.333-3.3-2.636-.203c-2.203 2.328-3.149 3.992-3.762 6.578c-.64 2.688-1.41 3.66-3.148 4.07c-1.051.231-1.54-.41-1.332-1.816l.152-1.129l-.973.668c-1.383.946-3.125 1.817-4.328 2.149s-2.789-.024-3.172-.692c-.691-1.175-.691-1.175-1.765-.332c-2.332 1.895-5.66 1.356-7.348-1.152l-.54-.793l-1.687 1.562c-4.867 4.454-10.957 6.45-15.464 5.067c-5.735-1.738-8.907-6.656-8.856-13.746c.024-7.117 3.172-14.617 8.473-20.172c2.996-3.125 5.812-4.969 8.68-5.66c2.07-.512 3.328-.485 5.296.129zm0 0"/><path d="M90.418 58.676c-.563.562-.356 2.816.36 4.25c.359.742.742 1.332.87 1.332c.102 0 .332-.59.512-1.309c.64-2.66-.512-5.504-1.742-4.273m0 0"/></g></svg>
|
After Width: | Height: | Size: 3.5 KiB |
1
src/icons/category.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M6.5 11L12 2l5.5 9zm11 11q-1.875 0-3.187-1.312T13 17.5t1.313-3.187T17.5 13t3.188 1.313T22 17.5t-1.312 3.188T17.5 22M3 21.5v-8h8v8z"/></svg>
|
After Width: | Height: | Size: 251 B |
1
src/icons/chatgpt.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="31.51" height="32" viewBox="0 0 256 260"><path d="M239.184 106.203a64.716 64.716 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.716 64.716 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.665 64.665 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.767 64.767 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483m-97.56 136.338a48.397 48.397 0 0 1-31.105-11.255l1.535-.87l51.67-29.825a8.595 8.595 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601M37.158 197.93a48.345 48.345 0 0 1-5.781-32.589l1.534.921l51.722 29.826a8.339 8.339 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803M23.549 85.38a48.499 48.499 0 0 1 25.58-21.333v61.39a8.288 8.288 0 0 0 4.195 7.316l62.874 36.272l-21.845 12.636a.819.819 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405zm179.466 41.695l-63.08-36.63L161.73 77.86a.819.819 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.544 8.544 0 0 0-4.4-7.213m21.742-32.69l-1.535-.922l-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.716.716 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391zM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87l-51.67 29.825a8.595 8.595 0 0 0-4.246 7.367zm11.868-25.58L128.067 97.3l28.188 16.218v32.434l-28.086 16.218l-28.188-16.218z"/></svg>
|
After Width: | Height: | Size: 1.6 KiB |
20
src/icons/cursor.svg
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200">
|
||||
<path d="M0 0 C66 0 132 0 200 0 C200 66 200 132 200 200 C134 200 68 200 0 200 C0 134 0 68 0 0 Z " fill="#000000" transform="translate(0,0)"/>
|
||||
<path d="M0 0 C39.6 0 79.2 0 120 0 C118.60210955 3.49472611 117.28283805 6.44993488 115.453125 9.67578125 C114.94426758 10.57530518 114.43541016 11.4748291 113.91113281 12.40161133 C113.08887207 13.84250122 113.08887207 13.84250122 112.25 15.3125 C111.6837793 16.30983154 111.11755859 17.30716309 110.53417969 18.3347168 C104.50846191 28.91930069 98.36177244 39.43349239 92.19238281 49.93481445 C91.42257859 51.2681015 90.68851331 52.62297338 90 54 C90.721875 54.103125 91.44375 54.20625 92.1875 54.3125 C95.29467554 55.0720318 97.28420425 56.32412604 100 58 C101.74488378 58.96761737 103.49508214 59.92570233 105.25 60.875 C110.18394349 63.55285264 115.09783491 66.26447084 120 69 C117.60030318 71.65565787 115.03555811 73.27998553 111.93359375 75.046875 C110.90258545 75.63766846 109.87157715 76.22846191 108.80932617 76.8371582 C107.14027222 77.78401489 107.14027222 77.78401489 105.4375 78.75 C103.68936575 79.74881077 101.94123691 80.74763116 100.19343567 81.74702454 C98.98268849 82.43897522 97.77157244 83.13028085 96.56010437 83.82096863 C92.25195885 86.27808382 87.95845136 88.75937928 83.671875 91.25390625 C82.48904542 91.94113538 81.30618455 92.62831065 80.12329102 93.31542969 C77.86995957 94.6251896 75.61780084 95.93696962 73.36694336 97.25097656 C72.34221924 97.84491211 71.31749512 98.43884766 70.26171875 99.05078125 C69.36412842 99.57325439 68.46653809 100.09572754 67.54174805 100.6340332 C65.07603007 101.9591405 62.60431617 102.98255665 60 104 C60.04964905 103.17109253 60.0992981 102.34218506 60.15045166 101.48815918 C60.59685272 93.60609983 60.90308445 85.73288737 61.05958939 77.8395462 C61.14481397 73.78102921 61.28034792 69.73843602 61.53955078 65.68701172 C62.57694548 50.05067275 62.57694548 50.05067275 59 35 C52.47971003 27.65650377 43.64161192 24.10112572 34.66487122 20.55438232 C28.66751109 18.01072116 23.24255419 14.63847868 17.6875 11.25 C15.77684081 10.12662684 13.86442714 9.00622467 11.94921875 7.890625 C11.10020996 7.39369141 10.25120117 6.89675781 9.37646484 6.38476562 C6.26206772 4.57000699 3.13386426 2.78089942 0 1 C0 0.67 0 0.34 0 0 Z " fill="#DFDFDF" transform="translate(40,65)"/>
|
||||
<path d="M0 0 C3.62138201 1.41792161 6.96563988 3.00154162 10.359375 4.90625 C11.38450195 5.48036621 12.40962891 6.05448242 13.46582031 6.64599609 C14.54959961 7.25781738 15.63337891 7.86963867 16.75 8.5 C18.42594238 9.44286865 18.42594238 9.44286865 20.13574219 10.40478516 C38.78493832 20.92546339 38.78493832 20.92546339 44.6410675 24.631073 C47.09069089 26.10752463 47.09069089 26.10752463 50.20042419 27.43374634 C59.3102628 31.96329979 59.3102628 31.96329979 61.14596558 37.38388062 C61.5070069 41.60685166 61.4665891 45.64949863 61.26953125 49.875 C61.24524992 51.41048358 61.22676121 52.94606867 61.21379089 54.48168945 C61.1644081 58.50237617 61.03699477 62.51684719 60.89263916 66.53509521 C60.75906203 70.64363487 60.70015772 74.75319102 60.63476562 78.86328125 C60.49580229 86.91169107 60.27429812 94.95507025 60 103 C56.20869126 101.52122382 52.76306168 99.82498817 49.23046875 97.796875 C48.10503662 97.15250488 46.97960449 96.50813477 45.82006836 95.84423828 C44.60912799 95.14630501 43.39827373 94.44822231 42.1875 93.75 C40.94224084 93.03481557 39.69687829 92.31981113 38.45141602 91.60498047 C28.13066924 85.67519888 17.83534569 79.70098688 7.55859375 73.6953125 C6.34127563 72.9839917 6.34127563 72.9839917 5.09936523 72.25830078 C3.36919416 71.22128465 1.67837031 70.11891354 0 69 C-0.36076355 66.13998413 -0.36076355 66.13998413 -0.34057617 62.43896484 C-0.34101425 61.76567642 -0.34145233 61.092388 -0.34190369 60.3986969 C-0.33981056 58.16915062 -0.3164562 55.94035269 -0.29296875 53.7109375 C-0.28737463 52.16680309 -0.28310366 50.62266336 -0.28010559 49.07852173 C-0.26864226 45.01116273 -0.23916262 40.94412429 -0.20599365 36.87689209 C-0.17532462 32.72786801 -0.16160706 28.57879222 -0.14648438 24.4296875 C-0.11429405 16.2863111 -0.06308008 8.14319541 0 0 Z " fill="#484848" transform="translate(40,66)"/>
|
||||
<path d="M0 0 C0 22.44 0 44.88 0 68 C-3.88461437 66.44615425 -7.17185557 64.86235312 -10.76953125 62.796875 C-11.89496338 62.15258545 -13.02039551 61.5082959 -14.17993164 60.84448242 C-15.39091613 60.14654429 -16.60176941 59.44837848 -17.8125 58.75 C-19.05778374 58.03412609 -20.30314599 57.31838873 -21.54858398 56.6027832 C-24.74091491 54.76701394 -27.93039492 52.92634305 -31.11938477 51.08477783 C-32.94883303 50.02951417 -34.77976574 48.97688227 -36.61132812 47.92529297 C-39.93140986 46.01788263 -43.2476105 44.1039165 -46.5625 42.1875 C-47.57223877 41.60774414 -48.58197754 41.02798828 -49.62231445 40.43066406 C-50.55261475 39.89022461 -51.48291504 39.34978516 -52.44140625 38.79296875 C-53.25295166 38.3239917 -54.06449707 37.85501465 -54.90063477 37.37182617 C-56.88292754 36.07650078 -58.40482701 34.74004303 -60 33 C-59.51345947 32.74943848 -59.02691895 32.49887695 -58.52563477 32.24072266 C-46.78977957 26.17827432 -35.32750239 19.79049827 -24 13 C-21.02697883 11.21995836 -18.05272846 9.44208512 -15.07543945 7.66918945 C-13.09234051 6.48826456 -11.1116357 5.30330839 -9.13354492 4.11401367 C-8.23482666 3.57881104 -7.3361084 3.0436084 -6.41015625 2.4921875 C-5.60715088 2.01120605 -4.80414551 1.53022461 -3.97680664 1.03466797 C-2 0 -2 0 0 0 Z " fill="#8B8B8B" transform="translate(100,101)"/>
|
||||
<path d="M0 0 C0.495 1.98 0.495 1.98 1 4 C1.66 4 2.32 4 3 4 C2.505 2.515 2.505 2.515 2 1 C8.14112694 2.622572 13.22238458 5.89740608 18.625 9.125 C20.75186933 10.38145306 22.87950797 11.63660456 25.0078125 12.890625 C26.13219727 13.55384766 27.25658203 14.21707031 28.41503906 14.90039062 C34.74270325 18.61739532 41.12430032 22.24126334 47.5 25.875 C48.75343408 26.58997827 50.00685243 27.30498411 51.26025391 28.02001953 C54.17296484 29.68095973 57.08618532 31.34099713 60 33 C60 33.33 60 33.66 60 34 C21.39 34 -17.22 34 -57 34 C-54.17078488 31.17078488 -51.87736324 29.64216959 -48.42578125 27.69921875 C-47.29382324 27.05799072 -46.16186523 26.4167627 -44.99560547 25.75610352 C-44.39472107 25.41862091 -43.79383667 25.08113831 -43.17474365 24.73342896 C-40.01608031 22.95936602 -36.86610898 21.16997243 -33.71484375 19.3828125 C-33.07327332 19.01956375 -32.43170288 18.656315 -31.77069092 18.28205872 C-24.23549438 14.01083191 -16.76296788 9.63415826 -9.2902832 5.25463867 C-8.35659912 4.70783447 -7.42291504 4.16103027 -6.4609375 3.59765625 C-5.64415527 3.11820557 -4.82737305 2.63875488 -3.98583984 2.14477539 C-2.67872426 1.39126359 -1.3494684 0.6747342 0 0 Z " fill="#303030" transform="translate(99,31)"/>
|
||||
<path d="M0 0 C37.95 0 75.9 0 115 0 C112.17078488 2.82921512 109.87736324 4.35783041 106.42578125 6.30078125 C105.29398437 6.94200928 104.1621875 7.5832373 102.99609375 8.24389648 C101.76827628 8.93351487 100.54041063 9.62304749 99.3125 10.3125 C96.77603537 11.74508841 94.24144482 13.18097433 91.70703125 14.6171875 C91.06504791 14.98043625 90.42306458 15.343685 89.7616272 15.71794128 C85.46823099 18.15194037 81.20023905 20.62560146 76.94921875 23.1328125 C75.31569458 24.09513794 75.31569458 24.09513794 73.64916992 25.0769043 C71.58589067 26.295933 69.5247681 27.51862112 67.46606445 28.74536133 C66.53882568 29.29216553 65.61158691 29.83896973 64.65625 30.40234375 C63.84994141 30.88179443 63.04363281 31.36124512 62.21289062 31.85522461 C58.420094 33.81731898 58.420094 33.81731898 56 34 C53.35644531 32.86694336 53.35644531 32.86694336 50.328125 31.08984375 C49.19955078 30.4359668 48.07097656 29.78208984 46.90820312 29.10839844 C45.68858114 28.38513807 44.46918502 27.6614967 43.25 26.9375 C41.99996226 26.20630727 40.74930412 25.4761742 39.49804688 24.74707031 C36.92931611 23.24810929 34.36472762 21.74237875 31.80273438 20.23193359 C28.20171751 18.11254932 24.58040602 16.0311578 20.953125 13.95703125 C18.8019506 12.72158526 16.65090259 11.48591916 14.5 10.25 C13.4987207 9.67886475 12.49744141 9.10772949 11.46582031 8.51928711 C10.53866211 7.98182861 9.61150391 7.44437012 8.65625 6.890625 C7.84994141 6.42688477 7.04363281 5.96314453 6.21289062 5.48535156 C4 4 4 4 0 0 Z " fill="#FEFEFE" transform="translate(43,66)"/>
|
||||
<path d="M0 0 C0.33 0 0.66 0 1 0 C1 22.11 1 44.22 1 67 C-3.74248123 65.41917292 -6.46245807 64.24522061 -10.4375 61.5625 C-14.56294719 58.8625122 -18.42351731 56.79469909 -23 55 C-25.0072458 54.01462479 -27.00980253 53.01936943 -29 52 C-27.581156 47.09442235 -25.38601077 42.95951721 -22.75 38.625 C-21.85734258 37.13324929 -20.96546358 35.64103259 -20.07421875 34.1484375 C-19.58743652 33.33600586 -19.1006543 32.52357422 -18.59912109 31.68652344 C-15.54941479 26.56301684 -12.56072728 21.4037391 -9.5625 16.25 C-8.37668169 14.2133206 -7.19050497 12.17684982 -6.00390625 10.140625 C-5.49674072 9.26954102 -4.9895752 8.39845703 -4.46704102 7.50097656 C-3.74085571 6.26299316 -3.74085571 6.26299316 -3 5 C-2.46366943 4.06897461 -1.92733887 3.13794922 -1.37475586 2.17871094 C-0.92108643 1.45973633 -0.46741699 0.74076172 0 0 Z " fill="#292929" transform="translate(159,67)"/>
|
||||
<path d="M0 0 C4.48665931 0.40787812 7.22377581 1.65109864 11 4 C12.74488378 4.96761737 14.49508214 5.92570233 16.25 6.875 C21.18394349 9.55285264 26.09783491 12.26447084 31 15 C28.56968847 17.70057066 25.94322326 19.32877168 22.79296875 21.1171875 C21.74093262 21.71845459 20.68889648 22.31972168 19.60498047 22.93920898 C18.47721191 23.57802002 17.34944336 24.21683105 16.1875 24.875 C13.83852324 26.21514201 11.49155863 27.55879139 9.14453125 28.90234375 C8.55047485 29.24183548 7.95641846 29.58132721 7.34436035 29.93110657 C1.32152656 33.37731632 -4.65593567 36.8990629 -10.625 40.4375 C-11.57221924 40.99872559 -12.51943848 41.55995117 -13.49536133 42.13818359 C-15.66426974 43.42429041 -17.83254417 44.71144718 -20 46 C-20.99 45.34 -21.98 44.68 -23 44 C-23.66 43.67 -24.32 43.34 -25 43 C-21.796875 37.39453125 -18.59375 31.7890625 -15.390625 26.18359375 C-15.04290039 25.57501526 -14.69517578 24.96643677 -14.33691406 24.3394165 C-13.63204618 23.10601996 -12.92696738 21.87274393 -12.22167969 20.6395874 C-10.38237607 17.4230188 -8.54724692 14.20417113 -6.71875 10.98144531 C-6.35164917 10.33563507 -5.98454834 9.68982483 -5.60632324 9.02444458 C-4.90293724 7.78681027 -4.20081839 6.54845473 -3.50012207 5.30929565 C-1.11240206 1.11240206 -1.11240206 1.11240206 0 0 Z " fill="#BFBFBF" transform="translate(129,119)"/>
|
||||
<path d="M0 0 C1.3662889 2.73257779 0.61455804 3.81845307 -0.3125 6.6875 C-0.73595703 8.02748047 -0.73595703 8.02748047 -1.16796875 9.39453125 C-1.44253906 10.25433594 -1.71710938 11.11414063 -2 12 C-3.07815373 15.64649447 -4.11244538 19.30185577 -5 23 C-3.35 21.02 -1.7 19.04 0 17 C1.79505494 22.38516481 1 28.32353788 1 34 C-18.14 34 -37.28 34 -57 34 C-54.17078488 31.17078488 -51.87736324 29.64216959 -48.42578125 27.69921875 C-47.29382324 27.05799072 -46.16186523 26.4167627 -44.99560547 25.75610352 C-44.39472107 25.41862091 -43.79383667 25.08113831 -43.17474365 24.73342896 C-40.01608031 22.95936602 -36.86610898 21.16997243 -33.71484375 19.3828125 C-33.07327332 19.01956375 -32.43170288 18.656315 -31.77069092 18.28205872 C-24.23549438 14.01083191 -16.76296788 9.63415826 -9.2902832 5.25463867 C-8.35659912 4.70783447 -7.42291504 4.16103027 -6.4609375 3.59765625 C-5.64415527 3.11820557 -4.82737305 2.63875488 -3.98583984 2.14477539 C-2.67872426 1.39126359 -1.3494684 0.6747342 0 0 Z " fill="#7C7C7C" transform="translate(99,31)"/>
|
||||
<path d="M0 0 C5.85328136 -0.30806744 9.96791038 -0.1835669 15 3 C15.9375 6.6875 15.9375 6.6875 16 10 C10.42993177 7.31403824 4.98947109 4.67645238 0 1 C0 0.67 0 0.34 0 0 Z " fill="#ABABAB" transform="translate(42,135)"/>
|
||||
<path d="M0 0 C0.33 0 0.66 0 1 0 C2.0735426 13.08699552 2.0735426 13.08699552 -0.55859375 17.77734375 C-2.27860641 19.69116066 -3.99065622 21.3956005 -6 23 C-6.33 22.34 -6.66 21.68 -7 21 C-6.38399858 19.64251539 -5.70845842 18.31160546 -5 17 C-2.91054047 11.44203765 -1.36454945 5.77309383 0 0 Z " fill="#545454" transform="translate(99,33)"/>
|
||||
<path d="M0 0 C4.39383706 1.59623144 8.28995807 3.78024333 12.3125 6.125 C12.95767578 6.49753906 13.60285156 6.87007812 14.26757812 7.25390625 C15.84675945 8.16632213 17.42361864 9.08275509 19 10 C18.34 10.66 17.68 11.32 17 12 C16.360625 11.690625 15.72125 11.38125 15.0625 11.0625 C11.07950896 9.68064597 7.18349151 9.3463457 3 9 C3.33 8.01 3.66 7.02 4 6 C2.10770734 3.36375566 2.10770734 3.36375566 0 1 C0 0.67 0 0.34 0 0 Z " fill="#9E9E9E" transform="translate(141,124)"/>
|
||||
<path d="M0 0 C4.30764815 1.56641751 8.06171464 3.68336156 12 6 C12 6.33 12 6.66 12 7 C8.8125 7.375 8.8125 7.375 5 7 C2.80738095 5.19271747 1.61442456 3.39717586 0 1 C0 0.67 0 0.34 0 0 Z " fill="#878787" transform="translate(141,124)"/>
|
||||
<path d="M0 0 C6.47556391 0.61090226 6.47556391 0.61090226 8.9375 2.4375 C10 4 10 4 10 6 C5.56909529 4.79157144 3.01887244 3.48331435 0 0 Z " fill="#B5B5B5" transform="translate(44,136)"/>
|
||||
<path d="M0 0 C1.58294782 -0.05437607 3.16644256 -0.09296271 4.75 -0.125 C5.63171875 -0.14820313 6.5134375 -0.17140625 7.421875 -0.1953125 C10.1135858 0.00860498 11.69904974 0.63448279 14 2 C11.42121756 3.28939122 9.85485837 2.96551673 7 2.6875 C5.6696875 2.56955078 5.6696875 2.56955078 4.3125 2.44921875 C2 2 2 2 0 0 Z " fill="#989898" transform="translate(50,135)"/>
|
||||
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C1.67 1.99 1.34 2.98 1 4 C-0.65 4.33 -2.3 4.66 -4 5 C-2.25 1.125 -2.25 1.125 0 0 Z " fill="#D4D4D4" transform="translate(106,162)"/>
|
||||
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C0.29374499 3.78388975 -0.47626739 4.85384206 -3.6875 5.75 C-4.8321875 5.87375 -4.8321875 5.87375 -6 6 C-6.33 5.34 -6.66 4.68 -7 4 C-1.7265625 0.77734375 -1.7265625 0.77734375 0 0 Z " fill="#797979" transform="translate(47,130)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 14 KiB |
1
src/icons/figma.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="21.34" height="32" viewBox="0 0 256 384"><path fill="#0ACF83" d="M64 384c35.328 0 64-28.672 64-64v-64H64c-35.328 0-64 28.672-64 64s28.672 64 64 64"/><path fill="#A259FF" d="M0 192c0-35.328 28.672-64 64-64h64v128H64c-35.328 0-64-28.672-64-64"/><path fill="#F24E1E" d="M0 64C0 28.672 28.672 0 64 0h64v128H64C28.672 128 0 99.328 0 64"/><path fill="#FF7262" d="M128 0h64c35.328 0 64 28.672 64 64s-28.672 64-64 64h-64z"/><path fill="#1ABCFE" d="M256 192c0 35.328-28.672 64-64 64s-64-28.672-64-64s28.672-64 64-64s64 28.672 64 64"/></svg>
|
After Width: | Height: | Size: 578 B |
1
src/icons/github.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2"/></svg>
|
After Width: | Height: | Size: 679 B |
1
src/icons/link.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7a5 5 0 0 0-5 5a5 5 0 0 0 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1M8 13h8v-2H8zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4a5 5 0 0 0 5-5a5 5 0 0 0-5-5"/></svg>
|
After Width: | Height: | Size: 324 B |
4
src/icons/mail.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 8L10.89 13.26C11.2187 13.4793 11.6049 13.5963 12 13.5963C12.3951 13.5963 12.7813 13.4793 13.11 13.26L21 8M5 19H19C19.5304 19 20.0391 18.7893 20.4142 18.4142C20.7893 18.0391 21 17.5304 21 17V7C21 6.46957 20.7893 5.96086 20.4142 5.58579C20.0391 5.21071 19.5304 5 19 5H5C4.46957 5 3.96086 5.21071 3.58579 5.58579C3.21071 5.96086 3 6.46957 3 7V17C3 17.5304 3.21071 18.0391 3.58579 18.4142C3.96086 18.7893 4.46957 19 5 19Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 663 B |
1
src/icons/notion.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 15 15"><path fill="currentColor" d="M3.258 3.117c.42.341.577.315 1.366.262l7.433-.446c.158 0 .027-.157-.026-.183l-1.235-.893c-.236-.184-.551-.394-1.155-.341l-7.198.525c-.262.026-.315.157-.21.262zm.446 1.732v7.821c0 .42.21.578.683.552l8.17-.473c.472-.026.525-.315.525-.656V4.324c0-.34-.131-.525-.42-.499l-8.538.499c-.315.026-.42.184-.42.525m8.065.42c.052.236 0 .472-.237.499l-.394.078v5.775c-.341.183-.657.288-.92.288c-.42 0-.525-.131-.84-.525L6.803 7.342v3.911l.815.184s0 .472-.657.472l-1.812.105c-.053-.105 0-.367.184-.42l.472-.13V6.292L5.15 6.24c-.053-.236.078-.577.446-.604l1.944-.13L10.22 9.6V5.978l-.683-.079c-.053-.289.157-.499.42-.525zm-9.93-3.937L9.326.781c.919-.08 1.156-.026 1.733.394l2.39 1.68c.395.288.526.367.526.682v9.212c0 .578-.21.92-.946.971l-8.694.525c-.552.027-.815-.052-1.104-.42l-1.76-2.283c-.315-.42-.446-.735-.446-1.103V2.25c0-.472.21-.866.814-.918"/></svg>
|
After Width: | Height: | Size: 956 B |
1
src/icons/obsidian.svg
Normal file
After Width: | Height: | Size: 6.0 KiB |
1
src/icons/progress.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20.777a9 9 0 0 1-2.48-.969M14 3.223a9.003 9.003 0 0 1 0 17.554m-9.421-3.684a9 9 0 0 1-1.227-2.592M3.124 10.5c.16-.95.468-1.85.9-2.675l.169-.305m2.714-2.941A9 9 0 0 1 10 3.223"/></svg>
|
After Width: | Height: | Size: 377 B |
1
src/icons/round-photo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2M8.9 13.98l2.1 2.53l3.1-3.99c.2-.26.6-.26.8.01l3.51 4.68a.5.5 0 0 1-.4.8H6.02c-.42 0-.65-.48-.39-.81L8.12 14c.19-.26.57-.27.78-.02"/></svg>
|
After Width: | Height: | Size: 327 B |
5
src/icons/rss.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 11a9 9 0 0 1 9 9"/>
|
||||
<path d="M4 4a16 16 0 0 1 16 16"/>
|
||||
<circle cx="5" cy="19" r="1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 293 B |
1
src/icons/tiktok.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M16.6 5.82s.51.5 0 0A4.278 4.278 0 0 1 15.54 3h-3.09v12.4a2.592 2.592 0 0 1-2.59 2.5c-1.42 0-2.6-1.16-2.6-2.6c0-1.72 1.66-3.01 3.37-2.48V9.66c-3.45-.46-6.47 2.22-6.47 5.64c0 3.33 2.76 5.7 5.69 5.7c3.14 0 5.69-2.55 5.69-5.7V9.01a7.35 7.35 0 0 0 4.3 1.38V7.3s-1.88.09-3.24-1.48"/></svg>
|
After Width: | Height: | Size: 396 B |
1
src/icons/tool.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 7.86c0-.43-.056-.849-.161-1.246c-.092-.349-.522-.432-.776-.177L18.34 8.16a1.767 1.767 0 1 1-2.5-2.5l1.723-1.722c.255-.255.172-.685-.177-.777a4.86 4.86 0 0 0-5.828 6.326c.071.2.031.424-.118.573L3.3 18.2c-.4.4-.4 1.049 0 1.448L4.352 20.7c.4.4 1.047.4 1.447 0l8.14-8.14c.15-.15.374-.19.573-.119A4.86 4.86 0 0 0 21 7.86"/></svg>
|
After Width: | Height: | Size: 520 B |
1
src/icons/twitter.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M22.46 6c-.77.35-1.6.58-2.46.69c.88-.53 1.56-1.37 1.88-2.38c-.83.5-1.75.85-2.72 1.05C18.37 4.5 17.26 4 16 4c-2.35 0-4.27 1.92-4.27 4.29c0 .34.04.67.11.98C8.28 9.09 5.11 7.38 3 4.79c-.37.63-.58 1.37-.58 2.15c0 1.49.75 2.81 1.91 3.56c-.71 0-1.37-.2-1.95-.5v.03c0 2.08 1.48 3.82 3.44 4.21a4.2 4.2 0 0 1-1.93.07a4.28 4.28 0 0 0 4 2.98a8.52 8.52 0 0 1-5.33 1.84q-.51 0-1.02-.06C3.44 20.29 5.7 21 8.12 21C16 21 20.33 14.46 20.33 8.79c0-.19 0-.37-.01-.56c.84-.6 1.56-1.36 2.14-2.23"/></svg>
|
After Width: | Height: | Size: 595 B |
1
src/icons/vscode.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32.26" height="32" viewBox="0 0 256 254"><defs><linearGradient id="logosVisualStudioCode0" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#FFF" stop-opacity="0"/></linearGradient><path id="logosVisualStudioCode1" d="M180.828 252.605a15.872 15.872 0 0 0 12.65-.486l52.501-25.262a15.94 15.94 0 0 0 9.025-14.364V41.197a15.939 15.939 0 0 0-9.025-14.363l-52.5-25.263a15.877 15.877 0 0 0-18.115 3.084L74.857 96.35l-43.78-33.232a10.614 10.614 0 0 0-13.56.603L3.476 76.494c-4.63 4.211-4.635 11.495-.012 15.713l37.967 34.638l-37.967 34.637c-4.623 4.219-4.618 11.502.012 15.714l14.041 12.772a10.614 10.614 0 0 0 13.56.604l43.78-33.233l100.507 91.695a15.853 15.853 0 0 0 5.464 3.571m10.464-183.649l-76.262 57.889l76.262 57.888z"/></defs><mask id="logosVisualStudioCode2" fill="#fff"><use href="#logosVisualStudioCode1"/></mask><path fill="#0065A9" d="M246.135 26.873L193.593 1.575a15.885 15.885 0 0 0-18.123 3.08L3.466 161.482c-4.626 4.219-4.62 11.502.012 15.714l14.05 12.772a10.625 10.625 0 0 0 13.569.604L238.229 33.436c6.949-5.271 16.93-.315 16.93 8.407v-.61a15.938 15.938 0 0 0-9.024-14.36" mask="url(#logosVisualStudioCode2)"/><path fill="#007ACC" d="m246.135 226.816l-52.542 25.298a15.887 15.887 0 0 1-18.123-3.08L3.466 92.207c-4.626-4.218-4.62-11.502.012-15.713l14.05-12.773a10.625 10.625 0 0 1 13.569-.603l207.132 157.135c6.949 5.271 16.93.315 16.93-8.408v.611a15.939 15.939 0 0 1-9.024 14.36" mask="url(#logosVisualStudioCode2)"/><path fill="#1F9CF0" d="M193.428 252.134a15.892 15.892 0 0 1-18.125-3.083c5.881 5.88 15.938 1.715 15.938-6.603V11.273c0-8.318-10.057-12.483-15.938-6.602a15.892 15.892 0 0 1 18.125-3.084l52.533 25.263a15.937 15.937 0 0 1 9.03 14.363V212.51c0 6.125-3.51 11.709-9.03 14.363z" mask="url(#logosVisualStudioCode2)"/><path fill="url(#logosVisualStudioCode0)" fill-opacity=".25" d="M180.828 252.605a15.874 15.874 0 0 0 12.65-.486l52.5-25.263a15.938 15.938 0 0 0 9.026-14.363V41.197a15.939 15.939 0 0 0-9.025-14.363L193.477 1.57a15.877 15.877 0 0 0-18.114 3.084L74.857 96.35l-43.78-33.232a10.614 10.614 0 0 0-13.56.603L3.476 76.494c-4.63 4.211-4.635 11.495-.012 15.713l37.967 34.638l-37.967 34.637c-4.623 4.219-4.618 11.502.012 15.714l14.041 12.772a10.614 10.614 0 0 0 13.56.604l43.78-33.233l100.506 91.695a15.857 15.857 0 0 0 5.465 3.571m10.464-183.65l-76.262 57.89l76.262 57.888z" mask="url(#logosVisualStudioCode2)"/></svg>
|
After Width: | Height: | Size: 2.4 KiB |
1
src/icons/windows.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M21 13v7.434a1.5 1.5 0 0 1-1.553 1.499l-.133-.011L12 21.008V13zm-11 0v7.758l-5.248-.656A2 2 0 0 1 3 18.117V13zm9.314-10.922a1.5 1.5 0 0 1 1.68 1.355l.006.133V11h-9V2.992zM10 3.242V11H3V5.883a2 2 0 0 1 1.752-1.985z"/></g></svg>
|
After Width: | Height: | Size: 868 B |
@ -1,8 +1,8 @@
|
||||
---
|
||||
import PageLayout from "@/layouts/Base.astro";
|
||||
|
||||
import Tools from "@/components/tools/index.astro";
|
||||
const meta = {
|
||||
description: "I'm a starter theme for Astro.build",
|
||||
description: "introduction for KazooTTT",
|
||||
title: "About",
|
||||
};
|
||||
---
|
||||
@ -10,4 +10,14 @@ const meta = {
|
||||
<PageLayout meta={meta}>
|
||||
<h1 class="title mb-6">About</h1>
|
||||
<div class="prose prose-sm prose-cactus max-w-none">TODO ...</div>
|
||||
<div class="mt-2">
|
||||
<a href="https://wakatime.com/@d3dc2570-e4bf-4469-b0c2-127b495e8b91"
|
||||
><img
|
||||
src="https://wakatime.com/badge/user/d3dc2570-e4bf-4469-b0c2-127b495e8b91.svg"
|
||||
alt="Total time coded since Nov 4 2017"
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
|
||||
<Tools />
|
||||
</PageLayout>
|
||||
|
6
src/utils/tailwind.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|