Convert styling to Tailwind (#15)
* Initialize tailwind * Convert styling to Tailwind * Add console output for CSS build * Allow reading the feeds from an environment variable * Change link styling and no-script time format * Fix ellipses
This commit is contained in:
parent
58d8f68825
commit
2932a6276e
11 changed files with 345 additions and 216 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,4 +1,6 @@
|
||||||
node_modules/*
|
node_modules/*
|
||||||
public/index.html
|
public/index.html
|
||||||
|
public/styles.css
|
||||||
dist/*
|
dist/*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
configs/feeds.json
|
||||||
|
|
|
||||||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -1,100 +0,0 @@
|
||||||
{
|
|
||||||
"Comics": [
|
|
||||||
"http://www.catanacomics.com/rss",
|
|
||||||
"http://feeds.feedburner.com/InvisibleBread",
|
|
||||||
"https://hejibits.com/rss#_=_",
|
|
||||||
"http://www.hrwiki.org/wiki/Special:Updates",
|
|
||||||
"http://rockpapercynic.tumblr.com/rss"
|
|
||||||
],
|
|
||||||
"Podcasts & Videos": [
|
|
||||||
"https://lilyandsam.show/feed",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UC3g-w83Cb5pEAu5UmRrge-A",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCakAg8hC_RFJm4RI3DlD7SA",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCHZOwvEh9FAG95RO3PWhe5g",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCvVsD2hFZRgKNH7x5Q1wwug",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCDq5v10l4wkV5-ZBIJJFbzQ",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCMkbjxvwur30YrFWw8kpSaw",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCSuT9FSddzI6W5Bij9XwtmA",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCqqJQ_cXSat0KIAVfIfKkVA",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCuvSqzfO_LV_QzHdmEj84SQ",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCSdma21fnJzgmPodhC9SJ3g",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCj1VqrHhDte54oLgPG4xpuQ",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCy0tKL1T7wFoYcxCe0xjN6Q",
|
|
||||||
"https://www.youtube.com/feeds/videos.xml?channel_id=UCsvn_Po0SmunchJYOWpOxMg"
|
|
||||||
],
|
|
||||||
"Friends and Family": [
|
|
||||||
"https://www.goodreads.com/review/list_rss/132710826?shelf=read",
|
|
||||||
"https://joekhoury.blog/feed/",
|
|
||||||
"https://mastodon.social/users/samwarnick.rss"
|
|
||||||
],
|
|
||||||
"Games": [
|
|
||||||
"https://www.nomanssky.com/news/feed/",
|
|
||||||
"https://www.lexaloffle.com/bbs/feed.php?uid=1",
|
|
||||||
"https://www.factorio.com/blog/rss",
|
|
||||||
"https://kill-the-newsletter.com/feeds/pghhn8aaf264tukg.xml",
|
|
||||||
"http://www.suppermariobroth.com/rss",
|
|
||||||
"http://unknownworlds.com/subnautica/feed/",
|
|
||||||
"https://store.steampowered.com/feeds/news/app/1675200/?cc=US&l=english"
|
|
||||||
],
|
|
||||||
"Finance": [
|
|
||||||
"http://feeds.feedburner.com/MrMoneyMustache",
|
|
||||||
"http://feeds.feedburner.com/DoctorOfCredit",
|
|
||||||
"https://kill-the-newsletter.com/feeds/tgor1vrwcf3f0uyo.xml",
|
|
||||||
"https://kill-the-newsletter.com/feeds/5q4gs79dfh32yr3h.xml"
|
|
||||||
],
|
|
||||||
"News": [
|
|
||||||
"http://www.economist.com/rss/the_world_this_week_rss.xml",
|
|
||||||
"https://newsroom.churchofjesuschrist.org/rss",
|
|
||||||
"http://feeds.feedburner.com/LdsChurchGrowth",
|
|
||||||
"https://www.sltrib.com/arc/outboundfeeds/news/?outputType=xml",
|
|
||||||
"https://www.ksl.com/rss/news/news_utah",
|
|
||||||
"https://www.readtangle.com/posts/rss/"
|
|
||||||
],
|
|
||||||
"Parenting": [
|
|
||||||
"https://technosapiens.substack.com/feed",
|
|
||||||
"https://parentdata.org/feed/"
|
|
||||||
],
|
|
||||||
"Fashion": [
|
|
||||||
"http://www.heddels.com/feed/",
|
|
||||||
"http://putthison.com/rss",
|
|
||||||
"https://articlesofinterest.substack.com/feed",
|
|
||||||
"https://dappered.com/feed/",
|
|
||||||
"https://dieworkwear.com/feed/",
|
|
||||||
"http://fromsqualortoballer.com/rss",
|
|
||||||
"https://fabricateurialist.substack.com/feed",
|
|
||||||
"https://www.menswearmusings.com/feed/",
|
|
||||||
"https://www.permanentstyle.com/feed"
|
|
||||||
],
|
|
||||||
"Products": [
|
|
||||||
"https://jellyfin.org/index.xml",
|
|
||||||
"https://gitlab.com/CalcProgrammer1/OpenRGB/-/tags?format=atom",
|
|
||||||
"http://feeds.feedburner.com/psblog",
|
|
||||||
"https://matrix.org/blog/feed/",
|
|
||||||
"https://brave.com/feed/",
|
|
||||||
"https://bitwarden.com/blog/feed.xml"
|
|
||||||
],
|
|
||||||
"Humor": [
|
|
||||||
"https://kill-the-newsletter.com/feeds/d90ng280lhh3p2nq.xml",
|
|
||||||
"https://aiweirdness.com/rss",
|
|
||||||
"http://mcmansionhell.tumblr.com/rss"
|
|
||||||
],
|
|
||||||
"Other": [
|
|
||||||
"https://kill-the-newsletter.com/feeds/400aiwxy5ox6ao0d.xml",
|
|
||||||
"http://brandonsanderson.com/feed/",
|
|
||||||
"https://kill-the-newsletter.com/feeds/j3c4wngg2hpjmdt8.xml",
|
|
||||||
"https://astralcodexten.substack.com/feed/"
|
|
||||||
],
|
|
||||||
"Tech News": [
|
|
||||||
"https://samwarnick.com/feed.rss",
|
|
||||||
"http://feeds.feedburner.com/CssTricks",
|
|
||||||
"http://chriscoyier.net/feed/",
|
|
||||||
"https://jenniferdaniel.substack.com/feed/",
|
|
||||||
"http://www.gamingonlinux.com/article_rss.php",
|
|
||||||
"http://www.theverge.com/rss/full.xml",
|
|
||||||
"https://hnrss.org/frontpage?points=70"
|
|
||||||
],
|
|
||||||
"Emulation": [
|
|
||||||
"http://melonds.kuribo64.net/rss.php",
|
|
||||||
"https://dolphin-emu.org/blog/feeds/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
<title>Carter's RSS Feeds</title>
|
<title>Carter's RSS Feeds</title>
|
||||||
<link rel="icon" href="news-emoji.svg" sizes="any" type="image/svg+xml">
|
<link rel="icon" href="news-emoji.svg" sizes="any" type="image/svg+xml">
|
||||||
<link rel="stylesheet" href="style.css" />
|
<link rel="stylesheet" href="styles.css" />
|
||||||
<link rel="manifest" href="manifest.json" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
<script type="module" defer>
|
<script type="module" defer>
|
||||||
/**
|
/**
|
||||||
|
|
@ -98,35 +98,45 @@
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="font-system bg-arc-background text-base m-4">
|
||||||
<header>
|
<header class="text-2xl text-arc-title">
|
||||||
<h1>📰 Carter's RSS Feeds</h1>
|
<h1>📰 Carter's RSS Feeds</h1>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main class="grid gap-4 grid-cols-[repeat(auto-fill,_minmax(300px,_1fr))] mt-2">
|
||||||
{% for group, feeds in data %}
|
{% for group, feeds in data %}
|
||||||
<section>
|
<section class="">
|
||||||
<h2>{{ group }}</h2>
|
<h2 class="text-xl">{{ group }}</h2>
|
||||||
{% for feed in feeds %}
|
{% for feed in feeds %}
|
||||||
<details>
|
<details class="group">
|
||||||
<summary>
|
<summary class="cursor-pointer p-2 rounded-xl mt-1 transition-colors group-has-[.has-recent]:border-2 group-has-[.has-recent]:border-arc-title hover:bg-arc-hover flex flex-row items-center">
|
||||||
<span class="feed-title">{{ feed.title }}</span>
|
{% if feed.hasRecent %}
|
||||||
<span class="feed-url" title="{{feed.feed}}">({{ feed.feed }})</span>
|
<div class="text-xl mr-2" aria-label="Feed has recent items">✳︎</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="w-full">
|
||||||
|
<div>{{ feed.title }}</div>
|
||||||
|
<div class="text-sm whitespace-nowrap text-ellipsis w-11/12 overflow-hidden text-arc-subtitle" title="{{feed.feed}}">({{ feed.feed }})</div>
|
||||||
|
</div>
|
||||||
</summary>
|
</summary>
|
||||||
<ul>
|
<ul class="max-h-64 overflow-y-auto list-none m-0">
|
||||||
{% for item in feed.items %}
|
{% for item in feed.items %}
|
||||||
<li class="{% if item.isRecent %}has-recent{% endif %}">
|
<li class="{% if item.isRecent %}has-recent {% endif %} flex flex-row items-center p-2 rounded-xl mt-1 transition-colors hover:bg-arc-hover">
|
||||||
<div>
|
{% if item.isRecent %}
|
||||||
<a class="article-title" href="{{ item.link }}" target="_blank" rel="noopener norefferer nofollow">{{
|
<div class="text-xl mr-2" aria-label="Item is recent">✳︎</div>
|
||||||
item.title | safe}}</a>
|
|
||||||
</div>
|
|
||||||
<div class="article-timestamp">
|
|
||||||
<relative-time data-time="{{item.timestamp}}">{{ item.timestamp | relative}}</relative-time>
|
|
||||||
</div>
|
|
||||||
{% if yazzyUrl %}
|
|
||||||
<div class="article-links">
|
|
||||||
<a href="{{ yazzyUrl }}/{{ item.link }}" target="_blank" rel="noreferrer noopener">yazzy</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<a class="no-underline hover:text-inherit" href="{{ item.link }}" target="_blank" rel="noopener norefferer nofollow">{{
|
||||||
|
item.title | safe}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-arc-subtitle">
|
||||||
|
<relative-time data-time="{{item.timestamp}}">{{ item.timestamp | formatDateTime}}</relative-time>
|
||||||
|
</div>
|
||||||
|
{% if yazzyUrl %}
|
||||||
|
<div class="text-sm text-arc-subtitle">
|
||||||
|
<a href="{{ yazzyUrl }}/{{ item.link }}" target="_blank" rel="noreferrer noopener">yazzy</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -136,17 +146,17 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</main>
|
</main>
|
||||||
{% if errors | length > 0 %}
|
{% if errors | length > 0 %}
|
||||||
<h2>Errors</h2>
|
<h2 class="text-xl">Errors</h2>
|
||||||
<p>There were errors trying to parse these feeds:</p>
|
<p>There were errors trying to parse these feeds:</p>
|
||||||
<ul class="errors">
|
<ul class="errors">
|
||||||
{% for error in errors %}
|
{% for error in errors %}
|
||||||
<li>{{ error }}</li>
|
<li class="break-all">{{ error }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<footer>
|
<footer>
|
||||||
<hr>
|
<hr>
|
||||||
<p>Last updated <relative-time data-time="{{ now }}">{{ now | relative}}</relative-time>.</p>
|
<p>Last updated <relative-time data-time="{{ now }}">{{ now | formatDateTime}}</relative-time>.</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="https://github.com/carterworks/rss-reader">View on GitHub</a>
|
<a href="https://github.com/carterworks/rss-reader">View on GitHub</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"build": "bun src/index.ts",
|
"build": "NODE_ENV=production bun src/index.ts",
|
||||||
"check": "biome check --write ./{src,config,public} ./*.json --no-errors-on-unmatched"
|
"check": "biome check --write ./{src,config,public} ./*.{json,js} --no-errors-on-unmatched",
|
||||||
|
"dev": "bun src/index.ts"
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"name": "George Mandis",
|
"name": "George Mandis",
|
||||||
|
|
@ -36,6 +37,7 @@
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/nunjucks": "^3.2.2",
|
"@types/nunjucks": "^3.2.2",
|
||||||
"@types/xml2js": "^0.4.11",
|
"@types/xml2js": "^0.4.11",
|
||||||
|
"tailwindcss": "^3.4.6",
|
||||||
"tslib": "^2.5.3",
|
"tslib": "^2.5.3",
|
||||||
"typescript": "^5.1.3",
|
"typescript": "^5.1.3",
|
||||||
"typescript-eslint": "^7.13.1"
|
"typescript-eslint": "^7.13.1"
|
||||||
|
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
:root {
|
|
||||||
--color-bg: oklch(97.1% 0.024 88.23);
|
|
||||||
--color-hover: oklch(94.48% 0.024 88.23);
|
|
||||||
--color-text: oklch(24.74% 0.024 88.23);
|
|
||||||
--color-new: oklch(94.48% 0.024 38.23);
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: system-ui;
|
|
||||||
font-size: 18px;
|
|
||||||
background: var(--color-bg);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
display: grid;
|
|
||||||
gap: 1em;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
section ul {
|
|
||||||
max-height: 250px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-icon {
|
|
||||||
height: 1em;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
cursor: pointer;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary,
|
|
||||||
details li {
|
|
||||||
padding: 0.5em;
|
|
||||||
border-radius: 12px;
|
|
||||||
margin-block-start: 4px;
|
|
||||||
transition: background cubic-bezier(0.39, 0.575, 0.565, 1) 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary:hover,
|
|
||||||
details li:hover {
|
|
||||||
background: var(--color-hover)
|
|
||||||
}
|
|
||||||
|
|
||||||
details:has(li.has-recent) {
|
|
||||||
|
|
||||||
summary,
|
|
||||||
li.has-recent {
|
|
||||||
background: var(--color-new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.errors li {
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-url {
|
|
||||||
color: #aaa;
|
|
||||||
white-space: nowrap;
|
|
||||||
display: inline-block;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-timestamp,
|
|
||||||
.feed-url,
|
|
||||||
.article-links {
|
|
||||||
font-size: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
details ul {
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
3
public/styles.input.css
Normal file
3
public/styles.input.css
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
19
src/index.ts
19
src/index.ts
|
|
@ -18,6 +18,7 @@ import Parser from "rss-parser";
|
||||||
import type { FeedItem, Feeds } from "./@types/bubo";
|
import type { FeedItem, Feeds } from "./@types/bubo";
|
||||||
import { render } from "./renderer.js";
|
import { render } from "./renderer.js";
|
||||||
import {
|
import {
|
||||||
|
buildCSS,
|
||||||
getBuboInfo,
|
getBuboInfo,
|
||||||
getFeedList,
|
getFeedList,
|
||||||
getLink,
|
getLink,
|
||||||
|
|
@ -26,9 +27,21 @@ import {
|
||||||
parseFeed,
|
parseFeed,
|
||||||
} from "./utilities.js";
|
} from "./utilities.js";
|
||||||
|
|
||||||
|
const cssInput = "./public/styles.input.css";
|
||||||
|
const cssOutput = "./public/styles.css";
|
||||||
|
const minifyCss = process.env.NODE_ENV === "production";
|
||||||
|
await buildCSS(minifyCss, cssInput, cssOutput);
|
||||||
|
|
||||||
const buboInfo = await getBuboInfo();
|
const buboInfo = await getBuboInfo();
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
const feedList = await getFeedList();
|
const feedOptions: Parameters<typeof getFeedList>[0] = {
|
||||||
|
feeds: process.env.FEEDS,
|
||||||
|
feedFilePath: process.env.FEEDS
|
||||||
|
? ""
|
||||||
|
: process.env.FEED_FILE ?? "../config/feeds.json",
|
||||||
|
};
|
||||||
|
console.log("feedOptions", JSON.stringify(feedOptions, null, 2));
|
||||||
|
const feedList = await getFeedList(feedOptions);
|
||||||
const feedListLength =
|
const feedListLength =
|
||||||
Object.entries(feedList).flat(2).length - Object.keys(feedList).length;
|
Object.entries(feedList).flat(2).length - Object.keys(feedList).length;
|
||||||
|
|
||||||
|
|
@ -90,7 +103,7 @@ const finishBuild: () => void = async () => {
|
||||||
data: sortedFeeds,
|
data: sortedFeeds,
|
||||||
errors: errors,
|
errors: errors,
|
||||||
info: buboInfo,
|
info: buboInfo,
|
||||||
yazzyUrl
|
yazzyUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
// write the output to public/index.html
|
// write the output to public/index.html
|
||||||
|
|
@ -140,7 +153,7 @@ const processFeed =
|
||||||
item.link = getLink(item);
|
item.link = getLink(item);
|
||||||
const timestamp = new Date(Number.parseInt(item.timestamp));
|
const timestamp = new Date(Number.parseInt(item.timestamp));
|
||||||
const eightHoursAgo = new Date();
|
const eightHoursAgo = new Date();
|
||||||
eightHoursAgo.setHours(eightHoursAgo.getHours() - 8);
|
eightHoursAgo.setHours(eightHoursAgo.getHours() - 24);
|
||||||
item.isRecent = timestamp > eightHoursAgo;
|
item.isRecent = timestamp > eightHoursAgo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ env.addFilter("formatTime", (dateString): string => {
|
||||||
return !Number.isNaN(date.getTime()) ? date.toLocaleTimeString() : dateString;
|
return !Number.isNaN(date.getTime()) ? date.toLocaleTimeString() : dateString;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
env.addFilter("formatDateTime", (dateString): string => {
|
||||||
|
const date: Date = new Date(Number.parseInt(dateString));
|
||||||
|
return !Number.isNaN(date.getTime()) ? date.toLocaleString() : dateString;
|
||||||
|
});
|
||||||
|
|
||||||
env.addGlobal("now", new Date().getTime());
|
env.addGlobal("now", new Date().getTime());
|
||||||
|
|
||||||
// load the template
|
// load the template
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { $ } from "bun";
|
||||||
/*
|
/*
|
||||||
There's a little inconsistency with how feeds report certain things like
|
There's a little inconsistency with how feeds report certain things like
|
||||||
title, links and timestamps. These helpers try to normalize that bit and
|
title, links and timestamps. These helpers try to normalize that bit and
|
||||||
|
|
@ -75,11 +76,18 @@ export async function parseFeed(response: Response): Promise<JSONValue> {
|
||||||
return (rssFeed && rssFeed) || (jsonFeed && jsonFeed) || {};
|
return (rssFeed && rssFeed) || (jsonFeed && jsonFeed) || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFeedList = async (): Promise<JSONValue> => {
|
export const getFeedList = async ({
|
||||||
|
feedFilePath,
|
||||||
|
feeds,
|
||||||
|
}: { feedFilePath?: string; feeds?: string }): Promise<JSONValue> => {
|
||||||
|
if (feeds) {
|
||||||
|
return JSON.parse(feeds);
|
||||||
|
}
|
||||||
|
if (!feedFilePath) {
|
||||||
|
throw new Error("No feed list provided");
|
||||||
|
}
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
(
|
(await readFile(new URL(feedFilePath, import.meta.url))).toString(),
|
||||||
await readFile(new URL("../config/feeds.json", import.meta.url))
|
|
||||||
).toString(),
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -88,3 +96,18 @@ export const getBuboInfo = async (): Promise<JSONValue> => {
|
||||||
(await readFile(new URL("../package.json", import.meta.url))).toString(),
|
(await readFile(new URL("../package.json", import.meta.url))).toString(),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const buildCSS = async (
|
||||||
|
minify: boolean,
|
||||||
|
input: string,
|
||||||
|
destination: string,
|
||||||
|
): Promise<void> => {
|
||||||
|
const output =
|
||||||
|
await $`bun x tailwindcss -i ${input} ${minify ? "--minify" : ""} -o ${destination}`;
|
||||||
|
if (output.exitCode !== 0) {
|
||||||
|
const err = new TextDecoder().decode(output.stderr);
|
||||||
|
throw new Error(`Building tailwind failed: ${err}`);
|
||||||
|
}
|
||||||
|
console.log(`Successfully built CSS to ${destination}`);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
|
||||||
251
tailwind.config.js
Normal file
251
tailwind.config.js
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
const plugin = require("tailwindcss/plugin");
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ["./config/*.html"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
// from https://github.com/system-fonts/modern-font-stacks
|
||||||
|
/* System UI fonts are those native to the operating system interface.
|
||||||
|
* They are highly legible and easy to read at small sizes, contains
|
||||||
|
* many font weights, and is ideal for UI elements.
|
||||||
|
*/
|
||||||
|
system: ["system-ui", "sans-serif"],
|
||||||
|
/* Transitional typefaces are a mix between Old Style and Modern
|
||||||
|
* typefaces that was developed during The Enlightenment. One of the
|
||||||
|
* most famous examples of a Transitional typeface is Times New Roman,
|
||||||
|
* which was developed for the Times of London newspaper.
|
||||||
|
*/
|
||||||
|
transitional: [
|
||||||
|
"Charter",
|
||||||
|
"'Bitstream Charter'",
|
||||||
|
"'Sitka Text'",
|
||||||
|
"Cambria",
|
||||||
|
"serif",
|
||||||
|
],
|
||||||
|
/* Old Style typefaces are characterized by diagonal stress, low
|
||||||
|
* contrast between thick and thin strokes, and rounded serifs, and
|
||||||
|
* were developed in the Renaissance period. One of the most famous
|
||||||
|
* examples of an Old Style typeface is Garamond.
|
||||||
|
*/
|
||||||
|
"old-style": [
|
||||||
|
"'Iowan Old Style'",
|
||||||
|
"'Palatino Linotype'",
|
||||||
|
"'URW Palladio L'",
|
||||||
|
"P052",
|
||||||
|
"serif",
|
||||||
|
],
|
||||||
|
/* Humanist typefaces are characterized by their organic, calligraphic
|
||||||
|
* forms and low contrast between thick and thin strokes. These
|
||||||
|
* typefaces are inspired by the handwriting of the Renaissance period
|
||||||
|
* and are often considered to be more legible and easier to read than
|
||||||
|
* other sans-serif typefaces.
|
||||||
|
*/
|
||||||
|
humanist: [
|
||||||
|
"Seravek",
|
||||||
|
"'Gill Sans Nova'",
|
||||||
|
"Ubuntu",
|
||||||
|
"Calibri",
|
||||||
|
"'DejaVu Sans'",
|
||||||
|
"source-sans-pro",
|
||||||
|
"sans-serif",
|
||||||
|
],
|
||||||
|
/* Geometric Humanist typefaces are characterized by their clean,
|
||||||
|
* geometric forms and uniform stroke widths. These typefaces are often
|
||||||
|
* considered to be modern and sleek in appearance, and are often used
|
||||||
|
* for headlines and other display purposes. Futura is a famous example
|
||||||
|
* of this classification.
|
||||||
|
*/
|
||||||
|
"gemoetric-humanist": [
|
||||||
|
"Avenir",
|
||||||
|
"Montserrat",
|
||||||
|
"Corbel",
|
||||||
|
"'URW Gothic'",
|
||||||
|
"source-sans-pro",
|
||||||
|
"sans-serif",
|
||||||
|
],
|
||||||
|
/* Classical Humanist typefaces are characterized by how the strokes
|
||||||
|
* subtly widen as they reach the stroke terminals without ending in a
|
||||||
|
* serif. These typefaces are inspired by classical Roman capitals and
|
||||||
|
* the stone-carving on Renaissance-period tombstones.
|
||||||
|
*/
|
||||||
|
"classical-humanist": [
|
||||||
|
"Optima",
|
||||||
|
"Candara",
|
||||||
|
"'Noto Sans'",
|
||||||
|
"source-sans-pro",
|
||||||
|
"sans-serif",
|
||||||
|
],
|
||||||
|
/* Neo-Grotesque typefaces are a style of sans-serif that was developed
|
||||||
|
* in the late 19th and early 20th centuries and is characterized by its
|
||||||
|
* clean, geometric forms and uniform stroke widths. One of the most
|
||||||
|
* famous examples of a Neo-Grotesque typeface is Helvetica.
|
||||||
|
*/
|
||||||
|
"neo-grotesque": [
|
||||||
|
"Inter",
|
||||||
|
"Roboto",
|
||||||
|
"'Helvetica Neue'",
|
||||||
|
"'Arial Nova'",
|
||||||
|
"'Nimbus Sans'",
|
||||||
|
"Arial",
|
||||||
|
"sans-serif",
|
||||||
|
],
|
||||||
|
/* Monospace Slab Serif typefaces are characterized by their fixed-width
|
||||||
|
* letters, which have the same width regardless of their shape, and its
|
||||||
|
* simple, geometric forms. Used to emulate typewriter output for
|
||||||
|
* reports, tabular work and technical documentation.
|
||||||
|
*/
|
||||||
|
"monospace-slab-serif": [
|
||||||
|
"'Nimbus Mono PS'",
|
||||||
|
"'Courier New'",
|
||||||
|
"monospace",
|
||||||
|
],
|
||||||
|
/* Monospace Code typefaces are specifically designed for use in
|
||||||
|
* programming and other technical applications. These typefaces are
|
||||||
|
* characterized by their monospaced design, which means that all
|
||||||
|
* letters and characters have the same width, and their clear, legible
|
||||||
|
* forms.
|
||||||
|
*/
|
||||||
|
"monospace-code": [
|
||||||
|
"ui-monospace",
|
||||||
|
"'Cascadia Code'",
|
||||||
|
"'Source Code Pro'",
|
||||||
|
"Menlo",
|
||||||
|
"Consolas",
|
||||||
|
"'DejaVu Sans Mono'",
|
||||||
|
"monospace",
|
||||||
|
],
|
||||||
|
/* Industrial typefaces originated in the late 19th century and was
|
||||||
|
* heavily influenced by the advancements in technology and industry
|
||||||
|
* during that time. Industrial typefaces are characterized by their
|
||||||
|
* bold, sans-serif letterforms, simple and straightforward appearance,
|
||||||
|
* and the use of straight lines and geometric shapes.
|
||||||
|
*/
|
||||||
|
industrial: [
|
||||||
|
"Bahnschrift",
|
||||||
|
"'DIN Alternate'",
|
||||||
|
"'Franklin Gothic Medium'",
|
||||||
|
"'Nimbus Sans Narrow'",
|
||||||
|
"sans-serif-condensed",
|
||||||
|
"sans-serif",
|
||||||
|
],
|
||||||
|
/* Rounded typefaces are characterized by the rounded curved letterforms
|
||||||
|
* and give a softer, friendlier appearance. The rounded edges give the
|
||||||
|
* typeface a more organic and
|
||||||
|
* playful feel, making it suitable for use in informal or child-friendly
|
||||||
|
* designs. The rounded sans-serif style has been popular since the 1950s,
|
||||||
|
* and it continues to be widely used in advertising, branding, and
|
||||||
|
* other forms of graphic design.
|
||||||
|
*/
|
||||||
|
"rounded-sans": [
|
||||||
|
"ui-rounded",
|
||||||
|
"'Hiragino Maru Gothic ProN'",
|
||||||
|
"Quicksand",
|
||||||
|
"Comfortaa",
|
||||||
|
"Manjari",
|
||||||
|
"'Arial Rounded MT'",
|
||||||
|
"'Arial Rounded MT Bold'",
|
||||||
|
"Calibri",
|
||||||
|
"source-sans-pro",
|
||||||
|
"sans-serif",
|
||||||
|
],
|
||||||
|
/* Slab Serif typefaces are characterized by the presence of thick,
|
||||||
|
* block-like serifs on the ends of each letterform. These serifs are
|
||||||
|
* usually unbracketed, meaning they do not have any curved or tapered
|
||||||
|
* transitions to the main stroke of the letter.
|
||||||
|
*/
|
||||||
|
"slab-serif": [
|
||||||
|
"Rockwell",
|
||||||
|
"'Rockwell Nova'",
|
||||||
|
"'Roboto Slab'",
|
||||||
|
"'DejaVu Serif'",
|
||||||
|
"'Sitka Small'",
|
||||||
|
"serif",
|
||||||
|
],
|
||||||
|
/* Antique typefaces, also known as Egyptians, are a subset of serif
|
||||||
|
* typefaces that were popular in the 19th century. They are
|
||||||
|
* characterized by their block-like serifs and thick uniform stroke
|
||||||
|
* weight.
|
||||||
|
*/
|
||||||
|
antique: [
|
||||||
|
"Superclarendon",
|
||||||
|
"'Bookman Old Style'",
|
||||||
|
"'URW Bookman'",
|
||||||
|
"'URW Bookman L'",
|
||||||
|
"'Georgia Pro'",
|
||||||
|
"Georgia",
|
||||||
|
"serif",
|
||||||
|
],
|
||||||
|
/* Didone typefaces, also known as Modern typefaces, are characterized
|
||||||
|
* by the high contrast between thick and thin strokes, vertical stress,
|
||||||
|
* and hairline serifs with no bracketing. The Didone style emerged in
|
||||||
|
* the late 18th century and gained popularity during the 19th century.
|
||||||
|
*/
|
||||||
|
didone: [
|
||||||
|
"Didot",
|
||||||
|
"'Bodoni MT'",
|
||||||
|
"'Noto Serif Display'",
|
||||||
|
"'URW Palladio L'",
|
||||||
|
"P052",
|
||||||
|
"Sylfaen",
|
||||||
|
"serif",
|
||||||
|
],
|
||||||
|
/* Handwritten typefaces are designed to mimic the look and feel of
|
||||||
|
* handwriting. Despite the vast array of handwriting styles, this font
|
||||||
|
* stack tend to adopt a more informal and everyday style of handwriting.
|
||||||
|
*/
|
||||||
|
handwritten: [
|
||||||
|
"'Segoe Print'",
|
||||||
|
"'Bradley Hand'",
|
||||||
|
"Chilanka",
|
||||||
|
"TSCu_Comic",
|
||||||
|
"casual",
|
||||||
|
"cursive",
|
||||||
|
],
|
||||||
|
emoji: [
|
||||||
|
"'Apple Color Emoji'",
|
||||||
|
"'Segoe UI Emoji'",
|
||||||
|
"'Segoe UI Symbol'",
|
||||||
|
"'Noto Color Emoji'",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
/*
|
||||||
|
* The Arc browser (can) expose its theme color to the pages beneath as
|
||||||
|
* CSS custom properties.
|
||||||
|
*/
|
||||||
|
arc: {
|
||||||
|
"background-simple": "var(--arc-background-simple-color, #FDF8EBFF)",
|
||||||
|
maxContrast: "var(--arc-palette-maxContrastColor, #6F540AFF)",
|
||||||
|
minContrast: "var(--arc-palette-minContrastColor, #FDF8EBFF)",
|
||||||
|
hover: "var(--arc-palette-hover, #E2DDCAFF)",
|
||||||
|
foregroundPrimary: "var(--arc-palette-foregroundPrimary, #EBB218FF)",
|
||||||
|
foregroundSecondary:
|
||||||
|
"var(--arc-palette-foregroundSecondary, #EBB218FF)",
|
||||||
|
foregroundTertiary:
|
||||||
|
"var(--arc-palette-foregroundTertiary, #FDF8EBFF)",
|
||||||
|
title: "var(--arc-palette-title, #211900FF)",
|
||||||
|
subtitle: "var(--arc-palette-subtitle, #C5BB96FF)",
|
||||||
|
background: "var(--arc-palette-background, #F1EEE5FF)",
|
||||||
|
backgroundExtra: "var(--arc-palette-backgroundExtra, #FEFDFCFF)",
|
||||||
|
cutout: "var(--arc-palette-cutoutColor, #FDF8EBFF)",
|
||||||
|
focus: "var(--arc-palette-focus, #B7A97CFF)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
plugin(({ addBase, theme }) => {
|
||||||
|
addBase({
|
||||||
|
a: {
|
||||||
|
textDecoration: "underline",
|
||||||
|
transition: "color 0.2s",
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": {
|
||||||
|
color: theme("colors.arc.subtitle"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue