Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
309260c
fix(.gitignore): add logotype with dynamic color, PM2 config
graycraft Jul 16, 2025
1e76573
fix(views/{404,index}.ejs): change anchor color on focus; font size a…
graycraft Jul 16, 2025
023f15f
fix(public/stylesheets/style.css): don't change anchor BG color on fo…
graycraft Jul 16, 2025
36223a9
fix({app,routes/index}.mts): rename logo with dynamic color to ignore…
graycraft Jul 16, 2025
e86270c
build(package-lock.json): updates vulnerable dependencies with NPM se…
graycraft Jul 16, 2025
affb745
fix(views/{404,index}.ejs): load `normalize.min.css` from cdnjs
graycraft Jul 16, 2025
f0807d2
build(package{,-lock}.json): install Pug template engine
graycraft Jul 16, 2025
a95c6e9
build(package{,-lock}.json): remove EJS template engine
graycraft Jul 16, 2025
4f1fca6
feat(app.mts): use Pug template engine
graycraft Jul 16, 2025
563a315
chore(.vscode/settings.json): delete file associations for "*.ejs" to…
graycraft Jul 16, 2025
3ac6d36
feat(views/{404,index}.pug): migrate to Pug templates
graycraft Jul 16, 2025
355a227
feat(views/{404,error,index}.ejs): migrate to Pug templates
graycraft Jul 16, 2025
77e01b3
refactor(views/{error,index,layout}.ejs): move repeating markup from …
graycraft Jul 17, 2025
2f18f6c
feat(library/constants.mts): extend HTTP statuses with code 404 and s…
graycraft Jul 17, 2025
1ed7f6c
fix(app.mts): support handling error pages
graycraft Jul 17, 2025
c1b2894
fix(index.mts): error handling on server start
graycraft Jul 17, 2025
21b0a69
fix(.vscode/settings.json): format files on save (with Prettier)
graycraft Jul 17, 2025
f882f50
fix(views/layout.pug): exclude error pages from W3C validation
graycraft Jul 17, 2025
2e57722
fix(views/layout.pug): update greeting ASCII art
graycraft Jul 18, 2025
66f24d2
build(package{,-lock}.json): install Webpack and deps
graycraft Jul 18, 2025
0c5d274
build(webpack.config.ts): add Webpack config
graycraft Jul 18, 2025
0136ec5
refactor(source/graycraft.{mjs,umd.js}): convert uncompiled UMD modul…
graycraft Jul 18, 2025
09a48d6
refactor({app,routes/index}.mts): update path to Graycraft logotype g…
graycraft Jul 18, 2025
06f04a3
refactor(tsconfig.json): update path to Graycraft logotype generation…
graycraft Jul 18, 2025
ba05291
fix(.gitignore): add path for `javascripts` dir
graycraft Jul 18, 2025
b2825d4
fix(public/javascripts/graycraft.umd.js): delete temporary uncompiled…
graycraft Jul 18, 2025
8bec6d2
refactor(views/index.pug): import UMD to the page
graycraft Jul 18, 2025
57f0b8e
feat(views/layout.pug): enable registration of service worker
graycraft Jul 18, 2025
5618aa1
feat(postcss.config.js): add config for PostCSS
graycraft Jul 18, 2025
71dcdd3
docs(README.md): update description and usage instructions
graycraft Jul 18, 2025
366eb74
build(package{,-lock}.json): install Webpack plugins for bundle minim…
graycraft Jul 19, 2025
27ca3c9
feat(webpack.config.ts): minimize CSS and JS
graycraft Jul 19, 2025
559ce46
refactor(public/stylesheets/style.css): move to the `source` dir
graycraft Jul 19, 2025
c6b9f43
refactor(source/main.css): move to the `source` dir
graycraft Jul 19, 2025
ad3eba1
refactor({app,routes/index}.mts): update path to `main.css`
graycraft Jul 19, 2025
416f5e4
feat(source/index.mjs): entry point for Webpack compilation at frontend
graycraft Jul 19, 2025
9cb9b22
fix(.gitignore): track `public/javascripts` dir by Git
graycraft Jul 19, 2025
dc31442
fix(.gitignore): do not track dist CSS, JS, license and map files but…
graycraft Jul 19, 2025
80a5395
feat(distribution/.gitkeep): keep dir in repo
graycraft Jul 19, 2025
a8ae316
feat(public/{javascripts,stylesheets}): add sym links to files at `di…
graycraft Jul 19, 2025
f025aee
build(package{,-lock}.json): install Pug type defs
graycraft Jul 19, 2025
228e487
feat(library/graycraft.mts): compile and minify logotype SVG template…
graycraft Jul 19, 2025
02d072d
fix(source/graycraft.mjs): append desc and title tags to generated SVG
graycraft Jul 19, 2025
d759029
fix(views/index.pug): generate desc and title tags for SVG dynamically
graycraft Jul 19, 2025
9bec358
refactor(public/): move all dist assets to `bundle` dir
graycraft Jul 19, 2025
cc543d6
fix(views/index.pug): defer non-critical JS resource `require.min.js`
graycraft Jul 19, 2025
51dab8c
fix(views/layout.pug): preload non-critical CSS resources asynchronously
graycraft Jul 19, 2025
2105f37
build(package{,-lock}.json): install Node.js compression middleware
graycraft Jul 20, 2025
5e9377c
build(package{,-lock}.json): install type defs for `compression`
graycraft Jul 20, 2025
939c85a
feat(app.mts): enable compression for text-based resources
graycraft Jul 20, 2025
716893a
fix(views/layout.pug): enable indexing by search engine crawlers
graycraft Jul 20, 2025
eec569a
fix(views/error.pug): disable indexing by search engine crawlers
graycraft Jul 20, 2025
2823625
fix(views/layout.pug): meta tags, source code formatting
graycraft Jul 23, 2025
0a0beda
docs(public/images/external_link.svg): add description with URL
graycraft Jul 23, 2025
40325f6
fix(views/layout.pug): meta tag with charset
graycraft Jul 23, 2025
47ec7a1
fix(routes/index.mts): page title
graycraft Jul 23, 2025
31a1259
refactor(static/): move static assets from `public` dir
graycraft Aug 8, 2025
fd4415a
style(source/main.css): format with Prettier
graycraft Aug 8, 2025
7283790
feat(source/main.css): set background image
graycraft Aug 8, 2025
385b5bd
feat(source/graycraft.mjs): add alpha channel to light variant of COTD
graycraft Aug 8, 2025
3a041e0
fix(webpack.config.ts): add link to Webpack docs; add `jpeg` and `web…
graycraft Aug 8, 2025
cf67784
docs(routes/index.mts): update comments to logo data
graycraft Aug 14, 2025
3e75dd3
docs(views/index.pug): update comments to logo elements
graycraft Aug 14, 2025
912957a
feat(library/graycraft.mts): add shadow by filter effects; wrap to `d…
graycraft Aug 14, 2025
72f3784
feat(source/graycraft.mjs): add shadow by filter effects; wrap to `de…
graycraft Aug 14, 2025
638c986
style(source/graycraft.mjs): add new lines before and after block sta…
graycraft Aug 14, 2025
580b293
refactor(static/images/external_link.svg): wrap `desc` and `title` to…
graycraft Aug 14, 2025
c6aa3b8
feat(views/index.pug): add shadow to logos by `drop-shadow` filter
graycraft Aug 14, 2025
7e09858
fix(app.mts): remove redundant variable from render
graycraft Aug 19, 2025
9e16e4f
fix(routes/index.mts): calculate current year by `getYear` method
graycraft Aug 19, 2025
2e48369
fix(source/graycraft.mjs): increase transparency by 50% for light var…
graycraft Aug 19, 2025
713d8eb
style(library/graycraft.mts): increase indent for SVG circle
graycraft Aug 19, 2025
522a98f
fix(views/index.pug): set an explicit width and height for images
graycraft Aug 19, 2025
8b6d7f4
fix(views/layout.pug): preload and set bg image dynamically with tran…
graycraft Aug 19, 2025
828f188
fix(source/main.css): set linear gradient; add blur transition; chang…
graycraft Aug 19, 2025
eee90a5
feat(static/images/rain.webp{,.sha256,.stats}): add bg image with rai…
graycraft Aug 19, 2025
839c768
feat(static/images/rust.webp{.sha256,.stats}): add bg image related f…
graycraft Aug 19, 2025
030a4d1
fix(.gitignore): ignore whole `distribution` dir; add header comment
graycraft Aug 19, 2025
9857907
chore(package-lock.json): update `caniuse-lite` package with browsers…
graycraft Aug 19, 2025
1ae7bda
feat(static/images/graycraft.png.sha256): add SHA256 message digest f…
graycraft Aug 19, 2025
a2bfefb
build(package{,-lock}.json): install Browserslist for Webpack builds
graycraft Aug 19, 2025
94b89e9
build(.browserslistrc): add Browserslist config
graycraft Aug 19, 2025
5ab4ec0
chore(.prettierignore): do not format `.browserslistrc` to avoid synt…
graycraft Aug 19, 2025
5992396
fix(webpack.config.ts): set target to `browserslist`
graycraft Aug 19, 2025
e19e0f6
fix(views/layout.pug): update path to bg image with rain drops theme
graycraft Aug 19, 2025
832643c
fix(routes/index.mts): render template with different variables for e…
graycraft Aug 20, 2025
dde0315
fix(views/layout.pug): render template with different variables for e…
graycraft Aug 20, 2025
df1b3e6
fix(source/main.css): decrease blur transition to 600 ms
graycraft Aug 20, 2025
2e33be9
fix(app.mts): add variable with logo image path to render
graycraft Aug 20, 2025
1b1f599
fix(source/graycraft.mjs): prefer specified foreground color over COT…
graycraft Aug 20, 2025
30e93ee
refactor(source/graycraft.mjs): split `shape` method to creating SVG …
graycraft Aug 20, 2025
23c1ae1
refactor(library/graycraft.mts): rename variables defining SVG path
graycraft Aug 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# @see https://github.com/browserslist/browserslist

