diff --git a/src/templates/components/Background/Background.sass b/src/templates/components/Background/Background.sass
new file mode 100644
index 0000000..1e0b5bc
--- /dev/null
+++ b/src/templates/components/Background/Background.sass
@@ -0,0 +1 @@
+@use '/src/styles/classes.sass'
diff --git a/src/templates/components/Background/Background.tsx b/src/templates/components/Background/Background.tsx
new file mode 100644
index 0000000..e33e37d
--- /dev/null
+++ b/src/templates/components/Background/Background.tsx
@@ -0,0 +1,50 @@
+import './Background.sass'
+import { Show, createSignal } from 'solid-js'
+import fs from 'fs'
+import webpPath from '../../../templates/images/background.webp'
+import avifPath from '../../../templates/images/background.avif'
+import noBackground from '../../../templates/images/no-background.webp'
+
+interface Props {
+ image?: boolean
+ color?: string
+}
+
+let [imageLoaded, setImageLoaded] = createSignal(false)
+
+const checkBackground = () => {
+ if (!fs.existsSync(avifPath.src) && !fs.existsSync(webpPath.src)) {
+ setImageLoaded(true)
+ } else {
+ setImageLoaded(false)
+ }
+}
+
+export default (props: Props) => {
+ checkBackground()
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/templates/components/Button/Button.sass b/src/templates/components/Button/Button.sass
new file mode 100644
index 0000000..329ba1d
--- /dev/null
+++ b/src/templates/components/Button/Button.sass
@@ -0,0 +1,223 @@
+@use '/src/styles/variables.sass' as vars
+@use '/src/styles/fonts.sass' as fonts
+@use 'sass:color'
+
+$bulmaPrimary: rgb(0, 235, 199)
+$bulmaPrimaryText: rgb(0, 31, 26)
+$bulmaLink: rgb(92, 111, 255)
+$bulmaLinkText: rgb(245, 246, 255)
+$bulmaInfo: rgb(128, 217, 255)
+$bulmaInfoText: rgb(0, 36, 51)
+$bulmaSuccess: rgb(91, 205, 154)
+$bulmaSuccessText: rgb(10, 31, 21)
+$bulmaWarning: rgb(255, 191, 41)
+$bulmaWarningText: rgb(41, 29, 0)
+$bulmaDanger: rgb(255, 128, 153)
+$bulmaDangerText: rgb(26, 0, 5)
+$bulmaLight: rgb(255, 255, 255)
+$bulmaLightText: rgb(46, 51, 61)
+$bulmaDark: rgb(57, 63, 76)
+$bulmaDarkText: rgb(243, 244, 246)
+$bulmaText: rgb(31, 34, 41)
+$bulmaTextText: rgb(235, 236, 240)
+$bulmaGhost: rgba(0,0,0,0)
+$bulmaGhostText: rgb(66, 88, 255)
+
+$bootstrapTextLight: rgb(255, 255, 253)
+$bootstrapTextDark: rgb(0, 0, 2)
+$bootstrapTextLink: rgb(139, 185, 254)
+$bootstrapPrimary: rgb(13, 110, 253)
+$bootstrapSecondary: rgb(92, 99, 106)
+$bootstrapSuccess: rgb(21, 115, 71)
+$bootstrapDanger: rgb(187, 45, 59)
+$bootstrapWarning: rgb(255, 202, 44)
+$bootstrapInfo: rgb(49, 210, 242)
+$bootstrapLight: rgb(211, 212, 213)
+$bootstrapDark: rgb(33, 37, 41)
+
+.button
+ background-color: vars.$primaryColor
+ border: none
+ color: white
+ padding: 0.5rem 1.25rem
+ text-align: center
+ text-decoration: none
+ display: inline-block
+ font-size: 1rem
+ font-weight: 500
+ cursor: pointer
+ transition: all 0.2s ease-out
+
+ &:hover
+ background-color: color.adjust(vars.$primaryColor, $blackness: 20%)
+
+ &:active
+ transform: scale(0.95)
+
+.bu-primary
+ @extend .button
+ font-family: fonts.$Inter
+ background-color: $bulmaPrimary
+ color: $bulmaPrimaryText
+ border: none
+ font-size: 1rem
+ border-radius: 0.375rem
+ font-weight: 500
+ padding: 0.5rem 1.25rem
+ height: 2.5rem
+
+ &:hover
+ background-color: color.adjust($bulmaPrimary, $lightness: 10%)
+
+.bu-link
+ @extend .bu-primary
+ background-color: $bulmaLink
+ color: $bulmaLinkText
+
+ &:hover
+ background-color: color.adjust($bulmaLink, $lightness: 5%)
+
+.bu-info
+ @extend .bu-primary
+ background-color: $bulmaInfo
+ color: $bulmaInfoText
+
+ &:hover
+ background-color: color.adjust($bulmaInfo, $lightness: 5%)
+
+.bu-success
+ @extend .bu-primary
+ background-color: $bulmaSuccess
+ color: $bulmaSuccessText
+
+ &:hover
+ background-color: color.adjust($bulmaSuccess, $lightness: 5%)
+
+.bu-warning
+ @extend .bu-primary
+ background-color: $bulmaWarning
+ color: $bulmaWarningText
+
+ &:hover
+ background-color: color.adjust($bulmaWarning, $lightness: 5%)
+
+.bu-danger
+ @extend .bu-primary
+ background-color: $bulmaDanger
+ color: $bulmaDangerText
+
+ &:hover
+ background-color: color.adjust($bulmaDanger, $lightness: 5%)
+
+.bu-light
+ @extend .bu-primary
+ background-color: $bulmaLight
+ color: $bulmaLightText
+
+ &:hover
+ background-color: color.adjust($bulmaLight, $lightness: 5%)
+
+.bu-dark
+ @extend .bu-primary
+ background-color: $bulmaDark
+ color: $bulmaDarkText
+
+ &:hover
+ background-color: color.adjust($bulmaDark, $lightness: 5%)
+
+.bu-text
+ @extend .bu-primary
+ background-color: rgba(0,0,0,0)
+ color: $bulmaTextText
+ text-decoration: underline
+
+ &:hover
+ background-color: hsl(221,14%,14%)
+
+.bu-ghost
+ @extend .bu-primary
+ background-color: $bulmaGhost
+ color: $bulmaGhostText
+
+ &:hover
+ background-color: transparent
+ text-decoration: underline
+
+.bo-primary
+ @extend .button
+ font-family: 'Segoe UI', fonts.$Roboto
+ background-color: $bootstrapPrimary
+ color: $bootstrapTextLight
+ border: none
+ font-size: 1rem
+ border-radius: 0.375rem
+ font-weight: 400
+ padding: 0.5rem 1.25rem
+ height: 2.5rem
+ margin: 0.25rem 0.125rem
+
+ &:hover
+ background-color: color.adjust($bootstrapPrimary, $blackness: 10%)
+
+.bo-secondary
+ @extend .bo-primary
+ background-color: $bootstrapSecondary
+
+ &:hover
+ background-color: color.adjust($bootstrapSecondary, $blackness: 10%)
+
+.bo-success
+ @extend .bo-primary
+ background-color: $bootstrapSuccess
+
+ &:hover
+ background-color: color.adjust($bootstrapSuccess, $blackness: 10%)
+
+.bo-danger
+ @extend .bo-primary
+ background-color: $bootstrapDanger
+
+ &:hover
+ background-color: color.adjust($bootstrapDanger, $blackness: 10%)
+
+.bo-warning
+ @extend .bo-primary
+ background-color: $bootstrapWarning
+ color: $bootstrapTextDark
+
+ &:hover
+ background-color: color.adjust($bootstrapWarning, $lightness: 5%)
+
+.bo-info
+ @extend .bo-primary
+ background-color: $bootstrapInfo
+ color: $bootstrapTextDark
+
+ &:hover
+ background-color: color.adjust($bootstrapInfo, $lightness: 5%)
+
+.bo-light
+ @extend .bo-primary
+ background-color: $bootstrapLight
+ color: $bootstrapTextDark
+
+ &:hover
+ background-color: color.adjust($bootstrapLight, $blackness: 10%)
+
+.bo-dark
+ @extend .bo-primary
+ background-color: $bootstrapDark
+ // color: $bootstrapTextDark
+
+ &:hover
+ background-color: color.adjust($bootstrapDark, $lightness: 10%)
+
+.bo-link
+ @extend .bo-primary
+ background-color: transparent
+ color: $bootstrapTextLink
+ text-decoration: underline
+
+ &:hover
+ color: color.adjust($bootstrapTextLink, $lightness: 5%)
+ background-color: transparent
diff --git a/src/templates/components/Button/Button.tsx b/src/templates/components/Button/Button.tsx
new file mode 100644
index 0000000..61b7e7a
--- /dev/null
+++ b/src/templates/components/Button/Button.tsx
@@ -0,0 +1,65 @@
+import './Button.sass'
+import { Show, Switch, Match } from 'solid-js'
+
+interface Props {
+ label?: string
+ to?: string
+ onClick?: () => void
+ edges?: 'curved' | 'rounded' | 'flat'
+ design?: 'bu-primary' | 'bu-link' | 'bu-info' | 'bu-success' | 'bu-warning' | 'bu-danger' | 'bu-dark' | 'bu-light' | 'bu-text' | 'bu-ghost' | 'bo-primary' | 'bo-secondary' | 'bo-success' | 'bo-danger' | 'bo-warning' | 'bo-info' | 'bo-light' | 'bo-dark' | 'bo-link'
+}
+
+const getBorderRadius = (edge: Props['edges']) => {
+ switch (edge) {
+ case 'curved':
+ return 'border-radius: 6px'
+ case 'rounded':
+ return 'border-radius: 32px'
+ case 'flat':
+ return 'border-radius: 0'
+ default:
+ return 'border-radius: 0'
+ }
+}
+
+export default (props: Props) => {
+ const borderRadius = getBorderRadius(props.edges)
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/templates/components/Image/Image.tsx b/src/templates/components/Image/Image.tsx
new file mode 100644
index 0000000..bfb3b19
--- /dev/null
+++ b/src/templates/components/Image/Image.tsx
@@ -0,0 +1,36 @@
+import sharp from 'sharp'
+import fs from 'fs'
+
+interface Props {
+ src: string
+ size?: number
+ alt?: string
+}
+
+const convertImage = async (props: Props) => {
+ const avifOutputPath = `src/assets/optimized/${props.src.split('.').slice(0, -1).join('.')}.avif`
+ const webpOutputPath = `src/assets/optimized/${props.src.split('.').slice(0, -1).join('.')}.webp`
+
+ if (!fs.existsSync(webpOutputPath) || !fs.existsSync(avifOutputPath)) {
+ const avifBuffer = await sharp(`src/assets/images/${props.src}`).avif({ quality: 60 }).resize(props.size).toBuffer()
+ await sharp(avifBuffer).toFile(avifOutputPath)
+
+ const webpBuffer = await sharp(`src/assets/images/${props.src}`).webp({ quality: 75 }).resize(props.size).toBuffer()
+ await sharp(webpBuffer).toFile(webpOutputPath)
+ }
+}
+
+export default (props: Props) => {
+ const imageSrc = `src/assets/optimized/${props.src.split('.').slice(0, -1).join('.')}.webp`
+ convertImage(props)
+
+ return (
+ <>
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/templates/components/Link/Link.tsx b/src/templates/components/Link/Link.tsx
new file mode 100644
index 0000000..4ccf49f
--- /dev/null
+++ b/src/templates/components/Link/Link.tsx
@@ -0,0 +1,14 @@
+interface Props {
+ to: string
+ children?: any
+}
+
+export default (props: Props) => {
+ return (
+ <>
+
+ {props.children}
+
+ >
+ )
+}
diff --git a/src/templates/components/Logo/Logo.tsx b/src/templates/components/Logo/Logo.tsx
new file mode 100644
index 0000000..d293b1a
--- /dev/null
+++ b/src/templates/components/Logo/Logo.tsx
@@ -0,0 +1,19 @@
+import webpPath from '../../../templates/images/logo.webp'
+import avifPath from '../../../templates/images/logo.avif'
+
+interface Props {
+ size?: number
+ alt?: string
+}
+
+export default (props: Props) => {
+ return (
+ <>
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/templates/components/Optimizer/OptimizeBackground.tsx b/src/templates/components/Optimizer/OptimizeBackground.tsx
new file mode 100644
index 0000000..2c004f5
--- /dev/null
+++ b/src/templates/components/Optimizer/OptimizeBackground.tsx
@@ -0,0 +1,17 @@
+import sharp from 'sharp'
+
+const convertBackground = async () => {
+ const inputSrc = 'src/assets/images/background.png'
+ const webpOutput = 'src/templates/images/background.webp'
+ const avifOutput = 'src/templates/images/background.avif'
+
+ const avifBuffer = await sharp(inputSrc).avif({ quality: 60 }).resize(1920).toBuffer()
+ await sharp(avifBuffer).toFile(avifOutput)
+
+ const webpBuffer = await sharp(inputSrc).webp({ quality: 75 }).resize(1920).toBuffer()
+ await sharp(webpBuffer).toFile(webpOutput)
+}
+
+export default () => {
+ convertBackground()
+}
diff --git a/src/templates/components/Optimizer/OptimizeLogo.tsx b/src/templates/components/Optimizer/OptimizeLogo.tsx
new file mode 100644
index 0000000..fb39b0d
--- /dev/null
+++ b/src/templates/components/Optimizer/OptimizeLogo.tsx
@@ -0,0 +1,22 @@
+import sharp from 'sharp'
+
+interface Props {
+ size?: number
+ alt?: string
+}
+
+const convertLogo = async (props: Props) => {
+ const inputSrc = 'src/assets/images/logo.png'
+ const webpImage = 'src/templates/images/logo.webp'
+ const avifImage = 'src/templates/images/logo.avif'
+
+ const avifBuffer = await sharp(inputSrc).avif({ quality: 60 }).resize(props.size).toBuffer()
+ await sharp(avifBuffer).toFile(avifImage)
+
+ const webpBuffer = await sharp(inputSrc).webp({ quality: 75 }).resize(props.size).toBuffer()
+ await sharp(webpBuffer).toFile(webpImage)
+}
+
+export default (props: Props) => {
+ convertLogo(props)
+}
diff --git a/src/templates/components/Page/Page.sass b/src/templates/components/Page/Page.sass
new file mode 100644
index 0000000..3d97ca8
--- /dev/null
+++ b/src/templates/components/Page/Page.sass
@@ -0,0 +1,15 @@
+.page
+ margin: 2rem
+ height: auto
+
+.column
+ @extend .page
+ display: flex
+ flex-direction: column
+ text-align: center
+ justify-content: center
+ align-items: center
+
+.row
+ @extend .column
+ flex-direction: row
diff --git a/src/templates/components/Page/Page.tsx b/src/templates/components/Page/Page.tsx
new file mode 100644
index 0000000..d16977f
--- /dev/null
+++ b/src/templates/components/Page/Page.tsx
@@ -0,0 +1,20 @@
+import './Page.sass'
+import { Show, Switch, Match } from 'solid-js'
+
+interface Props {
+ children?: any
+ alignment?: 'row' | 'column'
+}
+
+export default (props: Props) => {
+ return (
+ <>
+
+ {props.children}
+
+
+ {props.children}
+
+ >
+ )
+}
diff --git a/src/templates/images/no-background.webp b/src/templates/images/no-background.webp
new file mode 100644
index 0000000..87c7f4c
Binary files /dev/null and b/src/templates/images/no-background.webp differ