Rewritten in Astro (i had better separate this commit into smaller ones)

This commit is contained in:
DarkCat09 2023-05-05 14:01:09 +04:00
commit be964a8c49
32 changed files with 10954 additions and 0 deletions

25
.gitignore vendored Normal file
View file

@ -0,0 +1,25 @@
# 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
# IDE
.vscode/
.idea/

55
README.md Normal file
View file

@ -0,0 +1,55 @@
# Astro Starter Kit: Basics
```
npm create astro@latest -- --template basics
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png)
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```
/
├── public/
│ └── favicon.svg
├── src/
│ ├── components/
│ │ └── Card.astro
│ ├── layouts/
│ │ └── Layout.astro
│ └── pages/
│ └── index.astro
└── package.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.
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 |
| :--------------------- | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:3000` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).

8
astro.config.mjs Normal file
View file

@ -0,0 +1,8 @@
import { defineConfig } from 'astro/config';
import compress from "astro-compress";
// https://astro.build/config
export default defineConfig({
integrations: [compress()]
});

10114
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

17
package.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"astro": "^2.3.0",
"astro-compress": "^1.1.42",
"less": "^4.1.3"
}
}

9
public/favicon.svg Normal file
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

1
public/fonts/LICENSE.txt Executable file
View file

@ -0,0 +1 @@
If you use the icons publicly, please link to https://icons8.com/line-awesome somewhere on your page or artwork, so that more creators could know about it and use it for free.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

44
src/components/Icon.astro Normal file
View file

@ -0,0 +1,44 @@
---
export interface Props {
code: string;
}
const { code } = Astro.props;
/* https://icons8.com/line-awesome */
---
<span class="icon">{code}</span>
<style lang="less">
@font-face {
font-family: 'Line Awesome Regular';
font-style: normal;
font-weight: 400;
font-display: auto;
src: url("/fonts/la-regular-400.eot?#iefix") format("embedded-opentype"), url("/fonts/la-regular-400.woff2") format("woff2"), url("/fonts/la-regular-400.woff") format("woff"), url("/fonts/la-regular-400.ttf") format("truetype");
}
@font-face {
font-family: 'Line Awesome Solid';
font-style: normal;
font-weight: 900;
font-display: auto;
src: url("/fonts/la-solid-900.eot?#iefix") format("embedded-opentype"), url("/fonts/la-solid-900.woff2") format("woff2"), url("/fonts/la-solid-900.woff") format("woff"), url("/fonts/la-solid-900.ttf") format("truetype");
}
@font-face {
font-family: 'Line Awesome Brands';
font-style: normal;
font-weight: normal;
font-display: auto;
src: url("/fonts/la-brands-400.eot?#iefix") format("embedded-opentype"), url("/fonts/la-brands-400.woff2") format("woff2"), url("/fonts/la-brands-400.woff") format("woff"), url("/fonts/la-brands-400.ttf") format("truetype");
}
span.icon {
display: inline-block;
font-family: 'Line Awesome Regular', 'Line Awesome Solid', 'Line Awesome Brands';
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
}
</style>

66
src/components/Menu.astro Normal file
View file

@ -0,0 +1,66 @@
---
import Icon from "./Icon.astro";
import ThemeModal from "./ThemeModal.astro";
---
<ThemeModal />
<nav>
<ul>
<li>
<a href="#">
<Icon code="&#xf015;" />
Homepage
</a>
</li>
<li>
<a href="#skills">
Skills
</a>
</li>
<li>
<a href="#projects">
Projects
</a>
</li>
</ul>
<ul>
<li>
<a href="#theme">
Theme
</a>
</li>
<li>
<a href="#dc09ru">
Services
</a>
</li>
<li>
<a href="https://blog.dc09.ru">
Blog
</a>
</li>
</ul>
</nav>
<style lang="less">
nav {
margin: 0.4rem;
display: flex;
flex-direction: row;
justify-content: space-between;
}
ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
li {
padding: 0 0.6rem;
font-size: 1.1rem;
}
</style>

View file

@ -0,0 +1,107 @@
---
import Icon from './Icon.astro';
export interface Props {
app: string;
url: string;
name: string;
icon: string;
copyName?: boolean;
}
const { app, url, name, icon, copyName = false } = Astro.props;
---
<div class="profile">
<a href={url} target="_blank" class="profile-main">
<span class="icon-wrapper">
<Icon code={icon} />
</span>
<span class="content">
<span class="app">{app}</span>
<span class="nickname">{name}</span>
</span>
</a>
<a href="javascript:void(0)" class="copy" data-url={copyName ? name : url}>
<span class="icon-wrapper">
<Icon code="&#xf0c5;" />
</span>
</a>
</div>
<script>
const copy = document.querySelectorAll('.copy')
const copyClickHandler = (ev: Event) => {
const elem = ev.currentTarget as HTMLElement
const link = elem.dataset.url
if (!link) return
navigator.clipboard.writeText(link)
}
for (let elem of copy) {
elem.addEventListener('click', copyClickHandler)
}
</script>
<style lang="less">
.profile {
padding: 0.4rem 0.6rem;
display: flex;
flex-direction: row;
align-items: center;
border-radius: 2rem;
&:hover {
background: var(--accent-bg);
}
}
a.profile-main {
@icon-size: 2.3rem;
flex-grow: 1;
display: flex;
flex-direction: row;
align-items: center;
color: var(--fg);
text-decoration: none;
.icon-wrapper {
font-size: @icon-size;
margin-right: 0.4rem;
}
}
a.copy {
@icon-size: 1.8rem;
position: relative;
font-size: 1.1rem;
display: flex;
flex-direction: column;
align-items: center;
.icon-wrapper {
font-size: @icon-size;
margin-left: 0.4rem;
}
}
.content {
display: flex;
flex-direction: column;
.app {
font-weight: 600;
}
.nickname {
font-size: 0.9rem;
color: var(--fg-sec);
}
}
</style>

View file

@ -0,0 +1,73 @@
---
import Input from '../layouts/Input.astro';
export interface Props {
id: string;
label: string;
checked?: boolean;
}
const { id, label, checked = false } = Astro.props;
---
<Input label={label}>
<span class="input-wrapper" slot="before">
<input type="checkbox" name={id} id={id} checked={checked} />
<span class="switch">
<span class="circle"></span>
</span>
</span>
</Input>
<style lang="less">
input {
display: none;
}
@switch-width: 2.5rem;
@switch-height: @switch-width * 0.5;
@switch-bdrs: @switch-width * 0.4;
@circle-size: @switch-width * 0.4;
@circle-margin: @switch-width * 0.04;
.switch {
display: inline-flex;
flex-direction: row;
align-items: center;
width: @switch-width;
height: @switch-height;
margin-right: 0.5rem;
background: var(--bg-sec);
border-radius: @switch-bdrs;
.circle {
width: @circle-size;
height: @circle-size;
margin-right: 0;
margin-left: @circle-margin;
// on the left if not checked
transform: translateX(0rem);
transition: transform 0.25s ease 0s;
background: #fff;
border-radius: 50%;
}
}
input:checked ~ .switch {
background: var(--accent);
.circle {
margin-left: 0;
margin-right: @circle-margin;
// on the right if checked
@x: @switch-width - @circle-margin - @circle-size;
transform: translateX(@x);
}
}
</style>

View file

@ -0,0 +1,22 @@
---
export interface Props {
id: string;
label: string;
placeholder?: string;
regex?: string | null;
}
const { id, label, placeholder = "", regex = null } = Astro.props;
---
<label>
<span class="label">{label}</span>
<input type="text" name={id} id={id} placeholder={placeholder} pattern={regex}>
</label>
<style lang="less">
input {
background: var(--sec-bg);
color: var(--fg);
}
</style>

View file

@ -0,0 +1,164 @@
---
import Modal from "../layouts/Modal.astro";
import Switch from "./Switch.astro";
import TextBox from "./TextBox.astro";
---
<Modal id="theme">
<noscript>
These options will <b>not</b> work without JavaScript
</noscript>
<Switch id="dark" label="Dark theme" checked />
<Switch id="custom-theme" label="Set custom colors" />
<section id="custom-colors">
<TextBox id="accent" label="Accent color" placeholder="HEX (#fff)" regex="#?(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})" />
<label>
<span class="label">Accent color</span>
<input type="color" name="accent" id="accent">
</label>
</section>
</Modal>
<style lang="less">
#custom-colors {
display: none;
&[data-show=1] {
display: flex;
flex-direction: column;
}
}
</style>
<script>
const colorsSection = document.getElementById('custom-colors')
class ThemingOption {
private cookieKey: string
private cookieSwitch: Array<string | RegExp>
private callback: (input: HTMLInputElement, state: boolean) => any
private getval: (input: HTMLInputElement) => any
private media: string | null
private _input: HTMLInputElement | null
get input() { return this._input }
constructor (
cookieKey: string,
cookieSwitch: Array<string | RegExp> | null,
callback: (input: HTMLInputElement, state: boolean) => any,
getval: (input: HTMLInputElement) => any,
input: HTMLInputElement | null = null,
media: string | null = null,
) {
this.cookieKey = cookieKey
this.cookieSwitch = cookieSwitch
this.callback = callback
this.getval = getval
this._input = input
this.media = media
}
/*
function themeCheck() {
if (
document.cookie.includes('dc09_dark=1') || (
matchMedia('(prefers-color-scheme: dark)') &&
!document.cookie.includes('dc09_dark=0')
)
) {
document.body.dataset.dark = '1'
switchDark.checked = true
}
else {
document.body.dataset.dark = '0'
switchDark.checked = false
}
}
Here is the rewritten themeCheck:
*/
check() {
const match = this.cookieSwitch.map(
value => document.cookie.match(
`${this.cookieKey}=${value}`
)
)
if (
match[1] || (
this.media && !match[0] &&
matchMedia(this.media).matches
)
) {
if (this.callback)
this.callback(this._input, true)
return
}
if (this.callback)
this.callback(this._input, false)
}
update() {
const value = this.getval(this._input)
const localhost = window.location.hostname == 'localhost'
const domain = localhost ? 'localhost' : 'dc09.ru'
const cookie = `${this.cookieKey}=${value};domain=${domain};samesite=lax`
document.cookie = cookie
this.check()
}
}
const opts = {
dark: new ThemingOption(
'dc09_dark', ['0', '1'],
(input: HTMLInputElement, state: boolean) => {
input.checked = state
document.body.dataset.dark = state ? '1' : '0'
},
(input: HTMLInputElement) => input.checked ? 1 : 0,
document.getElementById('dark') as HTMLInputElement,
'(prefers-color-scheme: dark)',
),
custom: new ThemingOption(
'dc09_custom', ['0', '1'],
(input: HTMLInputElement, state: boolean) => {
input.checked = state
colorsSection.dataset.show = state ? '1' : '0'
if (state)
loadCustomTheme()
},
(input: HTMLInputElement) => input.checked ? 1 : 0,
document.getElementById('custom-theme') as HTMLInputElement,
)
}
for (let optObj of Object.values(opts)) {
((opt) => {
opt.check()
opt.input.addEventListener('input', () => opt.update())
})(optObj)
}
function loadCustomTheme() {
// Load `theme.less`
const lessStyles = document.createElement('link')
lessStyles.rel = 'stylesheet/less'
lessStyles.type = 'text/css'
lessStyles.href = '/src/styles/theme.less'
document.head.append(lessStyles)
// Load LessCSS script element
const lessScript = document.createElement('script')
lessScript.src = 'https://cdn.jsdelivr.net/npm/less'
// Replace variables
lessScript.onload = () => {
eval('less').modifyVars({
'accent': '#50aa70',
})
}
// Load LessCSS
document.head.append(lessScript)
}
</script>

1
src/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="astro/client" />

26
src/layouts/Input.astro Normal file
View file

@ -0,0 +1,26 @@
---
export interface Props {
label: string;
}
const { label } = Astro.props;
---
<label>
<slot name="before" />
<span class="label">{label}</span>
<slot name="after" />
</label>
<style lang="less">
label {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
&:not(:first-of-type) {
margin-top: 0.25rem;
}
}
</style>

73
src/layouts/Layout.astro Normal file
View file

@ -0,0 +1,73 @@
---
import Menu from '../components/Menu.astro';
export interface Props {
title: string;
}
const { title } = Astro.props;
---
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<meta name="description" content="Hi! I'm Andrew aka DarkCat09, a 13-years-old fullstack developer from Russia" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
</head>
<body>
<Menu />
<slot />
</body>
</html>
<style lang="less" is:global>
@import "/src/styles/theme.less";
body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--fg);
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Liberation Sans', 'Helvetica Neue', sans-serif;
display: flex;
flex-direction: column;
}
main {
margin: auto;
padding: 0.25rem;
max-width: 300rem;
}
section {
margin: 0.5rem 0;
}
hgroup > * {
margin: 0;
&:last-child {
margin: initial;
color: var(--fg-sec);
font-size: 1.2rem;
font-weight: 500;
}
}
h1 { font-size: 2.4rem; }
a, .link {
color: var(--accent);
text-decoration: none;
&:hover {
color: var(--accent-hl);
}
}
</style>

68
src/layouts/Modal.astro Normal file
View file

@ -0,0 +1,68 @@
---
import Icon from '../components/Icon.astro';
export interface Props {
id: string;
}
const { id } = Astro.props;
---
<div class="modal-bg" id={id}>
<a href="#" class="modal-bg-close"></a>
<div class="modal">
<div class="controls">
<a href="#"><Icon code="&#xf00d;" /></a>
</div>
<div class="content">
<slot />
</div>
</div>
</div>
<style lang="less">
.modal-bg {
display: none;
position: absolute;
z-index: 100;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.3);
&:target {
display: flex;
flex-direction: column;
}
}
.modal-bg-close {
display: block;
position: absolute;
width: 100vw;
height: 100vh;
z-index: 101;
cursor: default;
}
.modal {
display: flex;
flex-direction: column;
margin: auto;
padding: 1rem;
z-index: 102;
border-radius: 2rem;
background: var(--bg);
}
.controls {
display: flex;
flex-direction: row;
justify-content: end;
}
</style>

36
src/pages/index.astro Normal file
View file

@ -0,0 +1,36 @@
---
import Layout from '../layouts/Layout.astro';
import Profile from '../components/Profile.astro';
---
<Layout title="DarkCat09 | Homepage">
<main>
<hgroup>
<h1>Hi! I'm Andrew.</h1>
<h2>13-years-old fullstack dev</h2>
</hgroup>
<section id="profiles">
<Profile app="Gitea" icon="&#xf1d3;" name="DarkCat09" url="https://git.dc09.ru/DarkCat09" />
<Profile app="GitHub" icon="&#xf09b;" name="DarkCat09" url="https://github.com/DarkCat09" />
<Profile app="E-mail" icon="&#xf0e0;" name="darkcat09@vivaldi.net" url="mailto:darkcat09@vivaldi.net" copyName />
<Profile app="Telegram" icon="&#xf2c6;" name="@darkcat09" url="https://t.me/darkcat09" />
<Profile app="Matrix" icon="&#xf4ad;" name="darkcat09@dc09.ru" url="https://matrix.to/#/@darkcat09:dc09.ru" copyName />
<Profile app="Discord" icon="&#xf392;" name="DarkCat09#5587" url="https://discord.com/app" copyName />
</section>
</main>
</Layout>
<style lang="less">
#profiles {
display: grid;
grid-template-columns: repeat(1, auto);
@media (min-width: 450px) {
grid-template-columns: repeat(2, auto);
}
@media (min-width: 670px) {
grid-template-columns: repeat(3, auto);
}
}
</style>

42
src/styles/theme.less Normal file
View file

@ -0,0 +1,42 @@
@accent: #5070ca;
body {
@bg-light: #fff;
@fg-light: #202020;
@bg-dark: #222229;
@fg-dark: #eee;
--accent: @accent;
.light-mixin() {
--bg: @bg-light;
--fg: @fg-light;
--bg-sec: darken(@bg-light, 30%);
--fg-sec: lighten(@fg-light, 15%);
--accent-bg: darken(@bg-light, 10%);
--accent-hl: darken(@accent, 15%);
}
.dark-mixin() {
--bg: @bg-dark;
--fg: @fg-dark;
--bg-sec: lighten(@bg-dark, 15%);
--fg-sec: darken(@bg-light, 25%);
--accent-bg: lighten(@bg-dark, 5%);
--accent-hl: lighten(@accent, 15%);
}
.dark-mixin();
// &.dark {
&[data-dark="1"] {
.dark-mixin();
}
// &.light {
&[data-dark="0"] {
.light-mixin();
}
@media (prefers-color-scheme: light) {
.light-mixin();
}
}

3
tsconfig.json Normal file
View file

@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/base"
}