# @see https://browsersl.ist/#q=last+1+chrome+version%0Alast+1+firefox+version%0Alast+1+safari+version
[development]
last 1 chrome version
last 1 firefox version
last 1 safari version

# @see https://browsersl.ist/#q=%3E+0.2%25%0Adefaults%0Aie+11%0Alast+2+versions%0Anot+dead%0Anot+op_mini+all
[production]
> 0.2%
defaults
ie 11
last 2 versions
not dead
not op_mini all
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Generated by GitHub New Repository wizard on Nov 5, 2024.

# Logs
logs
*.log
Expand Down Expand Up @@ -96,7 +98,7 @@ dist/*
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

public/images/graycraft.png
static/images/graycraft-cotd.png

# vuepress build output
.vuepress/dist
Expand Down Expand Up @@ -131,3 +133,7 @@ public/images/graycraft.png
.pnp.*

distribution/*

# PM2 production configuration.

ecosystem.config.js
4 changes: 3 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Directories with auto-generated files.

.browserslistrc
.vscode
distribution
public
static
10 changes: 6 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.rulers": [80, 100, 120],
"emmet.includeLanguages": {
"ejs": "html",
},
"editor.formatOnSave": true,
"editor.rulers": [
80,
100,
120
],
}
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Client and server logic of the [graycraft.me](https://graycraft.me) website.

The legacy stack CSS/Express/Pug/Webpack used for this project intentionally

to compare with modern approaches like SCSS/NestJS/React/Vite.

## Prerequisites

Node.js 22.11.0:
Expand Down Expand Up @@ -79,6 +83,12 @@ $ npm i

## Usage

Build static files for the frontend:

```bash
$ npm run webpack:build
```

Run production server:

```bash
Expand Down
68 changes: 35 additions & 33 deletions app.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,82 +7,84 @@
* @module app
*/

