feat: Initial commit

This commit is contained in:
houston[bot] authored and deytenit committed 2025-05-12 20:27:28 +03:00
commit c740630d0d
28 files changed
+11034

No files matched your search

@@ -0,0 +1,91 @@
name: origin/deploy-production
on:
release:
types: [published]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
env:
BUILD_PATH: "."
jobs:
setup:
name: Setup stage
runs-on: ubuntu-latest
if: ${{ ! github.event.release.prerelease }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build:
name: Setup stage
runs-on: ubuntu-latest
needs: setup
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Stage
run: pnpm build
- name: Upload artifacts
uses: actions/upload-pages-artifact@v3
with:
path: ./dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
name: Deploy stage
steps:
- name: Deploy in production
id: deployment
uses: actions/deploy-pages@v4
@@ -0,0 +1,71 @@
name: origin/release-dispatch
on:
workflow_run:
workflows: ["origin/required"]
branches: [main, next]
types:
- completed
concurrency:
group: ci-${{ github.ref_name }}
cancel-in-progress: true
jobs:
setup:
name: Setup stage
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
release:
runs-on: ubuntu-latest
needs: setup
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Stage
run: pnpm build
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pnpm exec semantic-release
+97
View File
@@ -0,0 +1,97 @@
name: origin/required
on:
push:
branches: [ main, next ]
concurrency:
group: ci-${{ github.ref_name }}
cancel-in-progress: true
jobs:
setup:
name: Setup stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
lint:
name: Lint
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run ESLint (CI mode)
run: pnpm run lint --max-warnings=0
type-check:
name: Typecheck
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run TypeScript compiler
run: pnpm exec tsc --noEmit
build:
name: Build stage
needs: type-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run Astro build
run: pnpm exec astro build
+100
View File
@@ -0,0 +1,100 @@
name: pr/required
on:
pull_request:
branches: [ main, next ]
paths-ignore:
- '**/*.md'
- '.github/ISSUE_TEMPLATE/**'
concurrency:
group: ci-${{ github.ref_name }}
cancel-in-progress: true
jobs:
setup:
name: Setup stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
lint:
name: Lint
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run ESLint (CI mode)
run: pnpm run lint --max-warnings=0
type-check:
name: Typecheck
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run TypeScript compiler
run: pnpm exec tsc --noEmit
build:
name: Build stage
needs: type-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run Astro build
run: pnpm exec astro build
+24
View File
@@ -0,0 +1,24 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/
View File
Whitespace-only changes.
+9
View File
@@ -0,0 +1,9 @@
{
"printWidth": 100,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"plugins": ["prettier-plugin-astro", "prettier-plugin-react", "prettier-plugin-tailwindcss"]
}
+68
View File
@@ -0,0 +1,68 @@
# Astro Starter Kit: Blog
```sh
pnpm create astro@latest -- --template blog
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/blog)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/blog)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/blog/devcontainer.json)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
![blog](https://github.com/withastro/astro/assets/2244813/ff10799f-a816-4703-b967-c78997e8323d)
Features:
- ✅ Minimal styling (make it your own!)
- ✅ 100/100 Lighthouse performance
- ✅ SEO-friendly with canonical URLs and OpenGraph data
- ✅ Sitemap support
- ✅ RSS Feed support
- ✅ Markdown & MDX support
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
├── public/
├── src/
│   ├── components/
│   ├── content/
│   ├── layouts/
│   └── pages/
├── astro.config.mjs
├── README.md
├── package.json
└── tsconfig.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :--------------------- | :----------------------------------------------- |
| `pnpm install` | Installs dependencies |
| `pnpm dev` | Starts local dev server at `localhost:4321` |
| `pnpm build` | Build your production site to `./dist/` |
| `pnpm preview` | Preview your build locally, before deploying |
| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` |
| `pnpm astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
## Credit
This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/).
+20
View File
@@ -0,0 +1,20 @@
// @ts-check
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import tailwindcss from '@tailwindcss/vite';
import react from '@astrojs/react';
// https://astro.build/config
export default defineConfig({
site: 'https://www.ermnvldmr.com',
output: 'static',
srcDir: './src/www',
integrations: [mdx(), sitemap(), react()],
vite: {
plugins: [tailwindcss()],
},
});
+21
View File
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/global.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@shadcn/components",
"utils": "@shadcn/lib/utils",
"ui": "@shadcn/components/ui",
"lib": "@shadcn/lib",
"hooks": "@shadcn/hooks"
},
"iconLibrary": "lucide"
}
+67
View File
@@ -0,0 +1,67 @@
// @ts-check
import eslint from '@eslint/js';
import eslintPluginAstro from 'eslint-plugin-astro';
import globals from 'globals';
import tseslint from 'typescript-eslint';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import prettier from 'eslint-config-prettier/flat';
export default tseslint.config(
{
ignores: ['**/dist', '**/node_modules', '**/.astro', '**/.github', '**/.changeset'],
},
// JavaScript
eslint.configs.recommended,
// TypeScript
...tseslint.configs.recommended,
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
},
},
// Allow triple-slash references in `*.d.ts` files.
{
files: ['**/*.d.ts'],
rules: {
'@typescript-eslint/triple-slash-reference': 'off',
},
},
// Astro
...eslintPluginAstro.configs.recommended,
// Set globals for Node scripts.
{
files: ['scripts/**'],
languageOptions: {
globals: globals.node,
},
},
// React
{
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
...react.configs.flat.recommended,
languageOptions: {
...react.configs.flat.recommended.languageOptions,
globals: {
...globals.serviceworker,
...globals.browser,
},
},
},
{
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
plugins: { 'react-hooks': reactHooks },
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
},
// Prettier
prettier
);
+70
View File
@@ -0,0 +1,70 @@
{
"name": "www.ermnvldmr.com",
"version": "0.0.1",
"description": "Thoughts and creations of mine!",
"author": "Vladimir Eremin <personal@ermnvldmr.com> (https://ermnvldmr.com)",
"license": "MIT",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/deytenit/www.ermnvldmr.com.git"
},
"bugs": {
"url": "https://github.com/deytenit/www.ermnvldmr.com/issues"
},
"homepage": "https://github.com/deytenit/www.ermnvldmr.com#readme",
"files": [
"src/",
"public/"
],
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"lint": "eslint --fix .",
"format": "prettier --write .",
"typecheck": "tsc --noEmit",
"check": "npm run lint && npm run typecheck",
"clean": "rm -rf dist .turbo node_modules",
"commit": "git-cz"
},
"dependencies": {
"@astrojs/mdx": "^4.2.5",
"@astrojs/react": "^4.2.5",
"@astrojs/rss": "^4.0.11",
"@astrojs/sitemap": "^3.3.1",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-navigation-menu": "^1.2.10",
"@radix-ui/react-slot": "^1.2.0",
"@tailwindcss/vite": "^4.1.4",
"astro": "^5.7.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.503.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.4"
},
"devDependencies": {
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"eslint": "^9.25.1",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-astro": "^1.3.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"git-cz": "^4.9.0",
"globals": "^16.0.0",
"prettier": "^3.5.3",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-react": "0.0.1-alpha.1",
"prettier-plugin-tailwindcss": "^0.6.11",
"semantic-release": "^24.2.3",
"tw-animate-css": "^1.2.8",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.1"
}
}
+9267
View File
File diff suppressed because it is too large. Load diff
+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

+4
View File
@@ -0,0 +1,4 @@
export default {
branches: ["main", { name: "next", prerelease: true }],
plugins: ['@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator', '@semantic-release/github']
};
+31
View File
@@ -0,0 +1,31 @@
import React, { memo } from 'react';
import NavigationMenu from './components/NavigationMenu/NavigationMenu';
import Breadcrumbs from './components/Breadcrumbs/Breadcrumbs';
import type { NavItem, Breadcrumb } from './types';
export type PageHeadProps = {
/** The main title displayed in the header */
title: string;
/** Array of breadcrumbs, displayed above the title */
breadcrumbs?: Breadcrumb[];
/** Navigation items displayed on the right */
navItems?: NavItem[];
};
export const PageHead = memo(function PageHead({ title, breadcrumbs, navItems }: PageHeadProps) {
return (
<div className="frosted-glass flex items-center justify-between space-x-4 p-4">
<div className="flex flex-1 flex-col gap-1">
{breadcrumbs && breadcrumbs.length > 0 && <Breadcrumbs items={breadcrumbs} />}
<h1 className="text-foreground font-serif text-2xl font-bold">{title}</h1>
</div>
<div className="flex items-center space-x-2">
{navItems && navItems.length > 0 && <NavigationMenu items={navItems} />}
</div>
</div>
);
});
export default PageHead;
@@ -0,0 +1,94 @@
import React from 'react';
import { memo, Fragment } from 'react';
import {
Breadcrumb as BreadcrumbBase,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
} from '@shadcn/components/ui/breadcrumb';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from '@shadcn/components/ui/dropdown-menu';
import type { Breadcrumb } from '../../types';
const MAX_VISIBLE_BREADCRUMBS = 4;
export type BreadcrumbsProps = {
items: Breadcrumb[];
};
const Breadcrumbs = memo(function Breadcrumbs({ items }: BreadcrumbsProps) {
const totalItems = items.length;
const hasOverflownBreadcrumbs = totalItems > MAX_VISIBLE_BREADCRUMBS;
return (
<BreadcrumbBase className="text-sm">
<BreadcrumbList>
{hasOverflownBreadcrumbs ? (
<>
{/* First crumb */}
<BreadcrumbItem>
<BreadcrumbLink href={items[0].href}>{items[0].label}</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
{/* Ellipsis dropdown for middle crumbs */}
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1">
<BreadcrumbEllipsis className="h-4 w-4" />
<span className="sr-only">More...</span>
</DropdownMenuTrigger>
<DropdownMenuContent>
{items.slice(1, totalItems - 2).map((item, i) => (
<DropdownMenuItem key={i}>
<a href={item.href}>{item.label}</a>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</BreadcrumbItem>
<BreadcrumbSeparator />
{/* Penultimate crumb */}
<BreadcrumbItem>
<BreadcrumbLink href={items[totalItems - 2].href}>
{items[totalItems - 2].label}
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
{/* Final “page” crumb */}
<BreadcrumbItem>
<BreadcrumbPage>{items[totalItems - 1].label}</BreadcrumbPage>
</BreadcrumbItem>
</>
) : (
// No overflow: just render them all
items.map((item, idx) => (
<Fragment key={idx}>
<BreadcrumbItem>
{idx === totalItems - 1 ? (
<BreadcrumbPage>{item.label}</BreadcrumbPage>
) : (
<BreadcrumbLink href={item.href}>{item.label}</BreadcrumbLink>
)}
</BreadcrumbItem>
{idx < totalItems - 1 && <BreadcrumbSeparator />}
</Fragment>
))
)}
</BreadcrumbList>
</BreadcrumbBase>
);
});
export default Breadcrumbs;
@@ -0,0 +1,63 @@
import React, { memo } from 'react';
import {
NavigationMenu as NavigationMenuBase,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuLink,
navigationMenuTriggerStyle,
} from '@shadcn/components/ui/navigation-menu';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from '@shadcn/components/ui/dropdown-menu';
import { HiOutlineMenu } from 'react-icons/hi';
import type { NavItem } from '../../types';
export type NavigationMenuProps = {
items: NavItem[];
};
const NavigationMenu = memo(function NavigationMenu({ items }: NavigationMenuProps) {
return (
<>
<div className="hidden items-center md:block">
<NavigationMenuBase>
<NavigationMenuList>
{items.map((item, idx) => (
<NavigationMenuItem key={idx}>
<a href={item.href}>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
{item.label}
</NavigationMenuLink>
</a>
</NavigationMenuItem>
))}
</NavigationMenuList>
</NavigationMenuBase>
</div>
<div className="flex items-center md:hidden">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button aria-label="Open menu">
<HiOutlineMenu className="h-6 w-6" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{items.map((item, i) => (
<DropdownMenuItem key={i}>
<a href={item.href}>{item.label}</a>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
</>
);
});
export default NavigationMenu;
+9
View File
@@ -0,0 +1,9 @@
export type NavItem = {
label: string;
href: string;
};
export type Breadcrumb = {
label: string;
href: string;
};
+315
View File
@@ -0,0 +1,315 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:where(.dark, .dark *));
/*
@layer base defaults inspired by shadcn UI Typography
Provides opinionated, utility-driven defaults for HTML elements.
*/
@layer base {
/* Headings */
h1 {
@apply scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl;
}
h2 {
@apply mt-10 scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0;
}
h3,
h4,
h5,
h6 {
@apply mt-8 scroll-m-20 text-2xl font-semibold tracking-tight;
}
/* Paragraphs */
p {
@apply leading-7 [&:not(:first-child)]:mt-6;
}
/* Links */
a {
@apply text-ring font-medium underline underline-offset-2;
}
/* Blockquotes */
blockquote {
@apply mt-6 border-l-2 pl-6 italic;
}
/* Code */
code {
@apply bg-muted rounded px-1 font-mono text-sm;
}
pre {
@apply bg-muted overflow-auto rounded-lg p-4 font-mono text-sm;
}
/* Images and Media */
img,
video {
@apply max-w-full rounded-lg;
}
/* Horizontal rules */
hr {
@apply border-border my-6 border-t;
}
}
@theme {
--font-sans: 'Lato', sans-serif;
--font-serif: 'EB Garamond', serif;
}
@theme {
--color-rainby-red-50: #fef4f2;
--color-rainby-red-100: #ffe7e1;
--color-rainby-red-200: #ffd2c8;
--color-rainby-red-300: #ffb4a3;
--color-rainby-red-400: #fd876c;
--color-rainby-red-500: #f5603e;
--color-rainby-red-600: #e24420;
--color-rainby-red-700: #be3617;
--color-rainby-red-800: #9d3017;
--color-rainby-red-900: #822d1a;
--color-rainby-red-950: #471408;
--color-rainby-orange-50: #fff8ed;
--color-rainby-orange-100: #ffefd5;
--color-rainby-orange-200: #fedbaa;
--color-rainby-orange-300: #fdc175;
--color-rainby-orange-400: #fb9b3c;
--color-rainby-orange-500: #f97e16;
--color-rainby-orange-600: #ea630c;
--color-rainby-orange-700: #c24a0c;
--color-rainby-orange-800: #9a3b12;
--color-rainby-orange-900: #7c3212;
--color-rainby-orange-950: #431707;
--color-rainby-yellow-50: #fefde8;
--color-rainby-yellow-100: #fefdc3;
--color-rainby-yellow-200: #fef9a3;
--color-rainby-yellow-300: #fdec47;
--color-rainby-yellow-400: #f9db16;
--color-rainby-yellow-500: #e9c209;
--color-rainby-yellow-600: #ca9704;
--color-rainby-yellow-700: #a16c07;
--color-rainby-yellow-800: #85550e;
--color-rainby-yellow-900: #714512;
--color-rainby-yellow-950: #422406;
--color-rainby-green-50: #f8faeb;
--color-rainby-green-100: #edf4d3;
--color-rainby-green-200: #ddebab;
--color-rainby-green-300: #c4dc7a;
--color-rainby-green-400: #b6d167;
--color-rainby-green-500: #8daf33;
--color-rainby-green-600: #6e8b25;
--color-rainby-green-700: #546b20;
--color-rainby-green-800: #44551f;
--color-rainby-green-900: #3a491e;
--color-rainby-green-950: #1e280b;
--color-rainby-cyan-50: #effbfc;
--color-rainby-cyan-100: #d7f2f6;
--color-rainby-cyan-200: #b4e6ed;
--color-rainby-cyan-300: #81d3e0;
--color-rainby-cyan-400: #45b6cb;
--color-rainby-cyan-500: #2a9ab0;
--color-rainby-cyan-600: #257d95;
--color-rainby-cyan-700: #24657a;
--color-rainby-cyan-800: #265464;
--color-rainby-cyan-900: #234756;
--color-rainby-cyan-950: #122e3a;
--color-rainby-blue-50: #eff5ff;
--color-rainby-blue-100: #dbe8fe;
--color-rainby-blue-200: #bfd8fe;
--color-rainby-blue-300: #a2c9fe;
--color-rainby-blue-400: #5f9efb;
--color-rainby-blue-500: #3a79f7;
--color-rainby-blue-600: #2358ed;
--color-rainby-blue-700: #1c44d9;
--color-rainby-blue-800: #1d38b0;
--color-rainby-blue-900: #1d358b;
--color-rainby-blue-950: #172254;
--color-rainby-violet-50: #eef0ff;
--color-rainby-violet-100: #dfe3ff;
--color-rainby-violet-200: #bdc2ff;
--color-rainby-violet-300: #a3a8fe;
--color-rainby-violet-400: #837ffa;
--color-rainby-violet-500: #6e60f4;
--color-rainby-violet-600: #6043e8;
--color-rainby-violet-700: #5435cd;
--color-rainby-violet-800: #442ea5;
--color-rainby-violet-900: #3b2c83;
--color-rainby-violet-950: #231a4c;
--color-rainby-neutral-50: #f6f6f6;
--color-rainby-neutral-100: #e7e7e7;
--color-rainby-neutral-200: #d1d1d1;
--color-rainby-neutral-300: #b0b0b0;
--color-rainby-neutral-400: #888888;
--color-rainby-neutral-500: #737373;
--color-rainby-neutral-600: #5d5d5d;
--color-rainby-neutral-700: #4f4f4f;
--color-rainby-neutral-800: #454545;
--color-rainby-neutral-900: #3d3d3d;
--color-rainby-neutral-950: #262626;
}
:root {
/* Core palette */
--background: var(--color-rainby-neutral-50);
--border: var(--color-rainby-neutral-500);
--input: var(--color-rainby-neutral-800);
--foreground: var(--color-rainby-neutral-950);
--ring: var(--color-rainby-cyan-700);
/* Surfaces */
--card: var(--color-rainby-neutral-50);
--card-foreground: var(--color-rainby-neutral-950);
--popover: var(--color-rainby-neutral-50);
--popover-foreground: var(--color-rainby-neutral-950);
/* Semantic */
--primary: var(--color-rainby-cyan-200);
--primary-foreground: var(--color-rainby-cyan-950);
--secondary: var(--color-rainby-green-200);
--secondary-foreground: var(--color-rainby-green-950);
--muted: var(--color-rainby-neutral-300);
--muted-foreground: var(--color-rainby-neutral-600);
--accent: var(--color-rainby-violet-200);
--accent-foreground: var(--color-rainby-violet-950);
--destructive: var(--color-rainby-red-200);
--destructive-foreground: var(--color-rainby-red-950);
/* Charts (e.g. Recharts in ShadCN examples) */
--chart-1: var(--color-rainby-blue-500);
--chart-2: var(--color-rainby-green-500);
--chart-3: var(--color-rainby-yellow-500);
--chart-4: var(--color-rainby-orange-500);
--chart-5: var(--color-rainby-red-500);
/* Sidebar (if you use ShadCN’s sidebar components) */
--sidebar: var(--color-rainby-neutral-50);
--sidebar-foreground: var(--color-rainby-neutral-950);
--sidebar-primary: var(--color-rainby-cyan-200);
--sidebar-primary-foreground: var(--color-rainby-cyan-950);
--sidebar-accent: var(--color-rainby-violet-200);
--sidebar-accent-foreground: var(--color-rainby-violet-950);
--sidebar-border: var(--color-rainby-neutral-500);
--sidebar-ring: var(--color-rainby-cyan-700);
/* Radii */
--radius: 0.625rem;
}
/* ──────────────── Dark Mode Override ──────────────── */
.dark {
/* Core palette flips */
--background: var(--color-rainby-neutral-950);
--ring: var(--color-rainby-cyan-300);
--border: var(--color-rainby-neutral-500);
--input: var(--color-rainby-neutral-300);
--foreground: var(--color-rainby-neutral-50);
/* Surfaces */
--card: var(--color-rainby-neutral-950);
--card-foreground: var(--color-rainby-neutral-50);
--popover: var(--color-rainby-neutral-950);
--popover-foreground: var(--color-rainby-neutral-50);
/* Semantic */
--primary: var(--color-rainby-cyan-800);
--primary-foreground: var(--color-rainby-cyan-50);
--secondary: var(--color-rainby-green-800);
--secondary-foreground: var(--color-rainby-green-50);
--muted: var(--color-rainby-neutral-600);
--muted-foreground: var(--color-rainby-neutral-100);
--accent: var(--color-rainby-violet-800);
--accent-foreground: var(--color-rainby-violet-50);
--destructive: var(--color-rainby-red-800);
--destructive-foreground: var(--color-rainby-red-50);
/* Charts */
--chart-1: var(--color-rainby-blue-400);
--chart-2: var(--color-rainby-green-400);
--chart-3: var(--color-rainby-yellow-400);
--chart-4: var(--color-rainby-orange-400);
--chart-5: var(--color-rainby-red-400);
/* Sidebar */
--sidebar: var(--color-rainby-neutral-950);
--sidebar-foreground: var(--color-rainby-neutral-50);
--sidebar-primary: var(--color-rainby-cyan-800);
--sidebar-primary-foreground: var(--color-rainby-cyan-50);
--sidebar-accent: var(--color-rainby-violet-800);
--sidebar-accent-foreground: var(--color-rainby-violet-50);
--sidebar-border: var(--color-rainby-neutral-500);
--sidebar-ring: var(--color-rainby-cyan-700);
/* Radii stays same */
--radius: 0.625rem;
}
@utility frosted-glass {
@apply bg-background/30 rounded-lg shadow-lg backdrop-blur-md backdrop-opacity-75 backdrop-filter;
}
@utility vstack {
@apply flex flex-col;
}
@utility hstack {
@apply flex flex-row;
}
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
+102
View File
@@ -0,0 +1,102 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { ChevronRight, MoreHorizontal } from 'lucide-react';
import { cn } from '@shadcn/lib/utils';
function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
return (
<ol
data-slot="breadcrumb-list"
className={cn(
'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
className
)}
{...props}
/>
);
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
return (
<li
data-slot="breadcrumb-item"
className={cn('inline-flex items-center gap-1.5', className)}
{...props}
/>
);
}
function BreadcrumbLink({
asChild,
className,
...props
}: React.ComponentProps<'a'> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot : 'a';
return (
<Comp
data-slot="breadcrumb-link"
className={cn('hover:text-foreground transition-colors', className)}
{...props}
/>
);
}
function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn('text-foreground font-normal', className)}
{...props}
/>
);
}
function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<'li'>) {
return (
<li
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn('[&>svg]:size-3.5', className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
);
}
function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
return (
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
className={cn('flex size-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontal className="size-4" />
<span className="sr-only">More</span>
</span>
);
}
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};
+226
View File
@@ -0,0 +1,226 @@
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
import { cn } from '@shadcn/lib/utils';
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
}
function DropdownMenuPortal({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
}
function DropdownMenuTrigger({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
}
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
);
}
function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
}
function DropdownMenuItem({
className,
inset,
variant = 'default',
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: 'default' | 'destructive';
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
);
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
}
function DropdownMenuRadioGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
);
}
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn('px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', className)}
{...props}
/>
);
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
);
}
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
);
}
function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
);
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
className
)}
{...props}
/>
);
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
};
@@ -0,0 +1,161 @@
import * as React from 'react';
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
import { cva } from 'class-variance-authority';
import { ChevronDownIcon } from 'lucide-react';
import { cn } from '@shadcn/lib/utils';
function NavigationMenu({
className,
children,
viewport = true,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
viewport?: boolean;
}) {
return (
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
data-viewport={viewport}
className={cn(
'group/navigation-menu relative flex max-w-max flex-1 items-center justify-center',
className
)}
{...props}
>
{children}
{viewport && <NavigationMenuViewport />}
</NavigationMenuPrimitive.Root>
);
}
function NavigationMenuList({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn('group flex flex-1 list-none items-center justify-center gap-1', className)}
{...props}
/>
);
}
function NavigationMenuItem({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return (
<NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
className={cn('relative', className)}
{...props}
/>
);
}
const navigationMenuTriggerStyle = cva(
'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1'
);
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), 'group', className)}
{...props}
>
{children}{' '}
<ChevronDownIcon
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
);
}
function NavigationMenuContent({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return (
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
'data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto',
'group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none',
className
)}
{...props}
/>
);
}
function NavigationMenuViewport({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
return (
<div className={cn('absolute top-full left-0 isolate z-50 flex justify-center')}>
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn(
'origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]',
className
)}
{...props}
/>
</div>
);
}
function NavigationMenuLink({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
);
}
function NavigationMenuIndicator({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
return (
<NavigationMenuPrimitive.Indicator
data-slot="navigation-menu-indicator"
className={cn(
'data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden',
className
)}
{...props}
>
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
</NavigationMenuPrimitive.Indicator>
);
}
export {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
navigationMenuTriggerStyle,
};
+19
View File
@@ -0,0 +1,19 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function formatDate(input: string | number): string {
const date = new Date(input);
return date.toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
});
}
export function absoluteUrl(path: string) {
return `${process.env.NEXT_PUBLIC_APP_URL}${path}`;
}
@@ -0,0 +1,55 @@
---
import '@global.css';
export interface Props {
title: string;
description: string;
}
const { title, description } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<script is:inline>
const getThemePreference = () => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
const isDark = getThemePreference() === 'dark';
document.documentElement.classList[isDark ? 'add' : 'remove']('dark');
if (typeof localStorage !== 'undefined') {
const observer = new MutationObserver(() => {
const isDark = document.documentElement.classList.contains('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class'],
});
}
</script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="robots" content="index,follow" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href="https://www.ermnvldmr.com/" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap"
rel="stylesheet"
/>
</head>
<body>
<slot />
</body>
</html>
+25
View File
@@ -0,0 +1,25 @@
---
import IndexLayout from '@www/layouts/IndexLayout/IndexLayout.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '@www/shared/constants';
---
<IndexLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<main class="flex h-full w-full justify-center p-4">
<div>
<h2>Welcome to my page!</h2>
<p>
If you see this page, my website is successfuly installed and working.
<br />
It will be further configured.
</p>
<p>
For the source code and support please refer to <a href="https://github.com/deytenit"
>github.com</a
>.
<br />
Pictures of mine is available at <a href="https://instagram.com/deytenit">instagram.com</a>.
</p>
<p><em>Thank you for visiting!</em></p>
</div>
</main>
</IndexLayout>
+2
View File
@@ -0,0 +1,2 @@
export const SITE_TITLE = 'Vladimir Eremin';
export const SITE_DESCRIPTION = 'Thoughts and creations of mine';
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"strictNullChecks": true,
"baseUrl": ".",
"paths": {
"@*": ["./src/*"]
},
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}