mirror of
https://github.com/morten-olsen/morten-olsen.github.io.git
synced 2026-02-08 01:46:28 +01:00
foo
This commit is contained in:
@@ -23,7 +23,7 @@ const createProfile = ({ cwd, path, bundler }: ProfileOptions) => {
|
||||
...data,
|
||||
imageUrl: createImage({
|
||||
image: imagePath,
|
||||
format: 'webp',
|
||||
format: 'avif',
|
||||
bundler,
|
||||
}),
|
||||
imagePath,
|
||||
|
||||
@@ -1,40 +1,54 @@
|
||||
import express, { Express } from 'express';
|
||||
import { Bundler } from '../bundler';
|
||||
import { extname } from 'path';
|
||||
|
||||
const createServer = (bundler: Bundler): Express => {
|
||||
const app = express();
|
||||
app.use((req, res) => {
|
||||
let path = req.path;
|
||||
const getAsset = (path: string) => {
|
||||
let asset = bundler.get(path);
|
||||
if (!asset) {
|
||||
path = path.endsWith('/') ? path + 'index.html' : path + '/index.html';
|
||||
asset = bundler.get(path);
|
||||
}
|
||||
return asset;
|
||||
};
|
||||
const app = express();
|
||||
app.get('/dev', (req, res) => {
|
||||
res.set({
|
||||
'Cache-Control': 'no-cache',
|
||||
'Content-Type': 'text/event-stream',
|
||||
Connection: 'keep-alive',
|
||||
});
|
||||
res.flushHeaders();
|
||||
const path = req.query.path?.toString();
|
||||
if (!path) {
|
||||
res.status(400).send('Missing path');
|
||||
return;
|
||||
}
|
||||
const asset = getAsset(path);
|
||||
if (!asset) {
|
||||
res.status(404).send('Not found');
|
||||
return;
|
||||
}
|
||||
const reload = () => {
|
||||
res.write('data: reload\n\n');
|
||||
};
|
||||
asset.subscribe(reload);
|
||||
res.on('close', () => {
|
||||
asset.unsubscribe(reload);
|
||||
});
|
||||
res.on('error', () => {
|
||||
asset.unsubscribe(reload);
|
||||
});
|
||||
res.on('finish', () => {
|
||||
asset.unsubscribe(reload);
|
||||
});
|
||||
});
|
||||
app.use((req, res) => {
|
||||
let path = req.path;
|
||||
let asset = getAsset(path);
|
||||
if (asset) {
|
||||
const ext = extname(path);
|
||||
asset.data
|
||||
.then((data) => {
|
||||
if (ext === '.html') {
|
||||
const unsubscribe = asset!.subscribe(async () => {
|
||||
await asset?.data;
|
||||
unsubscribe();
|
||||
res.end('<script>window.location.reload()</script>');
|
||||
});
|
||||
res.on('close', unsubscribe);
|
||||
res.on('finish', unsubscribe);
|
||||
res.on('error', unsubscribe);
|
||||
res.writeHead(200, {
|
||||
'content-type': 'text/html;charset=utf-8',
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
Pragma: 'no-cache',
|
||||
Expires: '0',
|
||||
'keep-alive': 'timeout=5, max=100',
|
||||
});
|
||||
res.write(data.content.toString().replace('</html>', ''));
|
||||
} else {
|
||||
res.send(data.content);
|
||||
}
|
||||
res.send(data.content);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
|
||||
@@ -12,14 +12,27 @@ type GlobOptions<T> = {
|
||||
const defaultCreate = (a: any) => a;
|
||||
|
||||
const createGlob = <T = string>({ cwd, pattern, create = defaultCreate }: GlobOptions<T>) => {
|
||||
const setup = (item: any) => {
|
||||
if (item instanceof Observable) {
|
||||
item.subscribe(() => {
|
||||
glob.recreate();
|
||||
});
|
||||
}
|
||||
};
|
||||
const glob = new Observable(async () => {
|
||||
const files = await fastGlob(pattern, { cwd });
|
||||
return files.map((path) => create(resolve(cwd, path)));
|
||||
return files.map((path) => {
|
||||
const item = create(resolve(cwd, path));
|
||||
setup(item);
|
||||
return item;
|
||||
});
|
||||
});
|
||||
|
||||
const watcher = watchGlob(pattern, { cwd });
|
||||
watcher.on('add', (path) => {
|
||||
glob.set((current) => Promise.resolve([...(current || []), create(resolve(cwd, path))]));
|
||||
const item = create(resolve(cwd, path));
|
||||
glob.set((current) => Promise.resolve([...(current || []), item]));
|
||||
setup(item);
|
||||
|
||||
return glob;
|
||||
});
|
||||
|
||||
9
bin/resources/page/client.ts
Normal file
9
bin/resources/page/client.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import EventSource from 'eventsource';
|
||||
|
||||
const path = encodeURIComponent(window.location.pathname);
|
||||
const source = new EventSource(`/dev?path=${path}`);
|
||||
source.onmessage = (msg) => {
|
||||
if (msg.data === 'reload') {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import { Asset, Bundler } from '../../bundler';
|
||||
import { Observable } from '../../observable';
|
||||
import { ServerStyleSheet } from 'styled-components';
|
||||
import { resolve } from 'path';
|
||||
import { createScript } from '../script';
|
||||
|
||||
type PageOptions = {
|
||||
path: string;
|
||||
@@ -12,11 +13,28 @@ type PageOptions = {
|
||||
props: Observable<any>;
|
||||
bundler: Bundler;
|
||||
};
|
||||
|
||||
let devClientUrl: string | undefined;
|
||||
|
||||
const createPage = (options: PageOptions) => {
|
||||
const data = Observable.combine({
|
||||
template: options.template,
|
||||
props: options.props,
|
||||
});
|
||||
if (devClientUrl === undefined) {
|
||||
const devClient = createScript({
|
||||
path: resolve(__dirname, 'client.ts'),
|
||||
format: 'iife',
|
||||
});
|
||||
const devClientAsset = devClient.pipe<Asset>(async () => {
|
||||
const script = await devClient.data;
|
||||
return {
|
||||
content: script,
|
||||
};
|
||||
});
|
||||
const devClientBundle = options.bundler.register('/dev-client.js', devClientAsset);
|
||||
devClientUrl = devClientBundle;
|
||||
}
|
||||
const page = data.pipe(async ({ template, props }) => {
|
||||
const sheet = new ServerStyleSheet();
|
||||
const helmetContext: FilledContext = {} as any;
|
||||
@@ -33,6 +51,7 @@ const createPage = (options: PageOptions) => {
|
||||
const css = sheet.getStyleTags();
|
||||
const headHtml = [
|
||||
css,
|
||||
`<script src="${devClientUrl}"></script>`,
|
||||
helmet.title?.toString(),
|
||||
helmet.priority?.toString(),
|
||||
helmet.meta?.toString(),
|
||||
|
||||
@@ -20,9 +20,9 @@ First, an event goes in, such as a motion sensor triggering, or you set the bath
|
||||
|
||||
This distinction is essential because it comes with a few drawbacks:
|
||||
|
||||
* Because the event does not change the state but sends a request to the device that does it, everything becomes asynchronous and can happen out of order. This behaviour can be seen either as an issue or a feature, but it does make integrating with it a lot harder from a technical point of view.
|
||||
* The request is sent to the device as a "fire-and-forget" event. It then relies on the success of that request and the subsequent state change to be reported back from the device before the state gets updated. This behaviour means that if this request fails (something you often see with ZigBee-based devices), the device and the state don't get updated.
|
||||
* Since the device is responsible for reporting the state change, you are dependent on having that actual device there to make the change. Without sending the changes to the actual device, you cannot test the setup.
|
||||
- Because the event does not change the state but sends a request to the device that does it, everything becomes asynchronous and can happen out of order. This behaviour can be seen either as an issue or a feature, but it does make integrating with it a lot harder from a technical point of view.
|
||||
- The request is sent to the device as a "fire-and-forget" event. It then relies on the success of that request and the subsequent state change to be reported back from the device before the state gets updated. This behaviour means that if this request fails (something you often see with ZigBee-based devices), the device and the state don't get updated.
|
||||
- Since the device is responsible for reporting the state change, you are dependent on having that actual device there to make the change. Without sending the changes to the actual device, you cannot test the setup.
|
||||
|
||||
So can we create a setup that gets away from these issues?
|
||||
|
||||
@@ -34,7 +34,7 @@ Time to go a bit further down the React rabbit hole because another thing from R
|
||||
|
||||
Instead of dispatching events to the devices waiting for them to update and report their state back, we can rely on reconciliation to update our device. For example, let's say we have a device state for our living room light that says it is at 80% brightness in our Redux store. So now we dispatch an event that sets it to 20% brightness.
|
||||
|
||||
Instead of sending this event to the device, we update the Redux state.
|
||||
Instead of sending this event to the device, we update the Redux state.
|
||||
|
||||
We have a state listener that detects when the state changes and compares it to the state of the actual device. In our case, it seems that the state indicates that the living room light should be at 20% but are, in fact, at 80%, so it sends a request to the actual device to update it to the correct value.
|
||||
|
||||
@@ -56,14 +56,13 @@ So last thing we need is a direct way of updating devices, as we can not capture
|
||||
|
||||
I have separated the events going into Redux into two types:
|
||||
|
||||
* control events, which directly controls a device
|
||||
* environment events represent sensor data coming in (push on a button, motion sensor triggering, TV playing, etc.)
|
||||
- control events, which directly controls a device
|
||||
- environment events represent sensor data coming in (push on a button, motion sensor triggering, TV playing, etc.)
|
||||
|
||||
Now comes the part I have feared, where I need to draw a diagram.
|
||||
|
||||
...sorry
|
||||
|
||||
|
||||

|
||||
|
||||
So this shows our final setup.
|
||||
@@ -89,4 +88,3 @@ We have a setup where we both have control events and environments coming in. Co
|
||||
Let's say you always dim the light when playing a movie that is more than one hour long; your smart home would be able to recognize this pattern and automatically start to do this routine for you.
|
||||
|
||||
Would this work? I don't know. I am trying to get more skilled at machine learning to find out.
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
{
|
||||
\noindent\begin{columns}
|
||||
\noindent{\Large \textbf{#1}} \hfill {\small #2} \\
|
||||
\textit{#3}
|
||||
\textit{#3}\dotfill
|
||||
\ifnum\columncount>2
|
||||
\vfill\null\columnbreak
|
||||
\else
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import styled, { createGlobalStyle } from 'styled-components';
|
||||
import styled, { createGlobalStyle, keyframes } from 'styled-components';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Jumbo } from '../../typography';
|
||||
import { createTheme, ThemeProvider } from '../../theme';
|
||||
@@ -13,6 +13,163 @@ const GlobalStyle = createGlobalStyle`
|
||||
background-color: ${({ theme }) => theme.colors.background};
|
||||
color: ${({ theme }) => theme.colors.foreground};
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Archivo Black';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/archivoblack/v17/HTxqL289NzCGg4MzN6KJ7eW6CYKF_i7y.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Archivo Black';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/archivoblack/v17/HTxqL289NzCGg4MzN6KJ7eW6CYyF_g.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Black Ops One';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/blackopsone/v20/qWcsB6-ypo7xBdr6Xshe96H3aDbbtwkh.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Black Ops One';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/blackopsone/v20/qWcsB6-ypo7xBdr6Xshe96H3aDTbtwkh.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Black Ops One';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/blackopsone/v20/qWcsB6-ypo7xBdr6Xshe96H3aDXbtwkh.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Black Ops One';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/blackopsone/v20/qWcsB6-ypo7xBdr6Xshe96H3aDvbtw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-440qyriQwlOrhSvowK_l5-cSZMZ-Y.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-440qyriQwlOrhSvowK_l5-eCZMZ-Y.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-440qyriQwlOrhSvowK_l5-cyZMZ-Y.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-440qyriQwlOrhSvowK_l5-ciZMZ-Y.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-440qyriQwlOrhSvowK_l5-fCZM.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-4n0qyriQwlOrhSvowK_l52xwNZVcf6lvg.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-4n0qyriQwlOrhSvowK_l52xwNZXMf6lvg.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-4n0qyriQwlOrhSvowK_l52xwNZV8f6lvg.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-4n0qyriQwlOrhSvowK_l52xwNZVsf6lvg.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-4n0qyriQwlOrhSvowK_l52xwNZWMf6.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const TitleAnimation = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Title = styled(Jumbo)`
|
||||
@@ -24,7 +181,9 @@ const Title = styled(Jumbo)`
|
||||
min-height: 100%;
|
||||
margin-bottom: 10rem;
|
||||
margin-top: 10rem;
|
||||
animation: ${TitleAnimation} 0.4s ease-in-out;
|
||||
@media only screen and (max-width: 900px) {
|
||||
z-index: 1;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
`;
|
||||
@@ -40,6 +199,7 @@ const ArticleTitleWord = styled(Jumbo)`
|
||||
background: ${({ theme }) => theme.colors.primary};
|
||||
color: ${({ theme }) => theme.colors.foreground};
|
||||
@media only screen and (max-width: 900px) {
|
||||
z-index: 1;
|
||||
font-size: 2.5rem;
|
||||
line-height: 3.1rem;
|
||||
}
|
||||
@@ -55,9 +215,22 @@ const Wrapper = styled.div`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const ArticleAnimation = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ArticleWrapper = styled.article`
|
||||
font-size: 1.1rem;
|
||||
font-family: 'Merriweather', serif;
|
||||
animation: ${ArticleAnimation} 0.5s ease-in-out;
|
||||
|
||||
> p,
|
||||
ul,
|
||||
@@ -191,6 +364,18 @@ const Content = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const SideAnimation = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Side = styled.aside`
|
||||
position: fixed;
|
||||
right: 0;
|
||||
@@ -198,6 +383,7 @@ const Side = styled.aside`
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
clip-path: polygon(40% 0%, 100% 0%, 100% 100%, 0% 100%, 0% 50%);
|
||||
animation ${SideAnimation} 0.3s ease-in-out forwards;
|
||||
|
||||
@media only screen and (max-width: 900px) {
|
||||
position: static;
|
||||
@@ -254,12 +440,10 @@ const ArticlePage: Page<'article'> = ({ article, profile, pdfUrl }) => {
|
||||
<ThemeProvider theme={createTheme({ baseColor: article.color })}>
|
||||
<Helmet>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Black+Ops+One&family=Merriweather:wght@400;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>
|
||||
{article.title} by {profile.name}
|
||||
</title>
|
||||
</Helmet>
|
||||
<GlobalStyle />
|
||||
<Wrapper>
|
||||
|
||||
@@ -16,6 +16,151 @@ const GlobalStyle = createGlobalStyle`
|
||||
background-color: ${({ theme }) => theme.colors.background};
|
||||
color: ${({ theme }) => theme.colors.foreground};
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Archivo Black';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/archivoblack/v17/HTxqL289NzCGg4MzN6KJ7eW6CYKF_i7y.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Archivo Black';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/archivoblack/v17/HTxqL289NzCGg4MzN6KJ7eW6CYyF_g.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Black Ops One';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/blackopsone/v20/qWcsB6-ypo7xBdr6Xshe96H3aDbbtwkh.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Black Ops One';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/blackopsone/v20/qWcsB6-ypo7xBdr6Xshe96H3aDTbtwkh.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Black Ops One';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/blackopsone/v20/qWcsB6-ypo7xBdr6Xshe96H3aDXbtwkh.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Black Ops One';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/blackopsone/v20/qWcsB6-ypo7xBdr6Xshe96H3aDvbtw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-440qyriQwlOrhSvowK_l5-cSZMZ-Y.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-440qyriQwlOrhSvowK_l5-eCZMZ-Y.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-440qyriQwlOrhSvowK_l5-cyZMZ-Y.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-440qyriQwlOrhSvowK_l5-ciZMZ-Y.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-440qyriQwlOrhSvowK_l5-fCZM.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-4n0qyriQwlOrhSvowK_l52xwNZVcf6lvg.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-4n0qyriQwlOrhSvowK_l52xwNZXMf6lvg.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-4n0qyriQwlOrhSvowK_l52xwNZV8f6lvg.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-4n0qyriQwlOrhSvowK_l52xwNZVsf6lvg.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/merriweather/v30/u-4n0qyriQwlOrhSvowK_l52xwNZWMf6.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const Hero = styled.div`
|
||||
@@ -112,12 +257,8 @@ const FrontPage: Page<'frontpage'> = ({ articles, profile }) => {
|
||||
<ThemeProvider theme={theme}>
|
||||
<Helmet>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Black+Ops+One&family=Merriweather:wght@400;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Morten Olsen</title>
|
||||
</Helmet>
|
||||
<GlobalStyle />
|
||||
<Sheet color="#c85279">
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"commander": "^10.0.0",
|
||||
"ejs": "^3.1.9",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"eventsource": "^2.0.2",
|
||||
"express": "^4.18.2",
|
||||
"fast-glob": "^3.2.12",
|
||||
"glob-watcher": "^5.0.5",
|
||||
@@ -46,6 +47,7 @@
|
||||
"@react-native-community/eslint-config": "^3.2.0",
|
||||
"@types/chroma-js": "^2.4.0",
|
||||
"@types/ejs": "^3.1.2",
|
||||
"@types/eventsource": "^1.1.11",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/glob-watcher": "^5.0.2",
|
||||
"@types/jest": "^29.5.0",
|
||||
|
||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@@ -11,6 +11,7 @@ specifiers:
|
||||
'@rollup/plugin-typescript': ^11.0.0
|
||||
'@types/chroma-js': ^2.4.0
|
||||
'@types/ejs': ^3.1.2
|
||||
'@types/eventsource': ^1.1.11
|
||||
'@types/express': ^4.17.17
|
||||
'@types/glob-watcher': ^5.0.2
|
||||
'@types/jest': ^29.5.0
|
||||
@@ -25,6 +26,7 @@ specifiers:
|
||||
ejs: ^3.1.9
|
||||
eslint: ^8.36.0
|
||||
eventemitter3: ^5.0.0
|
||||
eventsource: ^2.0.2
|
||||
express: ^4.18.2
|
||||
fast-glob: ^3.2.12
|
||||
glob-watcher: ^5.0.5
|
||||
@@ -62,6 +64,7 @@ dependencies:
|
||||
commander: 10.0.0
|
||||
ejs: 3.1.9
|
||||
eventemitter3: 5.0.0
|
||||
eventsource: 2.0.2
|
||||
express: 4.18.2
|
||||
fast-glob: 3.2.12
|
||||
glob-watcher: 5.0.5
|
||||
@@ -86,6 +89,7 @@ devDependencies:
|
||||
'@react-native-community/eslint-config': 3.2.0_dzq4sglfl3yv4kxodieycp32vy
|
||||
'@types/chroma-js': 2.4.0
|
||||
'@types/ejs': 3.1.2
|
||||
'@types/eventsource': 1.1.11
|
||||
'@types/express': 4.17.17
|
||||
'@types/glob-watcher': 5.0.2
|
||||
'@types/jest': 29.5.0
|
||||
@@ -1095,6 +1099,10 @@ packages:
|
||||
resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==}
|
||||
dev: false
|
||||
|
||||
/@types/eventsource/1.1.11:
|
||||
resolution: {integrity: sha512-L7wLDZlWm5mROzv87W0ofIYeQP5K2UhoFnnUyEWLKM6UBb0ZNRgAqp98qE5DkgfBXdWfc2kYmw9KZm4NLjRbsw==}
|
||||
dev: true
|
||||
|
||||
/@types/express-serve-static-core/4.17.33:
|
||||
resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
|
||||
dependencies:
|
||||
@@ -2645,6 +2653,11 @@ packages:
|
||||
resolution: {integrity: sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==}
|
||||
dev: false
|
||||
|
||||
/eventsource/2.0.2:
|
||||
resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dev: false
|
||||
|
||||
/execa/5.1.1:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
Reference in New Issue
Block a user