Add /about
This commit is contained in:
parent
be35a0a7c1
commit
ae3e95a0c3
|
@ -9,8 +9,10 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router-dom": "^6.26.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.0.27",
|
"@types/react": "^18.0.27",
|
||||||
|
|
993
pnpm-lock.yaml
993
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
BIN
public/server.png
Normal file
BIN
public/server.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
15
public/three-dots.svg
Normal file
15
public/three-dots.svg
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
|
||||||
|
<svg width="30" height="10" viewBox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="#fa5f00">
|
||||||
|
<circle cx="15" cy="15" r="15">
|
||||||
|
<animate attributeName="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcMode="linear" repeatCount="indefinite"/>
|
||||||
|
<animate attributeName="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcMode="linear" repeatCount="indefinite"/>
|
||||||
|
</circle>
|
||||||
|
<circle cx="60" cy="15" r="9" fill-opacity="0.3">
|
||||||
|
<animate attributeName="r" from="9" to="9" begin="0s" dur="0.8s" values="9;15;9" calcMode="linear" repeatCount="indefinite"/>
|
||||||
|
<animate attributeName="fill-opacity" from="0.5" to="0.5" begin="0s" dur="0.8s" values=".5;1;.5" calcMode="linear" repeatCount="indefinite"/>
|
||||||
|
</circle>
|
||||||
|
<circle cx="105" cy="15" r="15">
|
||||||
|
<animate attributeName="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcMode="linear" repeatCount="indefinite"/>
|
||||||
|
<animate attributeName="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcMode="linear" repeatCount="indefinite"/>
|
||||||
|
</circle>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
30
src/App.tsx
30
src/App.tsx
|
@ -1,11 +1,27 @@
|
||||||
import { Main } from "./pages";
|
import React from 'react'
|
||||||
|
import { Route, Routes } from 'react-router-dom'
|
||||||
|
import Main from './pages/Main'
|
||||||
|
import About from './pages/About'
|
||||||
|
import SelfHosting from './pages/SelfHosting'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
let final: JSX.Element = <p>None</p>;
|
return (
|
||||||
|
<Routes>
|
||||||
document.location.pathname === "/" && (final = <Main />);
|
<Route
|
||||||
|
path="/"
|
||||||
return final;
|
index
|
||||||
|
element={<Main />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/about"
|
||||||
|
element={<About />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/about/selfhosting"
|
||||||
|
element={<SelfHosting />}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import "../keyframes.css";
|
import "../keyframes.css";
|
||||||
|
import { GenericProps } from "../types";
|
||||||
|
|
||||||
export interface Buttons {
|
export interface Buttons {
|
||||||
name: string;
|
name: string;
|
||||||
link: string;
|
link: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ButtonRowProps {
|
export interface ButtonRowProps extends GenericProps {
|
||||||
buttons: Buttons[];
|
buttons: Buttons[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ButtonRow = ({ buttons }: ButtonRowProps) => {
|
export const ButtonRow = ({ fadeIn, zoomIn, buttons }: ButtonRowProps) => {
|
||||||
|
const determineClasses = () => {
|
||||||
|
if (fadeIn) {
|
||||||
|
return "social-link-area fade-in";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zoomIn) {
|
||||||
|
return "social-link-area zoom-in";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "social-link-area";
|
||||||
|
};
|
||||||
|
|
||||||
const [shown, changeShown] = useState(false);
|
const [shown, changeShown] = useState(false);
|
||||||
|
|
||||||
const showItems = () => {
|
const showItems = () => {
|
||||||
|
@ -23,20 +37,20 @@ export const ButtonRow = ({ buttons }: ButtonRowProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="social-link-area"
|
className={determineClasses()}
|
||||||
onMouseOver={showItems}
|
onMouseOver={showItems}
|
||||||
onMouseOut={hideItems}
|
onMouseOut={hideItems}
|
||||||
>
|
>
|
||||||
{buttons.map((button, index) => {
|
{buttons.map((button, index) => {
|
||||||
return (
|
return (
|
||||||
<a
|
<Link
|
||||||
key={button.name}
|
key={button.name}
|
||||||
className="social-link-button"
|
className="social-link-button"
|
||||||
href={button.link}
|
to={button.link}
|
||||||
target="_blank"
|
target={button.link.startsWith("/") ? "_self" : "_blank"}
|
||||||
>
|
>
|
||||||
{button.name}
|
{button.name}
|
||||||
</a>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
|
import { TextProps } from "../types";
|
||||||
|
|
||||||
export interface DescriptionProps {
|
export interface DescriptionProps extends TextProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
main?: boolean;
|
||||||
|
noBackground?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Description = ({ children }: DescriptionProps) => {
|
export const Description = ({ noBackground, main, children }: DescriptionProps) => {
|
||||||
return <div className="description">{children}</div>;
|
return (
|
||||||
|
<div
|
||||||
|
className={noBackground ? "description no-background" : "description"}
|
||||||
|
style={main ? {margin: "14px"} : {}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
8
src/components/DoubleSpace.tsx
Normal file
8
src/components/DoubleSpace.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export function DoubleSpace() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { GenericProps } from "../types";
|
||||||
|
|
||||||
export interface TextLink {
|
export interface TextLink {
|
||||||
text: string;
|
text: string;
|
||||||
|
@ -6,23 +8,23 @@ export interface TextLink {
|
||||||
target?: React.HTMLAttributeAnchorTarget;
|
target?: React.HTMLAttributeAnchorTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TextLinks {
|
export interface TextLinks extends GenericProps {
|
||||||
links: TextLink[];
|
links: TextLink[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Header = ({ links }: TextLinks) => {
|
export const Header = ({ fadeIn, links }: TextLinks) => {
|
||||||
return (
|
return (
|
||||||
<div className="header">
|
<div className={fadeIn ? "header fade-in" : "header"}>
|
||||||
{links.map((link) => {
|
{links && links.map((link) => {
|
||||||
return (
|
return (
|
||||||
<a
|
<Link
|
||||||
className="header-link"
|
className="header-link"
|
||||||
key={link.text}
|
key={link.text}
|
||||||
href={link.link}
|
to={link.link}
|
||||||
target={link.target ?? "_self"}
|
target={link.target ?? "_self"}
|
||||||
>
|
>
|
||||||
{link.text}
|
{link.text}
|
||||||
</a>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,8 +2,15 @@ import React, { ReactNode } from "react";
|
||||||
|
|
||||||
export interface MessageProps {
|
export interface MessageProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Message = ({ children }: MessageProps) => {
|
export const Message = ({ className, children }: MessageProps) => {
|
||||||
return <div className="message">{children}</div>;
|
return (
|
||||||
|
<div
|
||||||
|
className={"message zoom-in" + " " + className}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,31 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
import { TextProps } from "../types";
|
||||||
|
|
||||||
export interface TitleProps {
|
export interface TitleProps extends TextProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
noBackground?: boolean;
|
||||||
glow?: boolean;
|
glow?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Title = ({ children, glow }: TitleProps) => {
|
export const Title = ({ children, noBackground, glow }: TitleProps) => {
|
||||||
|
const determineClasses = () => {
|
||||||
|
let result = "title";
|
||||||
|
|
||||||
|
if (noBackground) {
|
||||||
|
result += " no-background"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glow) {
|
||||||
|
result += " glow";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className={glow ? "title glow" : "title"}>{children}</p>
|
<p className={determineClasses()}>{children}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,3 +3,4 @@ export * from "./Title";
|
||||||
export * from "./Description";
|
export * from "./Description";
|
||||||
export * from "./ButtonRow";
|
export * from "./ButtonRow";
|
||||||
export * from "./Message";
|
export * from "./Message";
|
||||||
|
export * from "./DoubleSpace";
|
||||||
|
|
|
@ -24,6 +24,17 @@ body {
|
||||||
animation: zoom-in 0.3s;
|
animation: zoom-in 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.at:link, .at:visited {
|
||||||
|
color: var(--main);
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: opacity 0.15s, transform 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.at:hover {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -107,6 +118,10 @@ body {
|
||||||
color 0.6s,
|
color 0.6s,
|
||||||
text-shadow 0.6s,
|
text-shadow 0.6s,
|
||||||
transform 0.6s;
|
transform 0.6s;
|
||||||
|
padding: 42px 96px;
|
||||||
|
background-color: var(--primary);
|
||||||
|
border-radius: 17px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glow:hover {
|
.glow:hover {
|
||||||
|
@ -116,12 +131,17 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
margin: 15px 0;
|
display: inline-block;
|
||||||
|
max-width: 50%;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
padding: 24px 48px;
|
||||||
|
background-color: var(--primary);
|
||||||
|
border-radius: 17px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.social-link-area {
|
.social-link-area {
|
||||||
margin: 25px 0;
|
margin: 10px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,12 +174,20 @@ body {
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
width: 75%;
|
width: 75%;
|
||||||
margin: 25px auto;
|
margin: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 17px;
|
align-content: center;
|
||||||
background-color: var(--primary);
|
}
|
||||||
border-radius: 17px;
|
|
||||||
box-shadow: var(--shadow);
|
.no-background {
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
text-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-font {
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
|
|
|
@ -85,3 +85,11 @@
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.zoom-in {
|
||||||
|
animation: zoom-in 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in {
|
||||||
|
animation: fade-in 0.3s;
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
|
45
src/pages/About.tsx
Normal file
45
src/pages/About.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Description, Header, Message, Title, DoubleSpace, ButtonRow, Buttons, TextLink } from '../components'
|
||||||
|
|
||||||
|
function About() {
|
||||||
|
const links: TextLink[] = [
|
||||||
|
{
|
||||||
|
text: "Back",
|
||||||
|
link: "/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const buttons: Buttons[] = [
|
||||||
|
{
|
||||||
|
name: "Self-Hosting",
|
||||||
|
link: "/about/selfhosting",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header links={links} />
|
||||||
|
|
||||||
|
<Message>
|
||||||
|
<Title>About</Title>
|
||||||
|
|
||||||
|
<Description>
|
||||||
|
<p>
|
||||||
|
You've probably met me online and checked out my website, I know you IRL, or you're interested in me.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In the wise words of Lester Crest, "don't dawdle!" and read on.
|
||||||
|
</p>
|
||||||
|
</Description>
|
||||||
|
</Message>
|
||||||
|
|
||||||
|
<DoubleSpace />
|
||||||
|
|
||||||
|
<ButtonRow buttons={buttons} fadeIn />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default About;
|
|
@ -1,4 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
Buttons,
|
Buttons,
|
||||||
ButtonRow,
|
ButtonRow,
|
||||||
|
@ -9,7 +10,7 @@ import {
|
||||||
Description,
|
Description,
|
||||||
} from "../components";
|
} from "../components";
|
||||||
|
|
||||||
export function Main() {
|
function Main() {
|
||||||
const links: TextLink[] = [
|
const links: TextLink[] = [
|
||||||
{
|
{
|
||||||
text: "pHosting",
|
text: "pHosting",
|
||||||
|
@ -27,6 +28,10 @@ export function Main() {
|
||||||
text: "FreeTube Web",
|
text: "FreeTube Web",
|
||||||
link: "https://tube.povario.com",
|
link: "https://tube.povario.com",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: "About",
|
||||||
|
link: "/about"
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const buttons: Buttons[] = [
|
const buttons: Buttons[] = [
|
||||||
|
@ -53,6 +58,8 @@ export function Main() {
|
||||||
];
|
];
|
||||||
|
|
||||||
const [timesClicked, changeTimesClicked] = useState(0);
|
const [timesClicked, changeTimesClicked] = useState(0);
|
||||||
|
const [displayed, changeDisplay] = useState("");
|
||||||
|
|
||||||
const handlePictureClick = () => {
|
const handlePictureClick = () => {
|
||||||
changeTimesClicked((timesClicked) => timesClicked + 1);
|
changeTimesClicked((timesClicked) => timesClicked + 1);
|
||||||
};
|
};
|
||||||
|
@ -62,45 +69,51 @@ export function Main() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header links={links} />
|
<div id="loader" style={{display: displayed === "" ? "none" : "flex"}}>
|
||||||
|
<img src="/three-dots.svg" height={48} style={{display: "block", marginLeft: "auto", marginRight: "auto", marginTop: "20%", width: "50%"}}/>
|
||||||
<div className="easter-egg-box">
|
|
||||||
<img
|
|
||||||
className={
|
|
||||||
checkTimesClicked()
|
|
||||||
? "profile-picture slide-right"
|
|
||||||
: "profile-picture"
|
|
||||||
}
|
|
||||||
onClick={handlePictureClick}
|
|
||||||
height={130}
|
|
||||||
src="/potato.png"
|
|
||||||
/>
|
|
||||||
<p className={checkTimesClicked() ? "easter-egg" : "easter-egg hidden"}>
|
|
||||||
Stop that!
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="head">
|
<div id="main" style={{display: displayed}}>
|
||||||
<Title glow>@powermaker450</Title>
|
<Header links={links} fadeIn />
|
||||||
|
|
||||||
<Description>Professional Linux Enjoyer + Self Hosts a Lot</Description>
|
<div className="easter-egg-box">
|
||||||
</div>
|
<img
|
||||||
|
className={
|
||||||
<ButtonRow buttons={buttons} />
|
checkTimesClicked()
|
||||||
|
? "profile-picture slide-right"
|
||||||
<Message>
|
: "profile-picture"
|
||||||
<Title>Welcome!</Title>
|
}
|
||||||
</Message>
|
onClick={handlePictureClick}
|
||||||
|
height={130}
|
||||||
<Message>
|
src="/potato.png"
|
||||||
<Description>
|
/>
|
||||||
<p>You've reached my homepage.</p>
|
<p className={checkTimesClicked() ? "easter-egg" : "easter-egg hidden"}>
|
||||||
<p>
|
Stop that!
|
||||||
If you were looking for something, it's probably one of the links
|
|
||||||
above.
|
|
||||||
</p>
|
</p>
|
||||||
</Description>
|
</div>
|
||||||
</Message>
|
|
||||||
|
<div className="head zoom-in">
|
||||||
|
<Title noBackground glow>@powermaker450</Title>
|
||||||
|
|
||||||
|
<Description noBackground main>Professional Linux Enjoyer + Self Hosts a Lot</Description>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ButtonRow buttons={buttons} fadeIn />
|
||||||
|
|
||||||
|
<Message>
|
||||||
|
<Title>Welcome!</Title>
|
||||||
|
|
||||||
|
<Description>
|
||||||
|
<p>You've reached my homepage.</p>
|
||||||
|
<p>
|
||||||
|
If you were looking for something, it's probably one of the links
|
||||||
|
above.
|
||||||
|
</p>
|
||||||
|
</Description>
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Main;
|
||||||
|
|
76
src/pages/SelfHosting.tsx
Normal file
76
src/pages/SelfHosting.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Description, DoubleSpace, Header, Message, TextLink, Title } from '../components'
|
||||||
|
|
||||||
|
function SelfHosting() {
|
||||||
|
const links: TextLink[] = [
|
||||||
|
{
|
||||||
|
text: "Back",
|
||||||
|
link: "/about"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header links={links} />
|
||||||
|
|
||||||
|
<Message>
|
||||||
|
<Title>Self-Hosting</Title>
|
||||||
|
|
||||||
|
<Description>
|
||||||
|
<p>
|
||||||
|
Getting the more obvious stuff out of the way, I like self-hosting. I started my self-hosting journey back in
|
||||||
|
2021, when all I was was fed up with Netflix not having all the shows I wanted to watch.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Eventually, that grew into something much bigger, and suddenly this machine I got from a dumpster was my key
|
||||||
|
to a privacy suite.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<img style={{display: "block", margin: "auto", width: "350px"}} src="/public/server.png" />
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Yep. Did I say it was from a dumpster? It's beautiful in it's own way.
|
||||||
|
</p>
|
||||||
|
</Description>
|
||||||
|
|
||||||
|
<DoubleSpace />
|
||||||
|
|
||||||
|
<Description>
|
||||||
|
<p>
|
||||||
|
Managing your own server is a learning experience in itself, and with time I learned the
|
||||||
|
cold, hard, basic skills of being a Linux sysadmin.
|
||||||
|
Not to say it hasn't paid off.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
I've learned my way around the terminal, and can comfortably surf through it without worry,
|
||||||
|
and if I do come across something I don't know, I'll figure out how to do it.
|
||||||
|
Not to mention the money saved not paying for cloud services. Money's not exactly something that get's thrown at me in my life.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Instead of the Google Suite, I use <Link className="at" to="https://nextcloud.com" target="_blank">Nextcloud</Link>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Instead of Spotify, I use <Link className="at" to="https://navidrome.org" target="_blank">Navidrome</Link>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Instead of paying for game hosting, I use <Link className="at" to="https://www.pufferpanel.com">Pufferpanel</Link>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
And so on. Between becoming comfortable with the ways of Linux, and saving my family money in the long run, this server
|
||||||
|
has been a worthwhile investment of my time and effort.
|
||||||
|
</p>
|
||||||
|
</Description>
|
||||||
|
</Message>
|
||||||
|
|
||||||
|
<DoubleSpace />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelfHosting;
|
|
@ -1 +0,0 @@
|
||||||
export * from "./Main";
|
|
12
src/types.ts
Normal file
12
src/types.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export interface GenericProps {
|
||||||
|
className?: string;
|
||||||
|
fadeIn?: boolean;
|
||||||
|
zoomIn?: boolean;
|
||||||
|
zoomInLarge?: boolean;
|
||||||
|
slide?: "left" | "right";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextProps extends GenericProps {
|
||||||
|
noBackground?: boolean;
|
||||||
|
glow?: boolean;
|
||||||
|
}
|
Loading…
Reference in a new issue