feat: initial commit

This commit is contained in:
Julian Lobbes 2023-03-13 19:54:16 +01:00
commit 09db9d9a2c
99 changed files with 875 additions and 0 deletions

14
.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
# Backend config file
/config.py
# Local environments
/.venv/
/node_modules/
# Cache files and directories
*.pyc
__pycache__/
/.parcel-cache/
# Bundled files
/dist/

7
.postcssrc Normal file
View file

@ -0,0 +1,7 @@
{
  "plugins": {
    "postcss-import": {},
    "tailwindcss/nesting": {},
    "tailwindcss": {},
  }
}

29
Caddyfile Normal file
View file

@ -0,0 +1,29 @@
:3000 {
encode zstd gzip
@staticfiles {
method GET
path /static/*
}
handle @staticfiles {
file_server {
root /app/public/
}
}
handle_path /api/* {
reverse_proxy * portfolio-backend:3001
}
handle * {
root * /app/dist/
file_server
try_files {path} /
}
log {
output stderr
format console
}
}

41
README.md Normal file
View file

@ -0,0 +1,41 @@
# TODO
In `index.html`: make parcel replace the hard-coded base URL (in the meta tags).
# About
This is a template project for a webapp using the following tech stack:
- **Frontend**:
- Vue.js
- TailwindCSS
- Axios
- **Backend**:
- FastAPI (python)
- **Other**:
- Caddy (webserver/reverse proxy)
- Parcel (bundler)
# Customization
A number of default template files and assets for the frontend are already present in the repository.
These include essential assets like a set of fonts, and a default favicon.
## Favicon
The standards for the correct favicons are annoying, many browser vendors use a different standard.
A quick and easy way to create a set of favicons with broad compatibility is to create your favicon as an `.svg`-file,
and then use [realfavicongenerator.net](https://realfavicongenerator.net/) to create a set of relevant files and HTML
for your favicon.
Place the generated icons into `/frontend/assets/images/common/` and be sure to adjust the generated HTML to point to
this path.
## OG Image
The OG image is served directly by Caddy, and thus needs to be placed into `/public/static/images/common/`,
ideally as a `.webp`-file.
The image's dimensions should be 1200 x 630 pixels, as according to the
[OGP standard](https://developers.facebook.com/docs/sharing/webmasters/images/).

25
backend.Dockerfile Normal file
View file

@ -0,0 +1,25 @@
# syntax=docker/dockerfile:1
FROM python:3
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
ENV CUSTOM_USERNAME=backend
ENV CUSTOM_GROUPNAME=backend
RUN groupadd --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
useradd --uid ${CUSTOM_UID:-1000} --gid ${CUSTOM_GID:-1000} --create-home --shell /bin/bash ${CUSTOM_USERNAME} && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app
# Copy source files
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} requirements.txt /app/
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} backend /app/backend/
# Install dependencies
RUN pip install -r requirements.txt
# Run ASGI server
EXPOSE 3001/tcp
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}
ENTRYPOINT ["uvicorn", "backend.main:app", "--root-path", "/api", "--host", "0.0.0.0", "--port", "3001"]

0
backend/__init__.py Normal file
View file

8
backend/main.py Normal file
View file

@ -0,0 +1,8 @@
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/hello/")
async def hello() -> str:
return "Hello World!"

31
docker-compose.yml Normal file
View file

@ -0,0 +1,31 @@
---
version: "3"
services:
portfolio-frontend:
container_name: portfolio-frontend
restart: unless-stopped
build:
context: .
dockerfile: ./frontend.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000
environment:
TZ: Europe/Berlin
ports:
- "8000:3000"
portfolio-backend:
container_name: portfolio-backend
restart: unless-stopped
build:
context: .
dockerfile: ./backend.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000
expose:
- "3001"
...

36
frontend.Dockerfile Normal file
View file

@ -0,0 +1,36 @@
# syntax=docker/dockerfile:1
FROM debian:latest
# Install packages
ENV DEBIAN_FRONTEND=noninteractive
#RUN apt update && apt install -y python3-pip python3-venv && rm -rf /var/lib/apt/lists/*
RUN apt update && apt install -y curl && \
curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && apt-get install -y nodejs && \
apt install -y debian-keyring debian-archive-keyring apt-transport-https && \
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg && \
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' > /etc/apt/sources.list.d/caddy-stable.list && \
apt update && apt install -y caddy && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
ENV CUSTOM_USERNAME=webserver
ENV CUSTOM_GROUPNAME=webserver
RUN groupadd --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
useradd --uid ${CUSTOM_UID:-1000} --gid ${CUSTOM_GID:-1000} --create-home --shell /bin/bash ${CUSTOM_USERNAME} && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app
# Copy source files
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} package.json package-lock.json .postcssrc postcss.config.js tailwind.config.js Caddyfile /app/
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} frontend /app/frontend/
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} public /app/public/
# Install dependencies
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}
RUN npm install
RUN npx parcel build frontend/src/html/index.html
ENTRYPOINT ["caddy", "run", "--config", "/app/Caddyfile"]

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.

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.

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.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,4 @@
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#00aaff" d="M20.501 6.028V6h-.02A10.28 10.28 0 0 0 4.519 6H4.5v.028a10.262 10.262 0 0 0 0 12.944V19h.02a10.28 10.28 0 0 0 15.962 0h.021v-.028a10.262 10.262 0 0 0 0-12.944zM13 6V3.272A4.533 4.533 0 0 1 15.54 6zm2.935 1a16.827 16.827 0 0 1 .853 5H13V7zM12 3.272V6H9.46A4.533 4.533 0 0 1 12 3.272zM12 7v5H8.212a16.827 16.827 0 0 1 .853-5zm-4.787 5H3.226a9.234 9.234 0 0 1 1.792-5h2.984a17.952 17.952 0 0 0-.79 5zm0 1a17.952 17.952 0 0 0 .789 5H5.018a9.234 9.234 0 0 1-1.792-5zm1 0H12v5H9.065a16.827 16.827 0 0 1-.853-5zM12 19v2.728A4.533 4.533 0 0 1 9.46 19zm1 2.728V19h2.54A4.533 4.533 0 0 1 13 21.728zM13 18v-5h3.788a16.827 16.827 0 0 1-.853 5zm4.787-5h3.987a9.234 9.234 0 0 1-1.792 5h-2.984a17.952 17.952 0 0 0 .79-5zm0-1a17.952 17.952 0 0 0-.789-5h2.984a9.234 9.234 0 0 1 1.792 5zm1.352-6h-2.501a8.524 8.524 0 0 0-1.441-2.398A9.306 9.306 0 0 1 19.139 6zM9.803 3.602A8.524 8.524 0 0 0 8.363 6H5.86a9.306 9.306 0 0 1 3.942-2.398zM5.861 19h2.501a8.524 8.524 0 0 0 1.441 2.398A9.306 9.306 0 0 1 5.861 19zm9.336 2.398A8.524 8.524 0 0 0 16.637 19h2.502a9.306 9.306 0 0 1-3.942 2.398z"/>
<path fill="none" d="M0 0h24v24H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,71 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M3468 6355 c-2 -1 -37 -5 -78 -9 -114 -9 -170 -16 -230 -27 -30 -6
-68 -13 -85 -15 -16 -3 -48 -10 -70 -15 -22 -5 -51 -12 -65 -15 -53 -11 -110
-27 -235 -69 -451 -149 -844 -392 -1180 -729 -111 -111 -248 -272 -305 -356
-8 -12 -33 -47 -56 -78 -119 -167 -211 -346 -339 -657 -28 -66 -88 -273 -111
-380 -12 -52 -25 -123 -30 -155 -2 -19 -6 -44 -9 -55 -5 -24 -13 -99 -23 -220
-9 -102 -8 -400 2 -490 10 -89 17 -146 22 -180 3 -16 7 -43 10 -60 2 -16 14
-73 25 -125 96 -441 284 -841 568 -1210 88 -114 115 -145 256 -286 360 -361
812 -625 1308 -764 43 -12 86 -23 95 -25 9 -1 35 -8 57 -13 35 -10 135 -28
255 -46 90 -14 222 -21 405 -21 178 1 370 11 386 21 3 1 28 6 56 9 49 5 74 9
123 20 14 3 34 7 45 9 11 3 40 9 65 15 25 7 52 13 60 15 298 73 616 213 890
392 206 134 346 251 521 432 400 413 661 902 783 1462 24 111 27 132 41 250 4
30 9 66 12 80 9 45 11 483 3 550 -5 36 -11 90 -13 120 -3 30 -18 116 -32 190
-92 475 -297 921 -592 1293 -32 40 -62 78 -68 86 -42 55 -330 334 -414 400
-233 185 -492 337 -741 436 -52 20 -104 41 -115 45 -138 57 -479 138 -650 156
-33 3 -73 8 -90 10 -34 6 -452 14 -457 9z m32 -710 l0 -395 -370 0 -370 0 24
63 c105 280 338 538 619 686 43 23 82 41 87 41 6 0 10 -151 10 -395z m385 357
c171 -86 381 -272 488 -433 52 -78 135 -241 152 -296 l6 -23 -370 0 -371 0 0
396 0 395 23 -7 c12 -3 45 -18 72 -32z m-1035 -60 c0 -4 -10 -18 -23 -32 -108
-118 -296 -431 -370 -617 l-17 -43 -365 0 -366 0 58 57 c252 246 585 456 938
589 159 61 145 56 145 46z m1803 -71 c321 -129 607 -314 871 -563 l61 -58
-366 0 -366 0 -48 108 c-26 59 -63 133 -81 165 -19 32 -34 60 -34 62 0 11 -98
157 -164 245 -39 52 -74 100 -79 107 -8 13 77 -14 206 -66z m-2323 -921 c0 -4
-9 -36 -19 -71 -16 -52 -47 -170 -67 -249 -20 -84 -73 -358 -78 -405 -4 -33
-9 -69 -11 -80 -7 -30 -13 -83 -20 -155 -4 -36 -8 -76 -10 -89 -6 -33 -18
-264 -19 -338 l-1 -63 -583 0 -582 0 5 63 c7 77 14 135 30 247 18 126 101 422
150 535 7 17 23 55 35 85 49 121 209 408 262 469 9 11 21 28 27 39 10 19 24
20 446 20 239 0 435 -4 435 -8z m1170 -721 l0 -729 -552 2 -553 3 2 65 c1 58
4 109 18 290 6 79 22 210 29 245 2 11 7 43 10 70 12 93 57 309 92 445 20 74
37 140 38 145 10 40 50 163 57 176 8 15 50 17 434 17 l425 -1 0 -728z m1163
677 c29 -81 106 -363 121 -446 3 -14 8 -36 11 -50 24 -104 59 -334 79 -520 6
-53 17 -228 20 -322 l3 -68 -554 0 -553 0 0 728 0 729 428 0 427 0 18 -51z
m1226 -35 c87 -129 182 -300 234 -421 8 -19 22 -51 30 -70 54 -120 124 -361
156 -535 20 -110 25 -151 37 -278 l6 -67 -581 0 c-320 0 -581 3 -582 8 0 4 -2
48 -4 97 -3 94 -14 255 -20 315 -14 125 -36 297 -40 310 -2 8 -7 35 -10 60 -4
25 -11 68 -16 95 -6 28 -12 59 -14 70 -10 66 -78 336 -122 486 -4 15 35 16
432 16 l436 1 58 -87z m-3788 -1669 c1 -4 3 -45 4 -92 6 -148 28 -413 40 -480
2 -14 7 -45 10 -70 4 -39 35 -224 50 -300 10 -52 48 -217 60 -265 14 -51 53
-195 62 -227 5 -17 -20 -18 -429 -18 l-435 0 -60 88 c-229 336 -383 735 -438
1132 -4 25 -8 59 -10 75 -3 17 -7 60 -10 97 l-5 67 580 0 c319 0 580 -3 581
-7z m1399 -722 l0 -730 -429 0 -428 0 -41 138 c-37 122 -77 271 -88 327 -2 11
-13 65 -24 120 -21 110 -36 196 -45 265 -3 25 -8 52 -10 60 -2 8 -7 48 -10 88
-4 41 -8 82 -10 93 -2 10 -7 76 -11 146 -10 187 -11 207 -7 215 2 4 251 8 553
8 l550 0 0 -730z m1394 652 c-5 -122 -14 -265 -18 -292 -3 -14 -7 -56 -11 -95
-3 -38 -8 -77 -10 -85 -2 -8 -7 -42 -10 -75 -5 -48 -55 -324 -71 -385 -26
-106 -56 -221 -64 -245 -5 -16 -21 -69 -35 -117 l-26 -88 -430 0 -429 0 0 730
0 729 554 0 553 0 -3 -77z m1452 6 c-8 -99 -43 -329 -55 -361 -6 -15 -9 -27
-7 -27 5 0 -24 -109 -57 -215 -75 -243 -217 -529 -365 -737 l-34 -48 -435 0
c-409 0 -434 1 -429 18 2 9 14 49 25 87 23 82 74 286 90 365 20 97 21 104 26
140 3 19 8 44 10 56 10 45 34 224 41 294 3 41 8 83 10 93 5 27 19 256 23 384
l1 22 581 0 580 0 -5 -71z m-3870 -1767 c84 -187 210 -397 324 -539 28 -34 44
-62 37 -62 -8 0 -45 11 -83 24 -291 102 -580 263 -807 449 -102 84 -215 188
-221 202 -3 10 69 13 354 13 l358 0 38 -87z m1024 -307 c0 -263 -3 -394 -10
-394 -20 0 -135 62 -220 119 -113 74 -280 242 -354 355 -53 81 -134 239 -150
292 l-6 21 370 1 370 0 0 -394z m1024 372 c-19 -63 -85 -193 -141 -278 -119
-181 -295 -343 -482 -441 -49 -26 -93 -47 -100 -47 -8 0 -11 108 -11 394 l0
394 370 0 371 -1 -7 -21z m1046 15 c0 -10 -152 -149 -236 -216 -198 -160 -505
-334 -739 -421 -135 -50 -157 -56 -148 -41 8 14 75 100 84 107 3 3 40 57 81
120 81 123 154 257 207 381 l34 77 358 0 c198 0 359 -3 359 -7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

379
frontend/src/css/fonts.css Normal file
View file

@ -0,0 +1,379 @@
/* Kanit */
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-thin.ttf') format('truetype');
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-thin-italic.ttf') format('truetype');
font-weight: 100;
font-style: italic;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-extralight.ttf') format('truetype');
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-extralight-italic.ttf') format('truetype');
font-weight: 200;
font-style: italic;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-light-italic.ttf') format('truetype');
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-regular-italic.ttf') format('truetype');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-medium-italic.ttf') format('truetype');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-semibold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-semibold-italic.ttf') format('truetype');
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-bold-italic.ttf') format('truetype');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-extrabold.ttf') format('truetype');
font-weight: 800;
font-style: normal;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-extrabold-italic.ttf') format('truetype');
font-weight: 800;
font-style: italic;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-black.ttf') format('truetype');
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: 'Kanit';
src: url('/frontend/assets/fonts/kanit/kanit-black-italic.ttf') format('truetype');
font-weight: 900;
font-style: italic;
}
/* Montserrat */
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-thin.ttf') format('truetype');
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-thin-italic.ttf') format('truetype');
font-weight: 100;
font-style: italic;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-extralight.ttf') format('truetype');
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-extralight-italic.ttf') format('truetype');
font-weight: 200;
font-style: italic;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-light-italic.ttf') format('truetype');
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-regular-italic.ttf') format('truetype');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-medium-italic.ttf') format('truetype');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-semibold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-semibold-italic.ttf') format('truetype');
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-bold-italic.ttf') format('truetype');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-extrabold.ttf') format('truetype');
font-weight: 800;
font-style: normal;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-extrabold-italic.ttf') format('truetype');
font-weight: 800;
font-style: italic;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-black.ttf') format('truetype');
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: 'Montserrat';
src: url('/frontend/assets/fonts/montserrat/montserrat-black-italic.ttf') format('truetype');
font-weight: 900;
font-style: italic;
}
/* SourceCodePro */
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-extralight.ttf') format('truetype');
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-extralight-italic.ttf') format('truetype');
font-weight: 200;
font-style: italic;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-light-italic.ttf') format('truetype');
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-regular-italic.ttf') format('truetype');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-medium-italic.ttf') format('truetype');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-semibold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-semibold-italic.ttf') format('truetype');
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-bold-italic.ttf') format('truetype');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-extrabold.ttf') format('truetype');
font-weight: 800;
font-style: normal;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-extrabold-italic.ttf') format('truetype');
font-weight: 800;
font-style: italic;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-black.ttf') format('truetype');
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: 'SourceCodePro';
src: url('/frontend/assets/fonts/sourcecodepro/sourcecodepro-black-italic.ttf') format('truetype');
font-weight: 900;
font-style: italic;
}
/* Lora */
@font-face {
font-family: 'Lora';
src: url('/frontend/assets/fonts/lora/lora-regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Lora';
src: url('/frontend/assets/fonts/lora/lora-regular-italic.ttf') format('truetype');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Lora';
src: url('/frontend/assets/fonts/lora/lora-medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Lora';
src: url('/frontend/assets/fonts/lora/lora-medium-italic.ttf') format('truetype');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: 'Lora';
src: url('/frontend/assets/fonts/lora/lora-semibold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Lora';
src: url('/frontend/assets/fonts/lora/lora-semibold-italic.ttf') format('truetype');
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: 'Lora';
src: url('/frontend/assets/fonts/lora/lora-bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Lora';
src: url('/frontend/assets/fonts/lora/lora-bold-italic.ttf') format('truetype');
font-weight: 700;
font-style: italic;
}
/* NotoColorEmoji */
@font-face {
font-family: 'NotoColorEmoji';
src: url('/frontend/assets/fonts/notocoloremoji/notocoloremoji-regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}

View file

@ -0,0 +1,4 @@
@import "./fonts.css";
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

View file

@ -0,0 +1,48 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Example Site</title>
<meta name="description" content="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.">
<meta name="author" content="John Doe">
<meta property="og:title" content="Example Site">
<meta property="og:type" content="website">
<meta property="og:url" content="https://example.com">
<meta property="og:description" content="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.">
<meta property="og:image" content="https://example.com/static/images/common/og-image.webp">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<link rel="apple-touch-icon" sizes="180x180" href="/frontend/assets/images/common/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/frontend/assets/images/common/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/frontend/assets/images/common/favicon/favicon-16x16.png">
<link rel="manifest" href="/frontend/assets/images/common/favicon/site.webmanifest">
<link rel="mask-icon" href="/frontend/assets/images/common/favicon/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="/frontend/src/css/styles.css">
<link rel="stylesheet" href="/frontend/src/css/fonts.css">
</head>
<body>
<noscript>
<div class="flex justify-center w-full min-h-screen">
<div class="flex flex-col gap-8 max-w-xl min-h-screen justify-center items-center text-center m-8">
<div class="flex flex-col justify-center items-center w-full border-2 border-darkaccent rounded-xl p-8">
<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="block w-12 h-12">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
<p class="text-xl font-semibold mt-4">This page requires JavaScript.</p>
<p class="text-lg mt-4">Please enable JavaScript in your browser to continue.</p>
</div>
</div>
</div>
</noscript>
<div id="app"></div>
<script type="module" src="/frontend/src/js/main.js"></script>
</body>
</html>

5
frontend/src/js/App.vue Normal file
View file

@ -0,0 +1,5 @@
<template>
<div>
<router-view />
</div>
</template>

View file

@ -0,0 +1,5 @@
<template>
<div>
<p>Amet cursus sit amet dictum. Feugiat vivamus at augue eget arcu dictum. Sed enim ut sem viverra aliquet eget sit. Pellentesque eu tincidunt tortor aliquam nulla facilisi.</p>
</div>
</template>

12
frontend/src/js/main.js Normal file
View file

@ -0,0 +1,12 @@
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
// Enable global Vue feature flags:
// https://stackoverflow.com/questions/70083869/parcel-2-vue-3-how-to-set-global-feature-flags-vue-devtools-disabled
globalThis.__VUE_OPTIONS_API__ = true;
globalThis.__VUE_PROD_DEVTOOLS__ = false;
const app = createApp(App);
app.use(router);
app.mount("#app");

View file

@ -0,0 +1,22 @@
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('../views/NotFound.vue'),
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;

View file

@ -0,0 +1,17 @@
<script>
import ExampleComponent from '../components/ExampleComponent.vue'
export default {
components: {
ExampleComponent,
},
}
</script>
<template>
<div>
<ExampleComponent />
</div>
</template>

View file

@ -0,0 +1,14 @@
<script>
export default {
components: {
},
}
</script>
<template>
<div>
<p>The page you are looking for was not found &#1F641;</p>
</div>
</template>

23
package.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "Vue/Tailwind/FastAPI Template",
"version": "0.0.1",
"description": "A template for a new web application.",
"scripts": {},
"private": true,
"author": "John Doe",
"license": "UNLICENSED",
"devDependencies": {
"@parcel/transformer-vue": "^2.8.2",
"buffer": "^5.7.1",
"parcel": "^2.8.2",
"postcss": "^8.4.20",
"postcss-import": "^15.1.0",
"process": "^0.11.10",
"tailwindcss": "^3.2.4"
},
"dependencies": {
"axios": "^1.2.2",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
}
}

8
postcss.config.js Normal file
View file

@ -0,0 +1,8 @@
module.exports = {
plugins: [
require('postcss-import'),
require('tailwindcss/nesting'),
require('tailwindcss'),
require('autoprefixer'),
]
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

16
requirements.txt Normal file
View file

@ -0,0 +1,16 @@
anyio==3.6.2
click==8.1.3
fastapi==0.88.0
h11==0.14.0
httptools==0.5.0
idna==3.4
pydantic==1.10.4
python-dotenv==0.21.0
PyYAML==6.0
sniffio==1.3.0
starlette==0.22.0
typing_extensions==4.4.0
uvicorn==0.20.0
uvloop==0.17.0
watchfiles==0.18.1
websockets==10.4

28
tailwind.config.js Normal file
View file

@ -0,0 +1,28 @@
module.exports = {
content: [
"./frontend/src/**/*.html",
"./frontend/src/**/*.js",
"./frontend/src/**/*.vue",
],
theme: {
extend: {
fontFamily: {
sans: ["Montserrat", "NotoColorEmoji"],
serif: ["Lora", "NotoColorEmoji"],
mono: ["SourceCodePro", "NotoColorEmoji"],
accent: ["Kanit", "NotoColorEmoji"],
emoji: ["NotoColorEmoji"],
},
colors: {
transparent: 'transparent',
current: 'currentColor',
lightshade: '#eeebed',
lightaccent: '#8C919C',
main: '#1C4F97',
darkaccent: '#B3425C',
darkshade: '#221F2F',
},
},
},
plugins: [],
};