This commit is contained in:
Morten Olsen
2023-03-29 15:00:24 +02:00
parent a04d5f5d71
commit 35ef204433
12 changed files with 441 additions and 48 deletions

View File

@@ -23,7 +23,7 @@ const createProfile = ({ cwd, path, bundler }: ProfileOptions) => {
...data, ...data,
imageUrl: createImage({ imageUrl: createImage({
image: imagePath, image: imagePath,
format: 'webp', format: 'avif',
bundler, bundler,
}), }),
imagePath, imagePath,

View File

@@ -1,40 +1,54 @@
import express, { Express } from 'express'; import express, { Express } from 'express';
import { Bundler } from '../bundler'; import { Bundler } from '../bundler';
import { extname } from 'path';
const createServer = (bundler: Bundler): Express => { const createServer = (bundler: Bundler): Express => {
const app = express(); const getAsset = (path: string) => {
app.use((req, res) => {
let path = req.path;
let asset = bundler.get(path); let asset = bundler.get(path);
if (!asset) { if (!asset) {
path = path.endsWith('/') ? path + 'index.html' : path + '/index.html'; path = path.endsWith('/') ? path + 'index.html' : path + '/index.html';
asset = bundler.get(path); 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) { if (asset) {
const ext = extname(path);
asset.data asset.data
.then((data) => { .then((data) => {
if (ext === '.html') { res.send(data.content);
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);
}
}) })
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);

View File

@@ -12,14 +12,27 @@ type GlobOptions<T> = {
const defaultCreate = (a: any) => a; const defaultCreate = (a: any) => a;
const createGlob = <T = string>({ cwd, pattern, create = defaultCreate }: GlobOptions<T>) => { 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 glob = new Observable(async () => {
const files = await fastGlob(pattern, { cwd }); 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 }); const watcher = watchGlob(pattern, { cwd });
watcher.on('add', (path) => { 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; return glob;
}); });

View 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();
}
};

View File

@@ -5,6 +5,7 @@ import { Asset, Bundler } from '../../bundler';
import { Observable } from '../../observable'; import { Observable } from '../../observable';
import { ServerStyleSheet } from 'styled-components'; import { ServerStyleSheet } from 'styled-components';
import { resolve } from 'path'; import { resolve } from 'path';
import { createScript } from '../script';
type PageOptions = { type PageOptions = {
path: string; path: string;
@@ -12,11 +13,28 @@ type PageOptions = {
props: Observable<any>; props: Observable<any>;
bundler: Bundler; bundler: Bundler;
}; };
let devClientUrl: string | undefined;
const createPage = (options: PageOptions) => { const createPage = (options: PageOptions) => {
const data = Observable.combine({ const data = Observable.combine({
template: options.template, template: options.template,
props: options.props, 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 page = data.pipe(async ({ template, props }) => {
const sheet = new ServerStyleSheet(); const sheet = new ServerStyleSheet();
const helmetContext: FilledContext = {} as any; const helmetContext: FilledContext = {} as any;
@@ -33,6 +51,7 @@ const createPage = (options: PageOptions) => {
const css = sheet.getStyleTags(); const css = sheet.getStyleTags();
const headHtml = [ const headHtml = [
css, css,
`<script src="${devClientUrl}"></script>`,
helmet.title?.toString(), helmet.title?.toString(),
helmet.priority?.toString(), helmet.priority?.toString(),
helmet.meta?.toString(), helmet.meta?.toString(),

View File

@@ -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: 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. - 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. - 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. - 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? So can we create a setup that gets away from these issues?
@@ -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: I have separated the events going into Redux into two types:
* control events, which directly controls a device - control events, which directly controls a device
* environment events represent sensor data coming in (push on a button, motion sensor triggering, TV playing, etc.) - 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. Now comes the part I have feared, where I need to draw a diagram.
...sorry ...sorry
![Image description](./graph.png) ![Image description](./graph.png)
So this shows our final setup. 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. 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. Would this work? I don't know. I am trying to get more skilled at machine learning to find out.

View File

@@ -80,7 +80,7 @@
{ {
\noindent\begin{columns} \noindent\begin{columns}
\noindent{\Large \textbf{#1}} \hfill {\small #2} \\ \noindent{\Large \textbf{#1}} \hfill {\small #2} \\
\textit{#3} \textit{#3}\dotfill
\ifnum\columncount>2 \ifnum\columncount>2
\vfill\null\columnbreak \vfill\null\columnbreak
\else \else

View File

@@ -1,4 +1,4 @@
import styled, { createGlobalStyle } from 'styled-components'; import styled, { createGlobalStyle, keyframes } from 'styled-components';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import { Jumbo } from '../../typography'; import { Jumbo } from '../../typography';
import { createTheme, ThemeProvider } from '../../theme'; import { createTheme, ThemeProvider } from '../../theme';
@@ -13,6 +13,163 @@ const GlobalStyle = createGlobalStyle`
background-color: ${({ theme }) => theme.colors.background}; background-color: ${({ theme }) => theme.colors.background};
color: ${({ theme }) => theme.colors.foreground}; 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)` const Title = styled(Jumbo)`
@@ -24,7 +181,9 @@ const Title = styled(Jumbo)`
min-height: 100%; min-height: 100%;
margin-bottom: 10rem; margin-bottom: 10rem;
margin-top: 10rem; margin-top: 10rem;
animation: ${TitleAnimation} 0.4s ease-in-out;
@media only screen and (max-width: 900px) { @media only screen and (max-width: 900px) {
z-index: 1;
padding-bottom: 2rem; padding-bottom: 2rem;
} }
`; `;
@@ -40,6 +199,7 @@ const ArticleTitleWord = styled(Jumbo)`
background: ${({ theme }) => theme.colors.primary}; background: ${({ theme }) => theme.colors.primary};
color: ${({ theme }) => theme.colors.foreground}; color: ${({ theme }) => theme.colors.foreground};
@media only screen and (max-width: 900px) { @media only screen and (max-width: 900px) {
z-index: 1;
font-size: 2.5rem; font-size: 2.5rem;
line-height: 3.1rem; line-height: 3.1rem;
} }
@@ -55,9 +215,22 @@ const Wrapper = styled.div`
height: 100%; height: 100%;
`; `;
const ArticleAnimation = keyframes`
0% {
opacity: 0;
transform: translateY(100%);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
`;
const ArticleWrapper = styled.article` const ArticleWrapper = styled.article`
font-size: 1.1rem; font-size: 1.1rem;
font-family: 'Merriweather', serif; font-family: 'Merriweather', serif;
animation: ${ArticleAnimation} 0.5s ease-in-out;
> p, > p,
ul, 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` const Side = styled.aside`
position: fixed; position: fixed;
right: 0; right: 0;
@@ -198,6 +383,7 @@ const Side = styled.aside`
width: 40%; width: 40%;
height: 100%; height: 100%;
clip-path: polygon(40% 0%, 100% 0%, 100% 100%, 0% 100%, 0% 50%); 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) { @media only screen and (max-width: 900px) {
position: static; position: static;
@@ -254,12 +440,10 @@ const ArticlePage: Page<'article'> = ({ article, profile, pdfUrl }) => {
<ThemeProvider theme={createTheme({ baseColor: article.color })}> <ThemeProvider theme={createTheme({ baseColor: article.color })}>
<Helmet> <Helmet>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <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 rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link <title>
href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Black+Ops+One&family=Merriweather:wght@400;700&display=swap" {article.title} by {profile.name}
rel="stylesheet" </title>
/>
</Helmet> </Helmet>
<GlobalStyle /> <GlobalStyle />
<Wrapper> <Wrapper>

View File

@@ -16,6 +16,151 @@ const GlobalStyle = createGlobalStyle`
background-color: ${({ theme }) => theme.colors.background}; background-color: ${({ theme }) => theme.colors.background};
color: ${({ theme }) => theme.colors.foreground}; 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` const Hero = styled.div`
@@ -112,12 +257,8 @@ const FrontPage: Page<'frontpage'> = ({ articles, profile }) => {
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Helmet> <Helmet>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <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 rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link <title>Morten Olsen</title>
href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Black+Ops+One&family=Merriweather:wght@400;700&display=swap"
rel="stylesheet"
/>
</Helmet> </Helmet>
<GlobalStyle /> <GlobalStyle />
<Sheet color="#c85279"> <Sheet color="#c85279">

View File

@@ -22,6 +22,7 @@
"commander": "^10.0.0", "commander": "^10.0.0",
"ejs": "^3.1.9", "ejs": "^3.1.9",
"eventemitter3": "^5.0.0", "eventemitter3": "^5.0.0",
"eventsource": "^2.0.2",
"express": "^4.18.2", "express": "^4.18.2",
"fast-glob": "^3.2.12", "fast-glob": "^3.2.12",
"glob-watcher": "^5.0.5", "glob-watcher": "^5.0.5",
@@ -46,6 +47,7 @@
"@react-native-community/eslint-config": "^3.2.0", "@react-native-community/eslint-config": "^3.2.0",
"@types/chroma-js": "^2.4.0", "@types/chroma-js": "^2.4.0",
"@types/ejs": "^3.1.2", "@types/ejs": "^3.1.2",
"@types/eventsource": "^1.1.11",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/glob-watcher": "^5.0.2", "@types/glob-watcher": "^5.0.2",
"@types/jest": "^29.5.0", "@types/jest": "^29.5.0",

13
pnpm-lock.yaml generated
View File

@@ -11,6 +11,7 @@ specifiers:
'@rollup/plugin-typescript': ^11.0.0 '@rollup/plugin-typescript': ^11.0.0
'@types/chroma-js': ^2.4.0 '@types/chroma-js': ^2.4.0
'@types/ejs': ^3.1.2 '@types/ejs': ^3.1.2
'@types/eventsource': ^1.1.11
'@types/express': ^4.17.17 '@types/express': ^4.17.17
'@types/glob-watcher': ^5.0.2 '@types/glob-watcher': ^5.0.2
'@types/jest': ^29.5.0 '@types/jest': ^29.5.0
@@ -25,6 +26,7 @@ specifiers:
ejs: ^3.1.9 ejs: ^3.1.9
eslint: ^8.36.0 eslint: ^8.36.0
eventemitter3: ^5.0.0 eventemitter3: ^5.0.0
eventsource: ^2.0.2
express: ^4.18.2 express: ^4.18.2
fast-glob: ^3.2.12 fast-glob: ^3.2.12
glob-watcher: ^5.0.5 glob-watcher: ^5.0.5
@@ -62,6 +64,7 @@ dependencies:
commander: 10.0.0 commander: 10.0.0
ejs: 3.1.9 ejs: 3.1.9
eventemitter3: 5.0.0 eventemitter3: 5.0.0
eventsource: 2.0.2
express: 4.18.2 express: 4.18.2
fast-glob: 3.2.12 fast-glob: 3.2.12
glob-watcher: 5.0.5 glob-watcher: 5.0.5
@@ -86,6 +89,7 @@ devDependencies:
'@react-native-community/eslint-config': 3.2.0_dzq4sglfl3yv4kxodieycp32vy '@react-native-community/eslint-config': 3.2.0_dzq4sglfl3yv4kxodieycp32vy
'@types/chroma-js': 2.4.0 '@types/chroma-js': 2.4.0
'@types/ejs': 3.1.2 '@types/ejs': 3.1.2
'@types/eventsource': 1.1.11
'@types/express': 4.17.17 '@types/express': 4.17.17
'@types/glob-watcher': 5.0.2 '@types/glob-watcher': 5.0.2
'@types/jest': 29.5.0 '@types/jest': 29.5.0
@@ -1095,6 +1099,10 @@ packages:
resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==}
dev: false dev: false
/@types/eventsource/1.1.11:
resolution: {integrity: sha512-L7wLDZlWm5mROzv87W0ofIYeQP5K2UhoFnnUyEWLKM6UBb0ZNRgAqp98qE5DkgfBXdWfc2kYmw9KZm4NLjRbsw==}
dev: true
/@types/express-serve-static-core/4.17.33: /@types/express-serve-static-core/4.17.33:
resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
dependencies: dependencies:
@@ -2645,6 +2653,11 @@ packages:
resolution: {integrity: sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==} resolution: {integrity: sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==}
dev: false dev: false
/eventsource/2.0.2:
resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==}
engines: {node: '>=12.0.0'}
dev: false
/execa/5.1.1: /execa/5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'} engines: {node: '>=10'}