import type { Express, ErrorRequestHandler, RequestHandler } from 'express';

import nodeFs from 'node:fs';
import nodePath from 'node:path';
import { fileURLToPath } from 'node:url';

import compression from 'compression';
import cookieParser from 'cookie-parser';
import express from 'express';
import createError from 'http-errors';
import logger from 'morgan';

import type { Express, ErrorRequestHandler, RequestHandler } from 'express';

import { HTTP } from './library/constants.mts';
import { SIZE, SIZE_MIN } from './library/graycraft.mts';
import routerIndex from './routes/index.mts';
import graycraft from './source/graycraft.umd.js';
import graycraft from './source/graycraft.mjs';

const {
STATUS: { INTERNAL_SERVER_ERROR },
STATUS: { INTERNAL_SERVER_ERROR, NOT_FOUND },
} = HTTP,
__filename = fileURLToPath(import.meta.url),
__dirname = nodePath.dirname(__filename),
app: Express = express();

app.set('views', nodePath.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.set('view engine', 'pug');
app.use(compression());
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(nodePath.join(__dirname, 'public')));
app.use(express.static(nodePath.join(__dirname, 'static')));
app.use('/', routerIndex);

/**
* Catch 404 and forward to error handler.
* Catch HTTP status code 404 and forward to the error handler.
*/
app.use(((req, res, next) => {
next(createError(NOT_FOUND.CODE));
}) as RequestHandler);

