Profile -> GridItem + Grid, services page, upd: age
This commit is contained in:
parent
562caea0ff
commit
be5d01a844
10 changed files with 271 additions and 118 deletions
127
src/components/GridItem.astro
Normal file
127
src/components/GridItem.astro
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
elemClass?: Array<String>;
|
||||||
|
btnClass?: Array<string>;
|
||||||
|
dataJs?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
title, text, url,
|
||||||
|
elemClass = [],
|
||||||
|
btnClass = [],
|
||||||
|
dataJs = "",
|
||||||
|
} = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class:list={["gi", ...elemClass]}>
|
||||||
|
<a href={url} target="_blank" class="gi-main">
|
||||||
|
<span class="icon-wrapper">
|
||||||
|
<slot name="icon-primary" />
|
||||||
|
</span>
|
||||||
|
<span class="content">
|
||||||
|
<span class="title">
|
||||||
|
<slot name="title">
|
||||||
|
{title}
|
||||||
|
</slot>
|
||||||
|
</span>
|
||||||
|
<span class="text">
|
||||||
|
<slot name="text">
|
||||||
|
{text}
|
||||||
|
</slot>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a href="javascript:void(0)" class:list={["gi-btn", ...btnClass]} data-js={dataJs}>
|
||||||
|
<span class="icon-wrapper">
|
||||||
|
<slot name="icon-btn" />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
@import "/src/styles/grid_calc.less";
|
||||||
|
|
||||||
|
.gi {
|
||||||
|
// vertical horizontal
|
||||||
|
padding: 0.4rem @gi-horiz-padding;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: @gi-width;
|
||||||
|
min-height: 3rem;
|
||||||
|
border-radius: var(--bdrs);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--accent-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.gi-main {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
color: var(--fg);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
.icon-wrapper {
|
||||||
|
width: @gi-icon-primary;
|
||||||
|
font-size: @gi-icon-primary;
|
||||||
|
margin-right: @gi-icon-margin;
|
||||||
|
|
||||||
|
& > :global(img) {
|
||||||
|
width: @gi-icon-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.gi-btn {
|
||||||
|
position: relative;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon-wrapper {
|
||||||
|
width: @gi-icon-btn;
|
||||||
|
font-size: @gi-icon-btn;
|
||||||
|
margin-left: @gi-icon-margin;
|
||||||
|
|
||||||
|
& > :global(img) {
|
||||||
|
width: @gi-icon-btn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
width: @gi-text-width;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--fg-sec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
34
src/components/HostedService.astro
Normal file
34
src/components/HostedService.astro
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import GridItem from "./GridItem.astro";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
url: string;
|
||||||
|
iconUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, description, url, iconUrl = `${url}/favicon.ico` } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<GridItem
|
||||||
|
title={name}
|
||||||
|
text={description}
|
||||||
|
url={url}
|
||||||
|
elemClass={["gi-service"]}
|
||||||
|
>
|
||||||
|
<img src={iconUrl} slot="icon-primary" />
|
||||||
|
</GridItem>
|
||||||
|
|
||||||
|
<style lang="less" is:global>
|
||||||
|
@import "/src/styles/grid_calc.less";
|
||||||
|
|
||||||
|
// Overwriting default GridItem's width
|
||||||
|
.gi-service {
|
||||||
|
width: @service-width;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: @service-text-width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,4 +1,5 @@
|
||||||
---
|
---
|
||||||
|
import GridItem from './GridItem.astro';
|
||||||
import Icon from './Icon.astro';
|
import Icon from './Icon.astro';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -12,28 +13,21 @@ export interface Props {
|
||||||
const { app, url, name, icon, copyName = false } = Astro.props;
|
const { app, url, name, icon, copyName = false } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="profile">
|
<GridItem
|
||||||
<a href={url} target="_blank" class="profile-main">
|
title={app} text={name} url={url}
|
||||||
<span class="icon-wrapper">
|
elemClass={["gi-profile"]}
|
||||||
<Icon code={icon} />
|
btnClass={["profile-copy"]}
|
||||||
</span>
|
dataJs={copyName ? name : url}
|
||||||
<span class="content">
|
>
|
||||||
<span class="app">{app}</span>
|
<Icon code={icon} slot="icon-primary" />
|
||||||
<span class="nickname">{name}</span>
|
<Icon code="" slot="icon-btn" />
|
||||||
</span>
|
</GridItem>
|
||||||
</a>
|
|
||||||
<a href="javascript:void(0)" class="copy" data-url={copyName ? name : url}>
|
|
||||||
<span class="icon-wrapper">
|
|
||||||
<Icon code="" />
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const copy = document.querySelectorAll('.copy')
|
const copy = document.querySelectorAll('.profile-copy')
|
||||||
const copyClickHandler = (ev: Event) => {
|
const copyClickHandler = (ev: Event) => {
|
||||||
const elem = ev.currentTarget as HTMLElement
|
const elem = ev.currentTarget as HTMLElement
|
||||||
const link = elem.dataset.url
|
const link = elem.dataset.js
|
||||||
if (!link) return
|
if (!link) return
|
||||||
navigator.clipboard.writeText(link)
|
navigator.clipboard.writeText(link)
|
||||||
}
|
}
|
||||||
|
@ -42,70 +36,14 @@ const { app, url, name, icon, copyName = false } = Astro.props;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less" is:global>
|
||||||
@import "/src/styles/profile_calc.less";
|
@import "/src/styles/grid_calc.less";
|
||||||
|
|
||||||
.profile {
|
|
||||||
// vertical horizontal
|
|
||||||
padding: 0.4rem @profile-horiz-padding;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
|
.gi-profile {
|
||||||
width: @profile-width;
|
width: @profile-width;
|
||||||
border-radius: var(--bdrs);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--accent-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a.profile-main {
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
color: var(--fg);
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
.icon-wrapper {
|
|
||||||
font-size: @profile-icon-app;
|
|
||||||
margin-right: @profile-icon-margin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a.copy {
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.icon-wrapper {
|
|
||||||
font-size: @profile-icon-copy;
|
|
||||||
margin-left: @profile-icon-margin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
width: @profile-text-width;
|
width: @profile-text-width;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.app {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nickname {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--fg-sec);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
13
src/layouts/Grid.astro
Normal file
13
src/layouts/Grid.astro
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<section>
|
||||||
|
<slot />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
section {
|
||||||
|
margin: 0.4rem 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -48,16 +48,18 @@ const { title, description } = Astro.props;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: @main-padding;
|
padding: @main-padding;
|
||||||
max-width: @max-width;
|
max-width: @max-width;
|
||||||
/*
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: hidden;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
article {
|
article {
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
||||||
|
&.centered {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
---
|
---
|
||||||
import MainLayout from '../layouts/MainLayout.astro';
|
import MainLayout from '../layouts/MainLayout.astro';
|
||||||
|
import Grid from '../layouts/Grid.astro';
|
||||||
import Profile from '../components/Profile.astro';
|
import Profile from '../components/Profile.astro';
|
||||||
import ListItem from '../components/ListItem.astro';
|
import ListItem from '../components/ListItem.astro';
|
||||||
|
|
||||||
const description =
|
const description =
|
||||||
"Hi! I'm Andrew aka DarkCat09, " +
|
"Hi! I'm Andrew aka DarkCat09, " +
|
||||||
"a 13-years-old fullstack developer from Russia";
|
"a 14-years-old fullstack developer from Russia";
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainLayout page="Homepage" description={description}>
|
<MainLayout page="Homepage" description={description}>
|
||||||
<article class="centered">
|
<article class="centered">
|
||||||
<hgroup>
|
<hgroup>
|
||||||
<h1>Hi! I'm Andrew.</h1>
|
<h1>Hi! I'm Andrew.</h1>
|
||||||
<h2><span id="age">13</span>-years-old fullstack dev</h2>
|
<h2><span id="age">14</span>-years-old fullstack dev</h2>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
<section id="profiles">
|
<Grid>
|
||||||
<Profile app="Gitea" icon="" name="DarkCat09" url="https://git.dc09.ru/DarkCat09" />
|
<Profile app="Gitea" icon="" name="DarkCat09" url="https://git.dc09.ru/DarkCat09" />
|
||||||
<Profile app="GitHub" icon="" name="DarkCat09" url="https://github.com/DarkCat09" />
|
<Profile app="GitHub" icon="" name="DarkCat09" url="https://github.com/DarkCat09" />
|
||||||
<Profile app="E-mail" icon="" name="darkcat09@vivaldi.net" url="mailto:darkcat09@vivaldi.net" copyName />
|
<Profile app="E-mail" icon="" name="darkcat09@vivaldi.net" url="mailto:darkcat09@vivaldi.net" copyName />
|
||||||
<Profile app="Telegram" icon="" name="@darkcat09" url="https://t.me/darkcat09" />
|
<Profile app="Telegram" icon="" name="@darkcat09" url="https://t.me/darkcat09" />
|
||||||
<Profile app="Matrix" icon="" name="darkcat09:dc09.ru" url="https://matrix.to/#/@darkcat09:dc09.ru" copyName />
|
<Profile app="Matrix" icon="" name="darkcat09:dc09.ru" url="https://matrix.to/#/@darkcat09:dc09.ru" copyName />
|
||||||
<Profile app="Discord" icon="" name="DarkCat09#5587" url="https://discord.com/app" copyName />
|
<Profile app="Discord" icon="" name="DarkCat09#5587" url="https://discord.com/app" copyName />
|
||||||
</section>
|
</Grid>
|
||||||
</article>
|
</article>
|
||||||
<article class="card card-dashed" id="about">
|
<article class="card card-dashed" id="about">
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -47,7 +48,7 @@ const description =
|
||||||
</ul>
|
</ul>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem icon="">
|
<ListItem icon="">
|
||||||
Favorite OS: Manjaro Linux
|
Favorite OS: Arch Linux
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem icon="">
|
<ListItem icon="">
|
||||||
<code>
|
<code>
|
||||||
|
@ -61,21 +62,6 @@ const description =
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import "/src/styles/profile_calc.less";
|
|
||||||
|
|
||||||
#profiles {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(1, auto);
|
|
||||||
|
|
||||||
@media (min-width: @profile-width * 2) {
|
|
||||||
grid-template-columns: repeat(2, auto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: @profile-width * 3) {
|
|
||||||
grid-template-columns: repeat(3, auto);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#about > ul {
|
#about > ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -95,9 +81,14 @@ const description =
|
||||||
// in years
|
// in years
|
||||||
let age = ageMs / (1000 * 60 * 60 * 24 * 365.25)
|
let age = ageMs / (1000 * 60 * 60 * 24 * 365.25)
|
||||||
|
|
||||||
// fallback to default value
|
// if age < 14, date on user's PC is incorrect,
|
||||||
// + rounding
|
// so it's better to not update age.
|
||||||
age = (age < 13 ? 13 : Math.floor(age))
|
|
||||||
|
// otherwise, update:
|
||||||
|
if (age >= 14) {
|
||||||
|
// rounding
|
||||||
|
age = Math.floor(age)
|
||||||
|
|
||||||
document.getElementById('age').innerText = String(age)
|
document.getElementById('age').innerText = String(age)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
---
|
---
|
||||||
import MainLayout from "../layouts/MainLayout.astro";
|
import MainLayout from "../layouts/MainLayout.astro";
|
||||||
|
import Grid from "../layouts/Grid.astro";
|
||||||
|
import HostedService from "../components/HostedService.astro";
|
||||||
|
|
||||||
const description =
|
const description =
|
||||||
"Status and description of services " +
|
"Status and description of services " +
|
||||||
|
@ -7,5 +9,14 @@ const description =
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainLayout page="Services" description={description}>
|
<MainLayout page="Services" description={description}>
|
||||||
// soon
|
<Grid>
|
||||||
|
<HostedService name="SearXNG" description="Privacy-respecting hackable metasearch engine" url="https://searx.dc09.ru" />
|
||||||
|
<HostedService name="Gitea" description="Git with a cup of tea" url="https://git.dc09.ru" />
|
||||||
|
<HostedService name="Piped" description="Privacy-friendly alternative YouTube frontend, API and proxy" url="https://yt.dc09.ru" />
|
||||||
|
<HostedService name="Invidious" description="An open source alternative frontend to YouTube" url="https://inv.dc09.ru" />
|
||||||
|
<HostedService name="Shlink" description="The definitive self-hosted URL shortener written in PHP" url="https://url.dc09.ru" />
|
||||||
|
<HostedService name="SFTPGo" description="Fully featured and highly configurable SFTP server" url="https://cloud.dc09.ru" iconUrl="https://cloud.dc09.ru/static/favicon.ico" />
|
||||||
|
<HostedService name="Element" description="A glossy Matrix collaboration client for the web" url="https://elem.dc09.ru" iconUrl="https://elem.dc09.ru/themes/element/img/logos/element-logo.svg" />
|
||||||
|
<HostedService name="Cinny" description="A matrix client with simple, elegant and secure interface" url="https://cinny.dc09.ru" iconUrl="https://cinny.dc09.ru/assets/favicon-e5e25737.ico" />
|
||||||
|
</Grid>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
45
src/styles/grid_calc.less
Normal file
45
src/styles/grid_calc.less
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
@import "/src/styles/max_width.less";
|
||||||
|
|
||||||
|
@gi-icon-primary: 2.3rem;
|
||||||
|
@gi-icon-btn: 1.8rem;
|
||||||
|
|
||||||
|
@gi-text-width: 9.5rem;
|
||||||
|
@profile-text-width: 9rem;
|
||||||
|
@service-text-width: 14rem;
|
||||||
|
|
||||||
|
@gi-icon-margin: 0.4rem;
|
||||||
|
@gi-horiz-padding: 0.6rem;
|
||||||
|
|
||||||
|
// GridItem width without text
|
||||||
|
@gi-width-wo-text: @gi-horiz-padding + @gi-icon-primary + @gi-icon-margin + @gi-icon-margin + @gi-icon-btn + @gi-horiz-padding;
|
||||||
|
|
||||||
|
@gi-width: @gi-width-wo-text + @gi-text-width;
|
||||||
|
@profile-width: @gi-width-wo-text + @profile-text-width;
|
||||||
|
@service-width: @gi-width-wo-text + @service-text-width;
|
||||||
|
|
||||||
|
.grid-mixin(@custom-width) {
|
||||||
|
/*
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(1, auto);
|
||||||
|
|
||||||
|
@width-for-2: @custom-width * 2 + @all-padding;
|
||||||
|
@width-for-3: @custom-width * 3 + @all-padding;
|
||||||
|
|
||||||
|
& when (@width-for-2 < @max-width) {
|
||||||
|
@media (min-width: @width-for-2) {
|
||||||
|
grid-template-columns: repeat(2, auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& when (@width-for-3 < @max-width) {
|
||||||
|
@media (min-width: @width-for-3) {
|
||||||
|
grid-template-columns: repeat(3, auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
@max-width: 800px;
|
@max-width: 50rem;
|
||||||
@body-padding: 0.5rem;
|
@body-padding: 0.5rem;
|
||||||
@main-padding: 0.25rem;
|
@main-padding: 0.25rem;
|
||||||
|
@all-padding: @body-padding * 2 + @main-padding * 2;
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
@profile-icon-app: 2.3rem;
|
|
||||||
@profile-icon-copy: 1.8rem;
|
|
||||||
|
|
||||||
@profile-text-width: 9.5rem;
|
|
||||||
|
|
||||||
@profile-icon-margin: 0.4rem;
|
|
||||||
@profile-horiz-padding: 0.6rem;
|
|
||||||
|
|
||||||
@profile-width: @profile-horiz-padding + @profile-icon-app + @profile-icon-margin + @profile-text-width + @profile-icon-margin + @profile-icon-copy + @profile-horiz-padding;
|
|
Loading…
Add table
Reference in a new issue