/**
* Error handler.
*/
app.use(((error, req, res, next) => {
const { DEPLOYMENT, HOSTNAME, PORT, PORT_PROXY } = process.env,
externalLinkBuffer = nodeFs.readFileSync('public/images/external_link.svg'),
externalLink = global.encodeURIComponent(String(externalLinkBuffer)),
host = HOSTNAME + ':' + (DEPLOYMENT === 'local' ? PORT : PORT_PROXY),
cssBuffer = nodeFs.readFileSync('public/stylesheets/style.css'),
css = String(cssBuffer),
{ back: backQuery, fore: foreQuery, size: sizeQuery } = req.query,
back = String(backQuery ?? 'transparent'),
cssBuffer = nodeFs.readFileSync('distribution/main.css'),
css = String(cssBuffer),
fore = String(foreQuery ?? ''),
imagePath = 'images/graycraft-cotd.png',
host = HOSTNAME + ':' + (DEPLOYMENT === 'local' ? PORT : PORT_PROXY),
size = Number(sizeQuery ?? SIZE) < SIZE_MIN ? SIZE_MIN : Number(sizeQuery ?? SIZE),
{ getYear, hsl, hslLight, rgb } = graycraft(size, fore, back);
{ getYear, hsl, hslLight, rgb } = graycraft(size, fore, back),
status: number = error.status || INTERNAL_SERVER_ERROR.CODE;

res.render('404', {
back,
/** Set locals, only providing error in development mode. */
res.locals.message = error?.message;
res.locals.error = req.app.get('env') === 'development' ? error : {};
res.status(status);
res.render('error', {
css,
externalLink,
header: status,
host,
header: '404',
hsl,
hslLight,
imagePath: 'images/graycraft.png',
paragraph: 'This page is not found on the server.',
imagePath,
paragraph:
({
404: 'This page is not found on the server',
500: 'An error occurred on the server',
}[status] ?? 'Unknown error') + '.',
rgb,
size,
title: 'Not Found',
title: Object.values(HTTP.STATUS).find(({ CODE }) => CODE === status)?.TEXT ?? 'Error',
year: getYear(),
});
next(createError(404));
}) as RequestHandler);

/**
* Error handler.
*/
app.use(((error, req, res) => {
/** Set locals, only providing error in development mode. */
res.locals.message = error?.message;
res.locals.error = req.app.get('env') === 'development' ? error : {};

res.status(error.status || INTERNAL_SERVER_ERROR);
res.render('error');
}) as ErrorRequestHandler);

export default app;
Empty file added distribution/.gitkeep
Empty file.
2 changes: 2 additions & 0 deletions index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@ function onError(error: NodeJS.ErrnoException) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
Expand Down
12 changes: 10 additions & 2 deletions library/constants.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@
/**
* @type {{
* STATUS: {
* INTERNAL_SERVER_ERROR: 500;
* INTERNAL_SERVER_ERROR: { CODE: 500, TEXT: string };
* NOT_FOUND: { CODE: 404, TEXT: string };
* };
* }}
*/
export const HTTP = {
STATUS: {
INTERNAL_SERVER_ERROR: 500,
INTERNAL_SERVER_ERROR: {
CODE: 500,
TEXT: 'Internal Server Error',
},
NOT_FOUND: {
CODE: 404,
TEXT: 'Not Found',
},
},
};
81 changes: 53 additions & 28 deletions library/graycraft.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,78 @@
* @module library/graycraft
*/

import { compile } from 'pug';

export const SIZE: number = 480;
export const SIZE_MIN: number = 256;

/**
* Draw shapes on SVG element (server only).
* @param {() => RSvg} drawSvg Draw shapes function to get parameters.
* @returns {string} SVG template.
* @returns {string} Compiled Pug template with SVG.
*/
const templateSvg = (
drawSvg: () => {
back: string;
defCraft: string;
defGray: string;
hsl: string;
pathCraft: string;
pathGray: string;
round: boolean;
size: string;
sizeHalf: string;
translateY: number;
},
) => {
const { back, hsl, pathCraft, pathGray, round, size, sizeHalf, translateY } = drawSvg(),
const { back, defCraft, defGray, hsl, round, size, sizeHalf, translateY } = drawSvg(),
/** Setting height keeps empty space at the top and bottom of a scaled SVG image. */
template = `<svg
class="logotype"
${/* height="" */ ''}
onerror="'use strict'; console.error('Can not load the SVG:', this);"
preserveAspectRatio="xMidYMid meet"
style="background-color: ${round ? 'transparent' : back};"
viewBox="0 0 ${size} ${size}"
width="${size}"
>
<title>Graycraft</title>
<desc>SVG is not supported by your browser.</desc>
${round ? `<circle cx="${sizeHalf}" cy="${sizeHalf}" fill="${back}" r="${sizeHalf}"></circle>` : ''}
<g transform="translate(0, ${translateY})">
<path
d="${pathGray}"
fill="${hsl}"
></path>
<path
d="${pathCraft}"
fill="black"
></path>
</g>
</svg>`;
template = compile(
`svg(
class="logotype"
onerror="'use strict'; console.error('Can not load the SVG:', this);"
preserveAspectRatio="xMidYMid meet"
style="background-color: ${round ? 'transparent' : back};"
viewBox="0 0 ${size} ${size}"
width="${size}"
)
defs
title="Graycraft"
desc="SVG is not supported by your browser."
filter(
color-interpolation-filters="sRGB"
id="shadow"
)
feDropShadow(
dx="16.666667"
dy="10"
flood-color="black"
flood-opacity="0.5"
stdDeviation="5"
)
${
round
? `circle(
cx="${sizeHalf}"
cy="${sizeHalf}"
fill="${back}"
r="${sizeHalf}"
)`
: ''
}
g(
filter="url(#shadow)"
transform="translate(0, ${translateY})"
)
path(
d="${defGray}"
fill="${hsl}"
)
path(
d="${defCraft}"
fill="black"
)`,
);

return template;
return template();
};

export default templateSvg;
Loading