Initial commit

This commit is contained in:
Hellings 2026-10-23 08:53:07 -04:00
commit d9858adf16
108 changed files with 6821 additions and 0 deletions

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# @muddle/amezing
Port of the Ametrine theme (by Daudix) as a muddle module.

8
components/alert.spug Normal file
View file

@ -0,0 +1,8 @@
props
type: 'note' | 'tip' | 'important' | 'warning' | 'caution'
content?: string | (() => string | Promise<string>)
blockquote(class='markdown-alert markdown-alert-' + type)
if typeof content === 'function'
!= await content()
else
= content ?? ''

View file

@ -0,0 +1,29 @@
props
pages: { title: string; href: string; description?: string; date?: string; updated?: string; banner?: string; bannerStyle?: string; articleClass?: string; draft?: boolean; categories?: { name: string; href: string; description?: string }[] }[]
div.article-list
each page in pages
article(class=page.articleClass style=page.bannerStyle)
if page.banner
a.open-post(href=page.href aria-label=page.title)
img.has-alpha.no-hover(src=page.banner decoding="async" loading="lazy")
div
a(href=page.href)
h3= page.title
if page.description
div.description
!= page.description
div.article-details(data-slot="article-details")
div.items
if page.date
div.item
i.ph.ph-calendar(aria-hidden="true")
time(datetime=page.date pubdate)= page.date
if page.updated
div.item
i.ph.ph-clock-clockwise(aria-hidden="true")
time(datetime=page.updated pubdate)= page.updated
if page.categories
div.item
i.ph.ph-book-bookmark(aria-hidden="true")
each category in page.categories
a(href=category.href title=category.description)= category.name

View file

@ -0,0 +1,14 @@
props
previousHref?: string
previousTitle?: string
nextHref?: string
nextTitle?: string
nav.article-nav
if previousHref
a.article-nav-item.prev(href=previousHref)
div.nav-arrow Previous
span.article-title= previousTitle
if nextHref
a.article-nav-item.next(href=nextHref)
div.nav-arrow Next
span.article-title= nextTitle

31
components/article.spug Normal file
View file

@ -0,0 +1,31 @@
props
title: string
description?: string
article
h1
= title
div.article-details(data-slot="article-details")
div.items
if date
div.item
i.ph.ph-calendar(aria-hidden="true")
time(datetime=isoDate pubdate)= date
if updated
div.item
i.ph.ph-clock-clockwise(aria-hidden="true")
time(datetime=isoUpdated pubdate)= updated
if authors
div.item
i.ph.ph-users(aria-hidden="true")
span= authors
if readingTime
div.item
i.ph.ph-clock-countdown(aria-hidden="true")
span= readingTime + ' min read'
if body
div.article-body
!= body
if notes
ul.statements
each statement in notes
li= statement

View file

@ -0,0 +1,5 @@
<button class="audio-button" {{ if accentStyle }}{{ accentStyle | safe }}{{ end }}>
{%- set icon = load_data(path="icons/phosphor/" ~ "music" ~ ".svg", required=false) -%}
{%- if icon -%}{{ icon | safe }}{{ else }}🎵{{ end }}
<span class="audio-button-label">Audio</span>
</button>

7
components/audio.spug Normal file
View file

@ -0,0 +1,7 @@
props
url: string
name?: string
button.audio(type="button" data-audio=url)
if name
span= name
i.ph.ph-speaker-high(aria-hidden="true")

7
components/banner.spug Normal file
View file

@ -0,0 +1,7 @@
props
src: string
alt?: string
bannerPixels?: boolean
div#banner(data-slot="banner" class=bannerPixels ? 'pixels' : '')
div
img.has-alpha.no-hover(src=src decoding="async" loading="lazy" alt=alt ?? '')

133
components/base.spug Normal file
View file

@ -0,0 +1,133 @@
doctype html
html(lang="en" style=accentStyle ?? '')
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1")
meta(name="description" content=description ?? '')
title= title
link(rel="stylesheet" href="/_muddle/amezing.css")
link(rel="stylesheet" href="/_muddle/styles.css")
link(rel="stylesheet" href="https://unpkg.com/@phosphor-icons/web@2.1.1/src/regular/style.css")
body
a#skip-to-content.link(href="#main-content" tabindex="0") Skip to content
header#site-header.firefox-unblur
ul#site-sidebar-controls
li#expand-sidebar-li
input#expand-sidebar.visually-hidden(type="checkbox" name="expand-sidebar" autocomplete="off")
label(for="expand-sidebar" title="Toggle sidebar")
i.ph.ph-sidebar(aria-hidden="true")
ul#site-sidebar-modes(role="radiogroup")
li
input#mode-nav.visually-hidden(type="radio" name="mode" checked)
label(for="mode-nav" title="Navigation mode")
i.ph.ph-compass(aria-hidden="true")
li
input#mode-meta.visually-hidden(type="radio" name="mode")
label(for="mode-meta" title="Meta mode")
i.ph.ph-info(aria-hidden="true")
span.title= title
ul.buttons
li
details.closable
summary(title="Feed")
i.ph.ph-rss-simple(aria-hidden="true")
ul.dropdown
li
a(href="/atom.xml") Atom
li
a(href="/rss.xml") RSS
if searchHref
li
button#search(title="Search" aria-haspopup="dialog")
i.ph.ph-magnifying-glass(aria-hidden="true")
div#site-sidebar
a#site-sidebar-header(href="#top" class=logo ? 'has-icon' : '')
if logo
img(width="36" height="36" class="has-alpha no-hover" src=logo alt="")
else
i.ph.ph-arrow-line-up(aria-hidden="true")
div
span= siteTitle ?? title
span Back to top
div#site-sidebar-content
nav#sidebar-nav.overshoot
ul
li
a(href="/" aria-current=(currentPath ?? '/') === '/' ? 'page' : '')
i.ph.ph-house(aria-hidden="true")
= siteTitle ?? title
each entry in nav
if entry.children
li
details.closable(open=entry.open)
summary
= entry.name
div.line
ul.dropdown
each child in entry.children
li
a(href=child.href aria-current=child.active ? 'page' : '' class=child.external ? 'external' : '')
if child.icon
i.ph.ph-(child.icon)(aria-hidden="true")
if child.label
span= child.label
else
span= child.name
else
li
a(href=entry.href aria-current=entry.active ? 'page' : '' class=(entry.external ?? entry.href.indexOf('http') === 0) ? 'external' : '')
if entry.icon
i.ph.ph-(entry.icon)(aria-hidden="true")
if entry.label
span= entry.label
else
span= entry.name
div#sidebar-meta.overshoot
if metaTitle ?? note
div.card
div.card-title
div.line
span.title= metaTitle ?? 'Meta'
div.line
if note
div.note= note
main#main-content
if content
!= await content()
footer#site-footer
div
if copyright
p.copyright= copyright
if poweredBy ?? undefined
p= 'Powered by '
a.link.external(href="https://www.getzola.org") Zola
= ' and '
a.link.external(href="https://ametrine.daudix.one") Ametrine
if sourceHref ?? undefined
p
a.link.external(href=sourceHref) Source
= ' | Updated ' + timestamp + ' UTC'
if socials
ul.socials
each social in socials
li
a(href=social.href rel="me external")
i.ph.ph-(social.name)(aria-hidden="true")
if webrings
ul.webrings
each webring in webrings
li.webring
a.link.external(href=webring.href)= webring.name
a.link.previous(href=webring.previous title="Previous")
i.ph.ph-arrow-left
if webring.random
a.link.random(href=webring.random title="Random")
i.ph.ph-dice-three
a.link.next(href=webring.next title="Next")
i.ph.ph-arrow-right
if note
div.note= note
script(src="/_muddle/amezing/closable.js" defer)
script(src="/_muddle/amezing/paginator.js" defer)
script(src="/_muddle/amezing/deanchor.js" defer)
script(src="/_muddle/amezing/marquee.js" defer)

32
components/comments.spug Normal file
View file

@ -0,0 +1,32 @@
{%- set alert_icon = load_data(path="icons/phosphor/" ~ icon ~ ".svg", required=false) -%}
{%- if not alert_icon -%}
{%- set alert_icon = "info" -%}
{%- endif -%}
<div class="comments" {{ if accentStyle }}{{ accentStyle | safe }}{{ end }}>
<div class="comments-header">
<h3 class="comments-title">Comments</h3>
<a class="comments-toggle" href="#">
{%- set icon = load_data(path="icons/phosphor/" ~ "message-circle" ~ ".svg", required=false) -%}
{%- if icon -%}{{ icon | safe }}{{ else }}💬{{ end }}
</a>
</div>
<div class="comments-list">
{%- set comments = load_data(path="data/comments.toml", required=false) -%}
{%- if comments -%}
{%- for comment in comments -%}
<div class="comment">
<div class="comment-header">
<span class="comment-author">{{ comment.author }}</span>
<span class="comment-date">{{ comment.date }}</span>
</div>
<p class="comment-content">{{ comment.content }}</p>
</div>
{%- endfor -%}
{%- else -%}
<div class="comments-empty">
<div class="comments-empty-icon">💭</div>
<p>No comments yet. Be the first to share your thoughts!</p>
</div>
{%- endif -%}
</div>
</div>

11
components/crt.spug Normal file
View file

@ -0,0 +1,11 @@
props
fontSize?: string
label?: string
content?: string | (() => string | Promise<string>)
pre.crt(style=fontSize ? 'font-size: ' + fontSize + ';' : '' role=label ? 'img' : false aria-label=label ? label : false)
code(aria-hidden=label ? false : true)
span
if typeof content === 'function'
!= await content()
else
!= content ?? ''

1
components/deanchor.spug Normal file
View file

@ -0,0 +1 @@
<span class="deanchor">{{ text | striptags }}</span>

5
components/emoji.spug Normal file
View file

@ -0,0 +1,5 @@
props
path: string
big?: boolean
title?: string
img.emoji(class=big ? 'big' : false src=path title=title ?? '' width="24" height="24")

15
components/footnotes.spug Normal file
View file

@ -0,0 +1,15 @@
<div class="footnotes-container" {{ if accentStyle }}{{ accentStyle | safe }}{{ end }}>
{%- set footnotes = load_data(path="data/footnotes.toml", required=false) -%}
{%- if footnotes -%}
<div class="footnotes-list">
{%- for footnote in footnotes -%}
<div class="footnote">
<sup class="footnote-reference">
<a href="#fn-{{ loop.index }}">{{ loop.index }}</a>
</sup>
<p id="fn-{{ loop.index }}" class="footnote-content">{{ footnote.content }}</p>
</div>
{%- endfor -%}
</div>
{%- endif -%}
</div>

4
components/fuse.spug Normal file
View file

@ -0,0 +1,4 @@
<div class="fuse">
<input type="text" class="fuse-input" placeholder="Search...">
<div class="fuse-results"></div>
</div>

3
components/icon.spug Normal file
View file

@ -0,0 +1,3 @@
props
name: string
i.ph.ph-(name)(aria-hidden="true")

22
components/image.spug Normal file
View file

@ -0,0 +1,22 @@
props
src: string
alt?: string
url?: string
caption?: string
width?: number
height?: number
lazy?: boolean
if caption
figure
if url
a(href=url)
img(src=src alt=alt ?? '' decoding=lazy ? 'async' : false loading=lazy ? 'lazy' : false)
else
img(src=src alt=alt ?? '' decoding=lazy ? 'async' : false loading=lazy ? 'lazy' : false)
figcaption!= caption
else
if url
a(href=url)
img(src=src alt=alt ?? '' decoding=lazy ? 'async' : false loading=lazy ? 'lazy' : false)
else
img(src=src alt=alt ?? '' decoding=lazy ? 'async' : false loading=lazy ? 'lazy' : false)

14
components/link.spug Normal file
View file

@ -0,0 +1,14 @@
props
name: string
href: string
icon?: string
external?: boolean
active?: boolean
afterActive?: boolean
li
a(href=href + (!external && !active && afterActive ? '#↓' : !external && !active ? '#↑' : '') aria-current=active ? 'page' : false class=external ? 'external' : false)
if icon
i.ph.ph-(icon)(aria-hidden="true")
else
i.icon.fallback= name.slice(0, 2)
= name

5
components/mastodon.spug Normal file
View file

@ -0,0 +1,5 @@
props
host: string
user: string
id: string
iframe.mastodon-embed(src='https://' + host + '/@' + user + '/' + id + '/embed')

View file

@ -0,0 +1,9 @@
props
overshoot?: boolean
overshootRow?: boolean
content?: string | (() => string | Promise<string>)
div.overflow-auto(class=overshoot ? 'overshoot' : false class=overshootRow ? 'overshoot-row' : false)
if typeof content === 'function'
!= await content()
else
!= content ?? ''

32
components/paginator.spug Normal file
View file

@ -0,0 +1,32 @@
props
counter: string
first?: string
previous?: string
next?: string
last?: string
nav.paginator
if first
a.paginator-item.first(href=first title="First page")
i.ph.ph-caret-line-left(aria-hidden="true")
else
span.paginator-item.first(aria-disabled="true")
i.ph.ph-caret-line-left(aria-hidden="true")
if previous
a.paginator-item.previous(href=previous rel="prev" title="Previous page")
i.ph.ph-caret-left(aria-hidden="true")
else
span.paginator-item.previous(aria-disabled="true")
i.ph.ph-caret-left(aria-hidden="true")
span.paginator-counter= counter
if next
a.paginator-item.next(href=next rel="next" title="Next page")
i.ph.ph-caret-right(aria-hidden="true")
else
span.paginator-item.next(aria-disabled="true")
i.ph.ph-caret-right(aria-hidden="true")
if last
a.paginator-item.last(href=last title="Last page")
i.ph.ph-caret-line-right(aria-hidden="true")
else
span.paginator-item.last(aria-disabled="true")
i.ph.ph-caret-line-right(aria-hidden="true")

View file

@ -0,0 +1,10 @@
<div class="search-fuse">
<div class="search-fuse-input-wrap">
<svg class="search-fuse-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input type="text" class="search-fuse-input" placeholder="Search..." autocomplete="off">
</div>
<div class="search-fuse-results"></div>
</div>

View file

@ -0,0 +1,11 @@
props
indexHref?: string
dialog#search-modal(data-slot="search-modal" closed ? aria-hidden="true")
div#search-modal-content
label(for="search-input" class="visually-hidden") Search
div
input#search-input(placeholder="Search" autocomplete="off" type="search")
div(title="Press / to search")
i.ph.ph-keyboard(aria-hidden="true")
div#search-results.overshoot
span#more-matches-text(hidden) More matches

View file

@ -0,0 +1,4 @@
<a id="skip-to-content" class="skip-to-content-link" href="#main-content" tabindex="0">
{%- set icon = load_data(path="icons/phosphor/" ~ "arrow-up-right" ~ ".svg", required=false) -%}
{%- if icon -%}{{ icon | safe }}{{ else }}Skip to content{{ end }}
</a>

11
components/statement.spug Normal file
View file

@ -0,0 +1,11 @@
props
kind: 'archive' | 'trigger' | 'disclaimer'
content?: string | (() => string | Promise<string>)
div.statement-container(class=kind)
strong.title
i.ph.ph-(kind === 'archive' ? 'box-arrow-down' : 'warning')(aria-hidden="true")
span= kind === 'archive' ? 'Archived' : kind === 'trigger' ? 'Trigger warning' : 'Disclaimer'
if typeof content === 'function'
!= await content()
else
!= content ?? ''

View file

@ -0,0 +1,9 @@
props
items: { name: string; href: string; count?: number }[]
ul#tag-chips
each item in items
li
a(href=item.href)
= item.name
if item.count
span.counter= item.count

21
components/video.spug Normal file
View file

@ -0,0 +1,21 @@
props
sources: { src: string, type?: string }[]
caption?: string
width?: number
height?: number
autoplay?: boolean
controls?: boolean
loop?: boolean
muted?: boolean
playsinline?: boolean
alt?: string
if caption
figure
video(preload="auto" aria-title=alt ?? '' width=width ?? false height=height ?? false autoplay=autoplay ?? false controls=controls ?? true loop=loop ?? false muted=muted ?? false playsinline=playsinline ?? false)
each source in sources
source(src=source.src type='video/' + (source.type ?? source.src.split('.').pop()))
figcaption!= caption
else
video(preload="auto" aria-title=alt ?? '' width=width ?? false height=height ?? false autoplay=autoplay ?? false controls=controls ?? true loop=loop ?? false muted=muted ?? false playsinline=playsinline ?? false)
each source in sources
source(src=source.src type='video/' + (source.type ?? source.src.split('.').pop()))

4
components/vimeo.spug Normal file
View file

@ -0,0 +1,4 @@
props
id: string
autoplay?: boolean
iframe.vimeo-embed(src='https://player.vimeo.com/video/' + id + (autoplay ? '?autoplay=1' : '') allow="autoplay; fullscreen; picture-in-picture" allowfullscreen=true)

5
components/youtube.spug Normal file
View file

@ -0,0 +1,5 @@
props
id: string
autoplay?: boolean
start?: number
iframe.youtube-embed(src='https://www.youtube-nocookie.com/embed/' + id + (autoplay && start ? '?autoplay=1&start=' + start : autoplay ? '?autoplay=1' : start ? '?start=' + start : '') allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=true)

BIN
fonts/commissioner.woff2 Normal file

Binary file not shown.

BIN
fonts/commit-mono.woff2 Normal file

Binary file not shown.

161
index.ts Normal file
View file

@ -0,0 +1,161 @@
/**
* Muddle integration of the Ametrine theme.
*
* Registers the theme's component set, exposes the compiled theme css and
* serves the vendored behavior scripts / self-hosted fonts.
*/
import { fileURLToPath } from "node:url";
import { readFile } from "node:fs/promises";
import type {
Integration,
Mounts,
} from "muddle";
import type { Views } from "muddle/views";
export interface AmezingAccentPreset { name: string; light: string; dark: string }
/** Accent presets from ametrine/data/accent_presets.toml. */
export const ACCENT_PRESETS: Record<string, AmezingAccentPreset> = {
red: { name: "red", light: "hsl(342.0779, 72%, 58%)", dark: "hsl(345.18988, 100%, 69%)" },
orange: { name: "orange", light: "hsl(15.771428, 74%, 54%)", dark: "hsl(19.731544, 96%, 70%)" },
yellow: { name: "yellow", light: "hsl(34.639175, 91%, 42%)", dark: "hsl(44.70588, 100%, 70%)" },
green: { name: "green", light: "hsl(153.7815, 61%, 38%)", dark: "hsl(90, 59%, 66%)" },
blue: { name: "blue", light: "hsl(192, 71%, 38%)", dark: "hsl(186.42857, 71%, 69%)" },
purple: { name: "purple", light: "hsl(254.11766, 44%, 55%)", dark: "hsl(249.88234, 77%, 78%)" },
teal: { name: "teal", light: "hsl(180, 65%, 35%)", dark: "hsl(183, 85%, 65%)" },
pink: { name: "pink", light: "hsl(333.88236, 69%, 58%)", dark: "hsl(336.78634, 100%, 70%)" },
cyan: { name: "cyan", light: "hsl(195, 71%, 35%)", dark: "hsl(193.41046, 72%, 70%)" },
indigo: { name: "indigo", light: "hsl(232.05884, 61%, 42%)", dark: "hsl(234.61538, 73%, 71%)" },
amber: { name: "amber", light: "hsl(38.57695, 94%, 44%)", dark: "hsl(40.35484, 100%, 72%)" },
ochrea: { name: "ochrea", light: "hsl(45.75298, 95%, 45%)", dark: "hsl(46.09091, 100%, 78%)" },
lime: { name: "lime", light: "hsl(86.25406, 60%, 42%)", dark: "hsl(86.84211, 93%, 71%)" },
maroon: { name: "maroon", light: "hsl(321.81819, 26%, 41%)", dark: "hsl(322.10526, 66%, 66%)" },
navy: { name: "navy", light: "hsl(216, 44%, 38%)", dark: "hsl(218, 69%, 69%)" },
olive: { name: "olive", light: "hsl(81, 36%, 41%)", dark: "hsl(81, 64%, 65%)" },
slate: { name: "slate", light: "hsl(212.53197, 15%, 44%)", dark: "hsl(212.90323, 61%, 72%)" },
};
function parseHsl(value: string): [string, string, string] | undefined {
const match = /hsl\(\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)\s*\)/.exec(value);
return match ? [match[1]!, match[2]!, match[3]!] : undefined;
}
/** Builds the inline accent variables used by the theme, mirrors components/accent_color.html. */
export function accentStyle(accent: string | [string, string?]): string {
const inputs = Array.isArray(accent) ? accent : [accent];
const light = ACCENT_PRESETS[inputs[0] ?? ""]?.light ?? inputs[0];
const dark = ACCENT_PRESETS[inputs[1] ?? inputs[0] ?? ""]?.dark ?? inputs[1] ?? ACCENT_PRESETS[inputs[0] ?? ""]?.dark ?? inputs[0];
const lightHsl = parseHsl(light ?? "");
const darkHsl = parseHsl(dark ?? "");
if (!lightHsl || !darkHsl) throw new Error(`Unknown accent color: ${accent}`);
return `--accent-light-h:${lightHsl[0]};--accent-light-s:${lightHsl[1]};--accent-light-l:${lightHsl[2]};--accent-dark-h:${darkHsl[0]};--accent-dark-s:${darkHsl[1]};--accent-dark-l:${darkHsl[2]};`;
}
const COMPONENTS = [
"base",
"article",
"article-list",
"article-details",
"article-nav",
"paginator",
"alert",
"banner",
"icon",
"search-modal",
"tag-chips",
"audio",
"image",
"video",
"vimeo",
"youtube",
"mastodon",
"link",
"overflow-auto",
"crt",
"emoji",
"statement",
"comments",
"footnotes",
"skip-to-content",
"marquee",
"audio-button",
"deanchor",
"fuse",
"search-fuse",
] as const;
function componentTemplates(): [string, string][] {
return COMPONENTS.map((name) => [`amezing-${name}`, fileURLToPath(new URL(`./components/${name}.spug`, import.meta.url))]);
}
interface TemplateViews {
component(name: string, component: unknown): unknown;
engine?: { renderComponent(path: string, props: Record<string, unknown>, context: { locals: Record<string, unknown>; params: Record<string, string> }): Promise<string> };
}
function registerAll(views: TemplateViews, accent: string | undefined): void {
for (const [name, template] of componentTemplates()) {
views.component(name, {
async render(props: Record<string, unknown>, context: { locals: Record<string, unknown>; params: Record<string, string> }) {
return views.engine!.renderComponent(template, { accentStyle: accent, ...props }, context);
},
});
}
}
/** Registers the Ametrine components on the views registry. */
export function registerAmezing(views: Views, options: { accentColor?: string | [string, string?] } = {}): void {
registerAll(views as unknown as Parameters<typeof registerAll>[0], options.accentColor ? accentStyle(options.accentColor) : undefined);
}
/** Compiled theme stylesheet (Citrine base + Ametrine layout). */
export async function amezingStyles(): Promise<string> {
const [theme, vars] = await Promise.all([
readFile(new URL("./styles/ametrine.css", import.meta.url), "utf8"),
readFile(new URL("./styles/vars.css", import.meta.url), "utf8"),
]);
return vars + "\n" + theme;
}
export const amezingScripts: Record<string, string> = {
"closable.js": "scripts/closable.js",
"paginator.js": "scripts/paginator.js",
"deanchor.js": "scripts/deanchor.js",
"marquee.js": "scripts/marquee.js",
"audio-button.js": "scripts/audio-button.js",
"fuse.js": "scripts/fuse.js",
"search-fuse.js": "scripts/search-fuse.js",
};
export const amezingFonts: Record<string, string> = {
"/fonts/commissioner.woff2": "fonts/commissioner.woff2",
"/fonts/commit-mono.woff2": "fonts/commit-mono.woff2",
};
export function amezingMount(options: { accentColor?: string | [string, string?] } = {}): Mounts {
const accent = options.accentColor ? accentStyle(options.accentColor) : undefined;
return {
views: (views) => registerAll(views as unknown as Parameters<typeof registerAll>[0], accent),
routes: (route) => {
for (const [routePath, file] of Object.entries(amezingScripts)) {
route("GET", `/_muddle/amezing/${routePath}`, async () =>
new Response(await readFile(new URL(`./${file}`, import.meta.url), "utf8"), {
headers: { "content-type": "text/javascript; charset=utf-8" },
}));
}
for (const [routePath, file] of Object.entries(amezingFonts)) {
route("GET", routePath, async () =>
new Response(await readFile(new URL(`./${file}`, import.meta.url)), {
headers: { "content-type": "font/woff2" },
}));
}
},
styles: { "/_muddle/amezing.css": () => amezingStyles() },
};
}
/** Convenience integration. */
export function amezingIntegration(options: { accentColor?: string | [string, string?] } = {}): Integration {
return { name: "amezing", mounts: [amezingMount(options)] };
}

7
package.json Normal file
View file

@ -0,0 +1,7 @@
{
"name": "@muddle/amezing",
"version": "0.1.0",
"type": "module",
"exports": { ".": "./index.ts", "./styles": "./styles/ametrine.css" },
"peerDependencies": { "muddle": "*" }
}

7
sass/amezing.scss Normal file
View file

@ -0,0 +1,7 @@
/*
* Ametrine port for muddle. Combines the Citrine base with the Ametrine layout.
*/
@use "fonts";
@use "citrine/style";
@use "style" as ametrine;

View file

@ -0,0 +1,5 @@
// Converts px values to rem
// rpx(1) --> 0.0625rem
@function rpx($px-value) {
@return ($px-value / 16) + rem;
}

View file

@ -0,0 +1,82 @@
@mixin icon($mode: normal) {
@if $mode == normal {
font-style: normal;
font-variant: normal;
font-weight: normal;
line-height: 1;
user-select: none;
text-transform: none;
-webkit-user-select: none;
display: inline-block;
box-sizing: content-box;
} @else if $mode == pseudo {
-webkit-mask-image: var(--icon);
-webkit-mask-size: cover;
display: block;
mask-image: var(--icon);
mask-size: cover;
background-color: currentColor;
width: 1em;
height: 1em;
content: "";
}
}
@mixin gradient-fill($mode: normal) {
@if $mode == normal {
box-shadow: var(--edge-highlight), var(--shadow-glow);
background-image: linear-gradient(
to bottom right,
var(--accent-color),
var(--accent-highlight)
);
color: var(--contrast-color);
text-shadow: var(--text-shadow-glow);
} @else if $mode == hover {
box-shadow: var(--edge-highlight);
background-color: var(--accent-color);
color: var(--contrast-color);
}
}
@mixin card {
box-shadow: var(--edge-highlight), var(--shadow);
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
padding: 1rem;
}
@mixin chip {
transition:
all var(--transition),
transform var(--transition-bezier);
box-shadow: var(--edge-highlight);
border-radius: var(--rounded-corner-small);
background-color: var(--bg-muted-1);
padding-inline: 0.375rem;
text-decoration: none;
}
@mixin easing-mask($direction: to bottom) {
--mask-image: linear-gradient(
to #{$direction},
hsl(0, 0%, 0%) 0%,
hsla(0, 0%, 0%, 0.987) 8.1%,
hsla(0, 0%, 0%, 0.951) 15.5%,
hsla(0, 0%, 0%, 0.896) 22.5%,
hsla(0, 0%, 0%, 0.825) 29%,
hsla(0, 0%, 0%, 0.741) 35.3%,
hsla(0, 0%, 0%, 0.648) 41.2%,
hsla(0, 0%, 0%, 0.55) 47.1%,
hsla(0, 0%, 0%, 0.45) 52.9%,
hsla(0, 0%, 0%, 0.352) 58.8%,
hsla(0, 0%, 0%, 0.259) 64.7%,
hsla(0, 0%, 0%, 0.175) 71%,
hsla(0, 0%, 0%, 0.104) 77.5%,
hsla(0, 0%, 0%, 0.049) 84.5%,
hsla(0, 0%, 0%, 0.013) 91.9%,
hsla(0, 0%, 0%, 0) 100%
);
mask-image: var(--mask-image);
-webkit-mask-image: var(--mask-image);
}

View file

@ -0,0 +1,570 @@
@use "functions" as *;
@use "mixins" as *;
@mixin variables($modes: "light") {
@each $mode in $modes {
@if $mode == "light" {
// COLORS
--bg-overlay: linear-gradient(
rgba(255, 255, 255, var(--dim-opacity)),
rgba(255, 255, 255, var(--dim-opacity))
);
--fg-color: #29242a;
--fg-contrast: #fcfcfa;
--red-fg: hsl(342.0779, 72%, 58%);
--red-h: 342.0779;
--red-s: 72%;
--red-l: 58%;
--orange-fg: hsl(15.771428, 74%, 54%);
--orange-h: 15.771428;
--orange-s: 74%;
--orange-l: 54%;
--yellow-fg: hsl(34.639175, 91%, 42%);
--yellow-h: 34.639175;
--yellow-s: 91%;
--yellow-l: 42%;
--green-fg: hsl(153.7815, 61%, 38%);
--green-h: 153.7815;
--green-s: 61%;
--green-l: 38%;
--blue-fg: hsl(192, 71%, 38%);
--blue-h: 192;
--blue-s: 71%;
--blue-l: 38%;
--purple-fg: hsl(254.11766, 44%, 55%);
--purple-h: 254.11766;
--purple-s: 44%;
--purple-l: 55%;
--red-bg-1: hsla(
var(--red-h),
var(--red-s),
var(--red-l),
var(--color-opacity-1)
);
--red-bg-2: hsla(
var(--red-h),
var(--red-s),
var(--red-l),
var(--color-opacity-2)
);
--orange-bg-1: hsla(
var(--orange-h),
var(--orange-s),
var(--orange-l),
var(--color-opacity-1)
);
--orange-bg-2: hsla(
var(--orange-h),
var(--orange-s),
var(--orange-l),
var(--color-opacity-2)
);
--yellow-bg-1: hsla(
var(--yellow-h),
var(--yellow-s),
var(--yellow-l),
var(--color-opacity-1)
);
--yellow-bg-2: hsla(
var(--yellow-h),
var(--yellow-s),
var(--yellow-l),
var(--color-opacity-2)
);
--green-bg-1: hsla(
var(--green-h),
var(--green-s),
var(--green-l),
var(--color-opacity-1)
);
--green-bg-2: hsla(
var(--green-h),
var(--green-s),
var(--green-l),
var(--color-opacity-2)
);
--blue-bg-1: hsla(
var(--blue-h),
var(--blue-s),
var(--blue-l),
var(--color-opacity-1)
);
--blue-bg-2: hsla(
var(--blue-h),
var(--blue-s),
var(--blue-l),
var(--color-opacity-2)
);
--purple-bg-1: hsla(
var(--purple-h),
var(--purple-s),
var(--purple-l),
var(--color-opacity-1)
);
--purple-bg-2: hsla(
var(--purple-h),
var(--purple-s),
var(--purple-l),
var(--color-opacity-2)
);
--red-fg-muted-1: hsla(
var(--red-h),
var(--red-s),
calc(var(--red-l) - var(--red-l) * 0.85),
var(--fg-muted-1-opacity)
);
--red-fg-muted-2: hsla(
var(--red-h),
var(--red-s),
calc(var(--red-l) - var(--red-l) * 0.85),
var(--fg-muted-2-opacity)
);
--orange-fg-muted-1: hsla(
var(--orange-h),
var(--orange-s),
calc(var(--orange-l) - var(--orange-l) * 0.85),
var(--fg-muted-1-opacity)
);
--orange-fg-muted-2: hsla(
var(--orange-h),
var(--orange-s),
calc(var(--orange-l) - var(--orange-l) * 0.85),
var(--fg-muted-2-opacity)
);
--yellow-fg-muted-1: hsla(
var(--yellow-h),
var(--yellow-s),
calc(var(--yellow-l) - var(--yellow-l) * 0.85),
var(--fg-muted-1-opacity)
);
--yellow-fg-muted-2: hsla(
var(--yellow-h),
var(--yellow-s),
calc(var(--yellow-l) - var(--yellow-l) * 0.85),
var(--fg-muted-2-opacity)
);
--green-fg-muted-1: hsla(
var(--green-h),
var(--green-s),
calc(var(--green-l) - var(--green-l) * 0.85),
var(--fg-muted-1-opacity)
);
--green-fg-muted-2: hsla(
var(--green-h),
var(--green-s),
calc(var(--green-l) - var(--green-l) * 0.85),
var(--fg-muted-2-opacity)
);
--blue-fg-muted-1: hsla(
var(--blue-h),
var(--blue-s),
calc(var(--blue-l) - var(--blue-l) * 0.85),
var(--fg-muted-1-opacity)
);
--blue-fg-muted-2: hsla(
var(--blue-h),
var(--blue-s),
calc(var(--blue-l) - var(--blue-l) * 0.85),
var(--fg-muted-2-opacity)
);
--purple-fg-muted-1: hsla(
var(--purple-h),
var(--purple-s),
calc(var(--purple-l) - var(--purple-l) * 0.85),
var(--fg-muted-1-opacity)
);
--purple-fg-muted-2: hsla(
var(--purple-h),
var(--purple-s),
calc(var(--purple-l) - var(--purple-l) * 0.85),
var(--fg-muted-2-opacity)
);
--shade-color: rgba(0, 0, 0, 0.07);
// COLORS (NEUTRAL)
--bg-muted-neutral-1: rgba(0, 0, 0, var(--color-opacity-1));
--bg-muted-neutral-2: rgba(0, 0, 0, var(--color-opacity-2));
--fg-muted-neutral-1: rgba(0, 0, 0, var(--fg-muted-1-opacity));
--fg-muted-neutral-2: rgba(0, 0, 0, var(--fg-muted-2-opacity));
// OPACITY
--scanlines-opacity: 0.4;
--fg-muted-1-opacity: 0.5;
--fg-muted-2-opacity: 0.6;
// SHADOWS
--edge-highlight: inset
0
#{rpx(-1)}
0
rgba(0, 0, 0, var(--color-opacity-1));
} @else if $mode == "dark" {
// COLORS
--bg-overlay: linear-gradient(
rgba(0, 0, 0, var(--dim-opacity)),
rgba(0, 0, 0, var(--dim-opacity))
);
--fg-color: #fcfcfa;
--fg-contrast: #29242a;
--red-fg: hsl(345.18988, 100%, 69%);
--red-h: 345.18988;
--red-s: 100%;
--red-l: 69%;
--orange-fg: hsl(19.731544, 96%, 70%);
--orange-h: 19.731544;
--orange-s: 96%;
--orange-l: 70%;
--yellow-fg: hsl(44.70588, 100%, 70%);
--yellow-h: 44.70588;
--yellow-s: 100%;
--yellow-l: 70%;
--green-fg: hsl(90, 59%, 66%);
--green-h: 90;
--green-s: 59%;
--green-l: 66%;
--blue-fg: hsl(186.42857, 71%, 69%);
--blue-h: 186.42857;
--blue-s: 71%;
--blue-l: 69%;
--purple-fg: hsl(249.88234, 77%, 78%);
--purple-h: 249.88234;
--purple-s: 77%;
--purple-l: 78%;
--red-fg-muted-1: hsla(
var(--red-h),
var(--red-s),
calc(var(--red-l) + (100% - var(--red-l)) * 0.85),
var(--fg-muted-1-opacity)
);
--red-fg-muted-2: hsla(
var(--red-h),
var(--red-s),
calc(var(--red-l) + (100% - var(--red-l)) * 0.85),
var(--fg-muted-2-opacity)
);
--orange-fg-muted-1: hsla(
var(--orange-h),
var(--orange-s),
calc(var(--orange-l) + (100% - var(--orange-l)) * 0.85),
var(--fg-muted-1-opacity)
);
--orange-fg-muted-2: hsla(
var(--orange-h),
var(--orange-s),
calc(var(--orange-l) + (100% - var(--orange-l)) * 0.85),
var(--fg-muted-2-opacity)
);
--yellow-fg-muted-1: hsla(
var(--yellow-h),
var(--yellow-s),
calc(var(--yellow-l) + (100% - var(--yellow-l)) * 0.85),
var(--fg-muted-1-opacity)
);
--yellow-fg-muted-2: hsla(
var(--yellow-h),
var(--yellow-s),
calc(var(--yellow-l) + (100% - var(--yellow-l)) * 0.85),
var(--fg-muted-2-opacity)
);
--green-fg-muted-1: hsla(
var(--green-h),
var(--green-s),
calc(var(--green-l) + (100% - var(--green-l)) * 0.85),
var(--fg-muted-1-opacity)
);
--green-fg-muted-2: hsla(
var(--green-h),
var(--green-s),
calc(var(--green-l) + (100% - var(--green-l)) * 0.85),
var(--fg-muted-2-opacity)
);
--blue-fg-muted-1: hsla(
var(--blue-h),
var(--blue-s),
calc(var(--blue-l) + (100% - var(--blue-l)) * 0.85),
var(--fg-muted-1-opacity)
);
--blue-fg-muted-2: hsla(
var(--blue-h),
var(--blue-s),
calc(var(--blue-l) + (100% - var(--blue-l)) * 0.85),
var(--fg-muted-2-opacity)
);
--purple-fg-muted-1: hsla(
var(--purple-h),
var(--purple-s),
calc(var(--purple-l) + (100% - var(--purple-l)) * 0.85),
var(--fg-muted-1-opacity)
);
--purple-fg-muted-2: hsla(
var(--purple-h),
var(--purple-s),
calc(var(--purple-l) + (100% - var(--purple-l)) * 0.85),
var(--fg-muted-2-opacity)
);
--shade-color: rgba(0, 0, 0, 0.25);
// COLORS (NEUTRAL)
--bg-muted-neutral-1: rgba(255, 255, 255, var(--color-opacity-1));
--bg-muted-neutral-2: rgba(255, 255, 255, var(--color-opacity-2));
--fg-muted-neutral-1: rgba(255, 255, 255, var(--fg-muted-1-opacity));
--fg-muted-neutral-2: rgba(255, 255, 255, var(--fg-muted-2-opacity));
// OPACITY
--scanlines-opacity: 1;
--fg-muted-1-opacity: 0.6;
--fg-muted-2-opacity: 0.7;
// SHADOWS
--edge-highlight: inset
0
#{rpx(1)}
0
rgba(255, 255, 255, var(--color-opacity-1));
} @else if $mode == "common" {
// WIDTHS
--content-width: #{rpx(720)};
--sidebar-width: clamp(
calc(var(--content-width) / 3),
20vw,
calc(var(--content-width) / 2)
);
// CORNERS
--rounded-corner: 0.75rem;
--rounded-corner-small: 0.5rem;
// FILTERS
--blur: blur(0.75rem) saturate(180%);
--printed: contrast(2) brightness(0.75) grayscale(1);
--drop-shadow: drop-shadow(0 0.08rem 0.1rem rgba(0, 0, 0, 0.35))
drop-shadow(0 0.4rem 0.7rem rgba(0, 0, 0, 0.18));
// FONTS
--font-system-ui: system-ui, sans-serif;
--font-ui-monospace: ui-monospace, monospace;
--font-emoji:
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
// FONT SIZES
--font-size-xx-small: 0.625rem;
--font-size-x-small: 0.75rem;
--font-size-small: 0.875rem;
--font-size-medium: 1rem;
--font-size-large: 1.25rem;
--font-size-x-large: 1.5rem;
--font-size-xx-large: 2rem;
--font-size-xxx-large: 3rem;
// FONT WEIGHTS
--font-weight-thin: 100;
--font-weight-extra-light: 200;
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semi-bold: 600;
--font-weight-bold: 700;
--font-weight-extra-bold: 800;
--font-weight-black: 900;
// OPACITY
--color-opacity-1: 0.1;
--color-opacity-2: 0.2;
--dim-opacity: 0.8;
--disabled-opacity: 0.4;
// SHADOWS
--shadow:
0 0 0 #{rpx(1)} rgba(0, 0, 0, 0.03),
0 #{rpx(1)} #{rpx(3)} #{rpx(1)} rgba(0, 0, 0, 0.07),
0 #{rpx(2)} #{rpx(6)} #{rpx(2)} rgba(0, 0, 0, 0.03);
--shadow-inset:
inset 0 0 0 #{rpx(1)} rgba(0, 0, 0, 0.03),
inset 0 #{rpx(1)} #{rpx(3)} #{rpx(1)} rgba(0, 0, 0, 0.07),
inset 0 #{rpx(2)} #{rpx(6)} #{rpx(2)} rgba(0, 0, 0, 0.03);
--shadow-raised:
0 0 0 #{rpx(1)} rgba(0, 0, 0, 0.06),
0 #{rpx(2)} #{rpx(6)} #{rpx(2)} rgba(0, 0, 0, 0.14),
0 #{rpx(4)} #{rpx(12)} #{rpx(4)} rgba(0, 0, 0, 0.06);
--shadow-glass: 0 #{rpx(6)} #{rpx(12)} #{rpx(-6)} rgba(0, 0, 0, 0.1);
// STATES
--hover: scale(1.1);
--active: scale(0.9);
// TRANSITIONS
--transition: 0.2s;
--transition-longer: 0.4s;
--transition-long: 0.8s;
--transition-bezier: 0.35s cubic-bezier(0.17, 0.89, 0.32, 1.28);
--transition-bezier-longer: 0.525s cubic-bezier(0.17, 0.89, 0.32, 1.28);
// ICONS
--icon-arrow-square-out: url(data:image/svg+xml,%3Csvg%20role%3D%22img%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22currentColor%22%20viewBox%3D%220%200%20256%20256%22%3E%3Cpath%20d%3D%22M228%2C104a12%2C12%2C0%2C0%2C1-24%2C0V69l-59.51%2C59.51a12%2C12%2C0%2C0%2C1-17-17L187%2C52H152a12%2C12%2C0%2C0%2C1%2C0-24h64a12%2C12%2C0%2C0%2C1%2C12%2C12Zm-44%2C24a12%2C12%2C0%2C0%2C0-12%2C12v64H52V84h64a12%2C12%2C0%2C0%2C0%2C0-24H48A20%2C20%2C0%2C0%2C0%2C28%2C80V208a20%2C20%2C0%2C0%2C0%2C20%2C20H176a20%2C20%2C0%2C0%2C0%2C20-20V140A12%2C12%2C0%2C0%2C0%2C184%2C128Z%22/%3E%3C/svg%3E);
--icon-caret-down: url(data:image/svg+xml,%3Csvg%20role%3D%22img%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22currentColor%22%20viewBox%3D%220%200%20256%20256%22%3E%3Cpath%20d%3D%22M216.49%2C104.49l-80%2C80a12%2C12%2C0%2C0%2C1-17%2C0l-80-80a12%2C12%2C0%2C0%2C1%2C17-17L128%2C159l71.51-71.52a12%2C12%2C0%2C0%2C1%2C17%2C17Z%22/%3E%3C/svg%3E);
--icon-check: url(data:image/svg+xml,%3Csvg%20role%3D%22img%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22currentColor%22%20viewBox%3D%220%200%20256%20256%22%3E%3Cpath%20d%3D%22M232.49%2C80.49l-128%2C128a12%2C12%2C0%2C0%2C1-17%2C0l-56-56a12%2C12%2C0%2C1%2C1%2C17-17L96%2C183%2C215.51%2C63.51a12%2C12%2C0%2C0%2C1%2C17%2C17Z%22/%3E%3C/svg%3E);
}
}
}
@mixin variables-accent($modes: "light") {
@each $mode in $modes {
@if $mode == "light" {
--accent-color: hsl(
var(--accent-light-h),
var(--accent-light-s),
var(--accent-light-l)
);
--accent-h: var(--accent-light-h);
--accent-s: var(--accent-light-s);
--accent-l: var(--accent-light-l);
--bg-color: hsl(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.6),
calc(var(--accent-l) + (100% - var(--accent-l)) * 0.8)
);
--fg-muted-1: hsla(
var(--accent-h),
var(--accent-s),
calc(var(--accent-l) - var(--accent-l) * 0.85),
var(--fg-muted-1-opacity)
);
--fg-muted-2: hsla(
var(--accent-h),
var(--accent-s),
calc(var(--accent-l) - var(--accent-l) * 0.85),
var(--fg-muted-2-opacity)
);
--glass-bg-1: hsla(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.7),
calc(var(--accent-l) + (100% - var(--accent-l)) * 0.9),
var(--dim-opacity)
);
--glass-bg-2: hsla(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.6),
calc(var(--accent-l) + (100% - var(--accent-l)) * 0.8),
var(--dim-opacity)
);
--crt-bg: radial-gradient(
hsl(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.5),
calc(var(--accent-l) + (100% - var(--accent-l)) * 0.9)
),
hsl(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.5),
calc(var(--accent-l) + (100% - var(--accent-l)) * 0.75)
)
80%,
hsl(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.5),
calc(var(--accent-l) + (100% - var(--accent-l)) * 0.65)
)
);
} @else if $mode == "dark" {
--accent-color: hsl(
var(--accent-dark-h),
var(--accent-dark-s),
var(--accent-dark-l)
);
--accent-h: var(--accent-dark-h);
--accent-s: var(--accent-dark-s);
--accent-l: var(--accent-dark-l);
--bg-color: hsl(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.7),
calc(var(--accent-l) - var(--accent-l) * 0.9)
);
--fg-muted-1: hsla(
var(--accent-h),
var(--accent-s),
calc(var(--accent-l) + (100% - var(--accent-l)) * 0.85),
var(--fg-muted-1-opacity)
);
--fg-muted-2: hsla(
var(--accent-h),
var(--accent-s),
calc(var(--accent-l) + (100% - var(--accent-l)) * 0.85),
var(--fg-muted-2-opacity)
);
--glass-bg-1: hsla(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.6),
calc(var(--accent-l) - var(--accent-l) * 0.8),
var(--dim-opacity)
);
--glass-bg-2: hsla(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.5),
calc(var(--accent-l) - var(--accent-l) * 0.7),
var(--dim-opacity)
);
--crt-bg: radial-gradient(
hsl(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.5),
calc(var(--accent-l) - var(--accent-l) * 0.7)
),
hsl(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.5),
calc(var(--accent-l) - var(--accent-l) * 0.9)
)
80%,
hsl(
var(--accent-h),
calc(var(--accent-s) - var(--accent-s) * 0.5),
calc(var(--accent-l) - var(--accent-l) * 0.95)
)
);
} @else if $mode == "common" {
// COLORS
--accent-highlight: hsla(
var(--accent-h),
var(--accent-s),
var(--accent-l),
var(--disabled-opacity)
);
// TODO: put this all in sub-mixins
--bg-muted-1: hsla(
var(--accent-h),
var(--accent-s),
var(--accent-l),
var(--color-opacity-1)
);
--bg-muted-2: hsla(
var(--accent-h),
var(--accent-s),
var(--accent-l),
var(--color-opacity-2)
);
// CONTRAST COLOR
--l: clamp(0, (l / 0.623 - 1) * -infinity, 1);
--a: calc(var(--l) + (var(--dim-opacity) * (1 - var(--l))));
--contrast-color: oklch(from var(--accent-color) var(--l) 0 h / var(--a));
// SHADOWS
--shadow-glow:
0 0 0 #{rpx(1)} var(--bg-muted-2),
0 #{rpx(2)} #{rpx(6)} #{rpx(2)} var(--bg-muted-1),
0 #{rpx(4)} #{rpx(24)} #{rpx(4)} var(--bg-muted-2);
--shadow-glow-inset:
inset 0 0 0 #{rpx(1)} var(--bg-muted-2),
inset 0 #{rpx(2)} #{rpx(6)} #{rpx(2)} var(--bg-muted-1),
inset 0 #{rpx(4)} #{rpx(24)} #{rpx(4)} var(--bg-muted-2);
--text-shadow-glow:
var(--bg-muted-2) 0 0 #{rpx(4)}, var(--accent-color) 0 0 #{rpx(12)};
}
}
}

View file

@ -0,0 +1,83 @@
@keyframes slide-in-up {
from {
transform: translateY(-1rem);
opacity: 0;
}
}
@keyframes slide-in-left {
from {
transform: translateX(1rem);
opacity: 0;
}
}
@keyframes slide-in-right {
from {
transform: translateX(-1rem);
opacity: 0;
}
}
@keyframes slide-in-up-longer {
from {
transform: translateY(-2rem);
opacity: 0;
}
}
@keyframes slide-in-down-longer {
from {
transform: translateY(2rem);
opacity: 0;
}
}
@keyframes slide-in-left-longer {
from {
transform: translateX(-2rem);
opacity: 0;
}
}
@keyframes slide-in-right-longer {
from {
transform: translateX(2rem);
opacity: 0;
}
}
@keyframes slide-out-down-longer {
to {
transform: translateY(-2rem);
opacity: 0;
}
}
@keyframes expand-up-left {
from {
transform: scale(0.5) translate(-1rem, -1rem);
opacity: 0;
}
}
@keyframes expand-up-right {
from {
transform: scale(0.5) translate(1rem, -1rem);
opacity: 0;
}
}
@keyframes expand-down-left {
from {
transform: scale(0.5) translate(-1rem, 1rem);
opacity: 0;
}
}
@keyframes expand-down-right {
from {
transform: scale(0.5) translate(1rem, 1rem);
opacity: 0;
}
}

View file

@ -0,0 +1,59 @@
@use "../abstracts/functions" as *;
// Make focused anchor not get covered by header
:target:not(#main-content) {
scroll-margin-block-start: 6.5rem;
}
:focus-visible,
:target:not(#main-content) {
animation: focus-in var(--transition);
outline: 0.125rem solid var(--accent-color);
outline-offset: 0.125rem;
@keyframes focus-in {
from {
outline: 0.5rem solid transparent;
outline-offset: 0.25rem;
}
}
}
// Style text selection to use accent color
::selection {
background-color: var(--accent-color);
color: var(--contrast-color);
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: var(--bg-color);
color: var(--fg-color);
font-family: var(--font-sans-serif), var(--font-system-ui), var(--font-emoji);
overflow-wrap: break-word;
@supports (min-height: 100dvh) {
min-height: 100dvh;
}
}
@media (prefers-reduced-motion) {
*,
*::before,
*::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
}
// Disable blur on Firefox Mobile
@-moz-document url-prefix() {
@media only screen and (max-width: 960px) {
.firefox-unblur,
.firefox-unblur::before,
.firefox-unblur::after {
backdrop-filter: none !important;
}
}
}

View file

@ -0,0 +1,36 @@
@use "../abstracts/functions" as *;
body > main,
body > nav,
body > footer {
--padding: 1rem;
margin-inline: auto;
padding-inline: 1rem;
max-width: calc(var(--content-width) + var(--padding) * 2);
}
body > footer {
margin-block-start: 4rem;
}
body > nav {
margin-block-end: 4rem;
border-block-end: rpx(1) solid var(--bg-muted-2);
ul {
display: flex;
gap: 0.25rem;
margin-block: 0.625rem;
padding-inline-start: 0;
list-style: none;
@media only screen and (max-width: 480px) {
flex-direction: column;
}
}
li {
margin-block: 0;
padding-inline-start: 0;
}
}

View file

@ -0,0 +1,135 @@
// Based on css-init, with a few important adjustments
// https://github.com/sir-sharkey/css-init
*,
:after,
:before {
vertical-align: baseline;
box-sizing: border-box;
margin: 0;
border: none;
background: none;
padding: 0;
color: inherit;
font: inherit;
-webkit-tap-highlight-color: transparent;
}
button,
select {
appearance: none;
}
body {
-webkit-text-size-adjust: none;
text-size-adjust: none;
width: 100%;
min-height: 100vh;
line-height: 1.5;
}
a,
button,
summary,
input[type="button"],
input[type="checkbox"],
input[type="color"],
input[type="file"],
input[type="image"],
input[type="radio"],
input[type="range"],
input[type="reset"],
input[type="submit"],
label,
select {
cursor: pointer;
}
article,
aside,
audio,
canvas,
details,
figcaption,
figure,
footer,
header,
main,
nav,
rt,
ruby,
section,
summary {
display: block;
}
bdi,
mark,
meter,
output,
progress,
time {
display: inline-block;
}
[hidden],
datalist,
dialog:not([open]),
rp {
display: none !important;
}
embed,
iframe,
img,
object,
picture,
svg,
video {
display: block;
max-width: 100%;
height: auto;
}
blockquote,
q {
quotes: none;
}
blockquote:after,
blockquote:before,
q:after,
q:before {
content: none;
}
strong {
font-weight: 700;
}
em {
font-style: italic;
}
code,
kbd,
pre,
samp {
font-family: monospace;
}
sub {
vertical-align: sub;
}
sup {
vertical-align: super;
}
small {
font-size: small;
}
table {
border-collapse: collapse;
}

View file

@ -0,0 +1,20 @@
@use "../abstracts/variables" as *;
:root {
@include variables(("light", "common"));
@include variables-accent(("light", "common"));
scrollbar-color: var(--accent-color) transparent;
accent-color: var(--accent-color);
@media (prefers-reduced-motion: no-preference) {
scroll-behavior: smooth;
}
@media (prefers-color-scheme: dark) {
& {
@include variables("dark");
@include variables-accent(("dark"));
color-scheme: dark;
}
}
}

View file

@ -0,0 +1,400 @@
@use "../abstracts/functions" as *;
@use "../abstracts/mixins" as *;
p,
ul,
ol,
dl,
blockquote,
pre {
margin-block: 1rem;
}
ul ul,
ul ol,
ol ul,
ol ol {
margin-block: 0;
}
p {
text-wrap: pretty;
overflow-wrap: break-word;
}
:disabled,
[aria-disabled="true"] {
opacity: var(--disabled-opacity);
cursor: not-allowed;
}
h1,
h2,
h3,
h4,
h5,
h6 {
transition-duration: var(--transition);
transition-property: text-shadow, font-weight;
margin-block: 2rem 1rem;
color: var(--accent-color);
font-weight: var(--font-weight-bold);
line-height: 1;
font-variation-settings: "FLAR" 100;
letter-spacing: -0.05em;
text-decoration: underline;
text-decoration-color: var(--bg-muted-1);
text-decoration-thickness: 0.125em;
text-wrap: balance;
overflow-wrap: break-word;
&:target {
animation: none !important;
outline: none !important;
font-weight: var(--font-weight-light);
text-shadow: var(--text-shadow-glow);
}
}
h1 {
font-size: var(--font-size-xxx-large);
}
h2 {
font-size: var(--font-size-xx-large);
}
h3 {
font-size: var(--font-size-x-large);
}
h4 {
font-size: var(--font-size-large);
}
h5 {
font-size: var(--font-size-medium);
}
h6 {
font-size: var(--font-size-small);
}
@media only screen and (max-width: 480px) {
h1 {
font-size: var(--font-size-xx-large);
}
h2 {
font-size: var(--font-size-x-large);
}
h3 {
font-size: var(--font-size-large);
}
h4 {
font-size: var(--font-size-medium);
}
h5 {
font-size: var(--font-size-small);
}
h6 {
font-size: var(--font-size-x-small);
}
}
small {
color: var(--fg-muted-2);
font-size: var(--font-size-small);
}
abbr[title] {
cursor: help;
text-decoration: underline;
text-decoration-style: dotted;
text-decoration-thickness: rpx(1);
}
figcaption {
color: var(--fg-muted-2);
font-size: var(--font-size-small);
text-align: center;
}
blockquote {
margin: 1rem 0;
border-inline-start: 0.25rem solid var(--accent-color);
border-start-start-radius: 0.25rem;
border-end-start-radius: 0.25rem;
padding-inline-start: 0.75rem;
color: var(--fg-muted-2);
}
pre,
code,
kbd,
samp {
font-family: var(--font-monospace), var(--font-ui-monospace);
}
pre {
margin: 1rem 0;
box-shadow: var(--edge-highlight), var(--shadow);
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
padding: 0.75rem 1rem;
overflow-x: auto;
scrollbar-width: thin;
line-height: 1.25;
}
code:not(pre code),
mark,
samp,
q {
@include chip;
}
code:not(pre code) {
background-color: var(--red-bg-1);
padding-block: rpx(3);
color: var(--red-fg);
font-size: var(--font-size-small);
}
mark {
color: var(--accent-color);
}
del {
color: var(--red-fg);
text-decoration: line-through;
text-decoration-thickness: rpx(1);
}
ins {
color: var(--green-fg);
text-decoration: underline;
text-decoration-thickness: rpx(1);
}
samp {
color: var(--fg-muted-2);
font-size: var(--font-size-small);
}
q {
color: var(--fg-muted-2);
font-style: italic;
}
u {
text-decoration: underline;
text-decoration-style: wavy;
text-decoration-color: var(--red-fg);
text-decoration-thickness: rpx(1);
}
progress {
appearance: none;
box-shadow: var(--edge-highlight);
border: none;
border-radius: 9999px;
background-color: var(--bg-muted-1);
padding: 0.25rem;
width: 100%;
height: 1rem;
color: var(--accent-color);
&:indeterminate {
background-color: var(--bg-muted-1);
}
&:indeterminate::-moz-progress-bar {
background-color: transparent;
}
&::-webkit-progress-bar {
background-color: transparent;
}
&::-moz-progress-bar {
border-radius: 9999px;
background-color: var(--accent-color);
}
&::-webkit-progress-value {
border-radius: 9999px;
background-color: var(--accent-color);
}
}
kbd {
display: inline-block;
transition-duration: var(--transition);
transition-property: transform, box-shadow, background-color;
cursor: pointer;
box-shadow:
var(--edge-highlight),
inset 0 -0.125rem 0 var(--bg-muted-2);
border-radius: var(--rounded-corner-small);
background-color: var(--bg-muted-1);
padding: 0 0.375rem;
font-size: var(--font-size-small);
// Small nice thingy, keys can be pressed!
&:active {
transform: translateY(0.125rem);
box-shadow: inset 0 rpx(1) 0 var(--bg-muted-2);
background-color: var(--bg-muted-2);
}
}
a {
color: var(--accent-color);
font-weight: var(--font-weight-semi-bold);
text-decoration-color: var(--accent-highlight);
text-decoration-thickness: rpx(1);
&:hover {
text-decoration-style: wavy;
}
}
hr {
margin: 2rem auto;
border: 0.125rem solid var(--bg-muted-2);
border-radius: 9999px;
}
dt {
font-weight: var(--font-weight-semi-bold);
}
dd {
margin-inline-start: 1.5rem;
margin-block-end: 1rem;
}
aside {
float: inline-end;
margin-inline-start: 1rem;
margin-block-end: 1rem;
box-shadow: var(--edge-highlight), var(--shadow);
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
padding: 1rem;
width: calc(var(--content-width) / 3);
@media only screen and (max-width: 720px) {
float: none;
margin-inline-start: 0;
width: 100%;
}
:first-child {
margin-block-start: 0;
}
:last-child {
margin-block-end: 0;
}
}
details {
display: flow-root;
transition: padding-block-end var(--transition);
margin-block-end: 1rem;
box-shadow: var(--edge-highlight), var(--shadow);
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
padding: 0.75rem 1rem;
&::details-content {
display: block;
opacity: 0;
transition-behavior: allow-discrete;
transition-duration: var(--transition);
transition-property: opacity, block-size, content-visibility;
block-size: 0;
overflow: hidden;
}
&[open] {
padding-block-end: 1rem;
&::details-content {
opacity: 1;
block-size: auto;
block-size: calc-size(auto, size);
}
summary {
color: var(--accent-color);
&::before {
transform: scaleY(-1);
}
@supports not (block-size: calc-size(auto, size)) {
& ~ * {
animation: slide-in-up var(--transition-bezier);
}
}
}
}
summary {
transition: var(--transition);
color: var(--fg-muted-1);
font-weight: var(--font-weight-semi-bold);
list-style: none;
&::-webkit-details-marker {
display: none;
}
&::before {
@include icon(pseudo);
--icon: var(--icon-caret-down);
display: inline-block;
vertical-align: rpx(-2);
transition:
all var(--transition),
transform var(--transition-bezier);
margin-inline-end: 0.25rem;
}
}
& > :last-child,
& > :last-child img {
margin-block-end: 0;
}
}
figure {
margin-inline: 0;
}
ol,
ul {
padding-inline-start: 1.5rem;
}
li {
margin-block: 0.25rem;
padding-inline-start: 0.25rem;
&::marker {
color: var(--accent-color);
font-weight: var(--font-weight-semi-bold);
}
}
b,
strong {
font-weight: var(--font-weight-semi-bold);
}

View file

@ -0,0 +1,100 @@
@use "../abstracts/mixins" as *;
.buttons {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 0.25rem;
margin-block-start: 2rem;
&.centered {
justify-content: center;
}
&.start {
justify-content: flex-start;
}
&.end {
justify-content: flex-end;
}
&.big {
gap: 0.5rem;
}
}
.buttons a,
a.button,
button {
display: inline-block;
transition:
background-color var(--transition),
color var(--transition),
box-shadow var(--transition),
transform var(--transition-bezier);
box-shadow: var(--edge-highlight);
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
padding: 0.5rem 1rem;
color: var(--fg-muted-1);
font-weight: var(--font-weight-semi-bold);
font-size: var(--font-size-small);
line-height: 1.5rem;
text-decoration: none;
&:not(:disabled, [aria-disabled="true"]):hover {
background-color: var(--bg-muted-2);
color: var(--fg-muted-2);
}
&:not(:disabled, [aria-disabled="true"]):active {
transform: var(--active);
}
&.suggested {
@include gradient-fill;
&:not(:disabled, [aria-disabled="true"]):hover {
@include gradient-fill(hover);
}
}
&.subtle {
box-shadow: none;
background-color: transparent;
&:not(:disabled, [aria-disabled="true"]):hover {
box-shadow: var(--edge-highlight);
background-color: var(--bg-muted-1);
color: var(--fg-muted-2);
}
}
&[aria-busy="true"] {
animation: progress-shimmer var(--transition-long) ease-in-out alternate
infinite;
background-image: linear-gradient(
to right,
transparent 50%,
var(--bg-muted-1) 75%,
transparent 100%
);
background-size: 200%;
@keyframes progress-shimmer {
to {
background-position-x: -200%;
}
}
}
}
.buttons.big a,
.buttons.big button,
a.button.big,
button.big {
border-radius: 9999px;
padding: 0.625rem 1.25rem;
}

View file

@ -0,0 +1,5 @@
@use "../abstracts/mixins" as *;
.card {
@include card;
}

View file

@ -0,0 +1,90 @@
.crt,
pre:has(code[data-crt], code[data-lang=""]) {
position: relative;
animation: flicker 0.25s alternate infinite;
margin-inline: auto;
box-shadow: var(--shadow-glow);
background-image: var(--crt-bg);
background-color: transparent !important;
padding: 0;
width: fit-content;
max-width: 100%;
overflow: hidden;
color: var(--accent-color) !important;
font-weight: var(--font-weight-medium);
text-shadow: var(--text-shadow-glow);
@keyframes flicker {
25% {
opacity: 0.95;
}
50% {
opacity: 0.85;
}
75% {
opacity: 1;
}
to {
opacity: 0.9;
}
}
&::before,
&::after {
position: absolute;
inset: 0;
content: "";
}
&::before {
opacity: var(--scanlines-opacity);
z-index: 1;
animation: scanlines 0.1s linear infinite;
background-image: repeating-linear-gradient(
to bottom,
rgba(0, 0, 0, 0.25),
rgba(0, 0, 0, 0.25) 0.125rem,
transparent 0.125rem,
transparent 0.25rem
);
background-position-y: -0.125rem;
background-repeat: no-repeat;
pointer-events: none;
@keyframes scanlines {
to {
background-position-y: 0.125rem;
}
}
}
&::after {
opacity: 0.05;
animation: scanline 5s linear infinite;
background-image: linear-gradient(
to bottom,
transparent,
var(--accent-color) 16rem
);
background-position-y: -16rem;
background-size: 100% 16rem;
pointer-events: none;
@keyframes scanline {
to {
background-position-y: calc(100% + 16rem);
}
}
}
& > code {
display: block;
border-radius: inherit;
padding: 0.75rem 1rem;
overflow-x: auto;
scrollbar-width: thin;
}
}

View file

@ -0,0 +1,12 @@
@use "../abstracts/functions" as *;
@use "../abstracts/mixins" as *;
a.external:not(:has(img, svg, video, picture, figure))::after {
@include icon(pseudo);
--icon: var(--icon-arrow-square-out);
display: inline-block;
vertical-align: -0.125rem;
opacity: var(--dim-opacity);
margin-inline-start: 0.125rem;
font-size: max(1rem, 1em);
}

View file

@ -0,0 +1,20 @@
iframe {
display: block;
margin: 1rem auto;
box-shadow: var(--edge-highlight), var(--shadow);
border: none;
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
width: 100%;
max-width: 100%;
&:fullscreen {
box-shadow: none;
border-radius: 0;
}
&:-webkit-full-screen {
box-shadow: none;
border-radius: 0;
}
}

View file

@ -0,0 +1,261 @@
@use "../abstracts/mixins" as *;
input {
appearance: none;
background-color: var(--bg-muted-1);
}
input:not([type]),
input[type="text"],
input[type="search"],
input[type="password"],
input[type="submit"] {
box-shadow: var(--edge-highlight);
border: none;
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
font-size: inherit;
line-height: inherit;
font-family: inherit;
}
input:not([type]),
input[type="text"],
input[type="search"],
input[type="password"] {
padding: 0.5rem 0.75rem;
width: 100%;
&::placeholder {
opacity: 1;
color: var(--fg-muted-1);
}
}
input[type="submit"] {
transition:
background-color var(--transition),
transform var(--transition-bezier);
box-shadow: var(--edge-highlight);
border-radius: var(--rounded-corner);
padding: 0.375rem 0.75rem;
color: var(--fg-muted-1);
font-weight: var(--font-weight-semi-bold);
font-size: var(--font-size-small);
&:enabled:hover {
background-color: var(--bg-muted-2);
}
&:enabled:active {
transform: var(--active);
}
}
input[type="radio"],
input[type="checkbox"],
input[type="color"] {
position: relative;
transition: background-color var(--transition);
border: 0.125rem solid var(--bg-muted-2);
width: 1rem;
height: 1rem;
&:not(:checked):enabled:hover {
background-color: var(--bg-muted-2);
}
}
input[type="radio"],
input[type="checkbox"] {
vertical-align: -0.125em;
margin-inline-end: 0.25rem;
&::before {
display: block;
position: absolute;
transform: scale(0.5);
opacity: 0;
transition:
opacity var(--transition),
transform var(--transition-bezier);
content: "";
}
&:checked {
border: 0.15rem solid transparent;
background-color: var(--accent-color);
&::before {
transform: scale(1);
opacity: 1;
}
}
}
input[type="radio"] {
border-radius: 50%;
&::before {
inset-block-start: 0.125rem;
inset-inline-start: 0.125rem;
border-radius: 50%;
background-color: var(--contrast-color);
width: 0.5rem;
height: 0.5rem;
}
}
input[type="checkbox"] {
border-radius: calc(var(--rounded-corner-small) / 2);
&::before {
@include icon(pseudo);
--icon: var(--icon-check);
transform-origin: bottom left;
inset-block-start: -0.125rem;
inset-inline-start: -0.125rem;
color: var(--contrast-color);
font-size: 1rem;
}
&.switch {
vertical-align: -0.375rem;
box-shadow: var(--edge-highlight);
border: none;
border-radius: 9999px;
width: 2.5rem;
height: 1.5rem;
&.big {
vertical-align: -0.625rem;
width: 3rem;
height: 2rem;
&::before {
width: 1.5rem;
height: 1.5rem;
}
}
&::before {
transform: none;
transform-origin: center;
opacity: 1;
mask-image: none;
inset-block-start: 0.25rem;
inset-inline-start: 0.25rem;
box-shadow: var(--shadow);
border-radius: 50%;
background-color: white;
width: 1rem;
height: 1rem;
content: "";
}
&:checked {
background-color: var(--accent-color);
&::before {
transform: translateX(1rem);
background-color: var(--contrast-color);
:root[dir*="rtl"] & {
transform: translateX(-1rem);
}
}
}
&:disabled::before {
box-shadow: none;
}
}
}
input[type="color"] {
vertical-align: -0.375em;
box-shadow: var(--edge-highlight);
border: none;
border-radius: var(--rounded-corner-small);
padding: 0.25rem;
width: 3rem;
height: 2rem;
&::-moz-color-swatch {
border: none;
border-radius: calc(var(--rounded-corner-small) - 0.25rem);
}
&::-webkit-color-swatch-wrapper {
padding: 0;
}
&::-webkit-color-swatch {
border-radius: calc(var(--rounded-corner-small) - 0.25rem);
}
}
input[type="range"] {
appearance: none;
box-shadow: var(--edge-highlight);
border-radius: 9999px;
background: var(--bg-muted-1);
padding: 0.5rem 0.25rem;
width: 100%;
height: 1rem;
&::-webkit-slider-runnable-track {
cursor: pointer;
border-radius: 9999px;
background: var(--accent-color);
width: 100%;
}
&::-moz-range-track {
cursor: pointer;
border-radius: 9999px;
background: var(--accent-color);
width: 100%;
height: 0.5rem;
}
&::-webkit-slider-thumb {
appearance: none;
cursor: ew-resize;
margin-block: -0.5rem;
box-shadow: var(--shadow);
border: none;
border-radius: 0.25rem;
background-color: white;
width: 0.5rem;
height: 1.5rem;
}
&::-moz-range-thumb {
appearance: none;
cursor: ew-resize;
margin-block: -0.5rem;
box-shadow: var(--shadow);
border: none;
border-radius: 0.25rem;
background-color: white;
width: 0.5rem;
height: 1.5rem;
}
&.hollow {
&::-webkit-slider-thumb {
margin-block: 0;
outline: 0.25rem solid white;
background-color: transparent;
height: 0.5rem;
}
&::-moz-range-thumb {
margin-block: 0;
outline: 0.25rem solid white;
background-color: transparent;
height: 0.5rem;
}
}
}

View file

@ -0,0 +1,165 @@
img,
video {
display: block;
margin: 1rem auto;
box-shadow: var(--edge-highlight), var(--shadow);
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
max-width: 100%;
max-height: calc(100vh - 2rem);
&.full,
&[src*="#full"] {
width: 100%;
}
&.start,
&.end,
&[src*="#start"],
&[src*="#end"] {
width: calc(var(--content-width) / 3);
}
&.start,
&[src*="#start"] {
float: inline-start;
transform-origin: left;
margin-inline-end: 1rem;
:root[dir*="rtl"] & {
transform-origin: right;
}
}
&.end,
&[src*="#end"] {
float: inline-end;
transform-origin: right;
margin-inline-start: 1rem;
:root[dir*="rtl"] & {
transform-origin: left;
}
}
&.pixels,
&[src*="#pixels"] {
image-rendering: pixelated;
}
&.has-alpha,
&[src*="#has-alpha"] {
box-shadow: none;
border-radius: 0;
background-color: transparent;
}
&.drop-shadow,
&[src*="#drop-shadow"] {
filter: var(--drop-shadow);
}
&.spoiler,
&[src*="#spoiler"] {
opacity: var(--dim-opacity);
clip-path: inset(0 0 0 0 round var(--rounded-corner));
filter: blur(1.5rem);
&:hover,
&:active {
opacity: 1;
clip-path: inset(
-0.75rem -0.75rem -0.75rem -0.75rem round var(--rounded-corner-small)
);
filter: none;
}
&.solid,
&[src*="#solid"] {
clip-path: none;
filter: brightness(0) contrast(0.5);
box-shadow: none;
&:hover,
&:active {
filter: none;
}
}
}
@media only screen and (max-width: 720px) {
&.start,
&.end,
&[src*="#start"],
&[src*="#end"] {
float: none;
margin-inline-start: auto;
margin-inline-end: auto;
width: auto;
}
}
}
img {
transition:
box-shadow var(--transition),
border-radius var(--transition),
transform var(--transition-bezier);
&:not(.no-hover, [src*="#no-hover"], .emoji) {
cursor: zoom-in;
&:hover {
position: relative;
transform: var(--hover);
z-index: 1;
box-shadow: var(--edge-highlight), var(--shadow-raised);
border-radius: var(--rounded-corner-small);
}
&.start,
&.end,
&[src*="#start"],
&[src*="#end"] {
transition: transform var(--transition-bezier-longer);
&:hover {
transform: scale(2);
}
@media only screen and (max-width: 720px) {
transform-origin: center;
transition: transform var(--transition-bezier);
&:hover {
transform: var(--hover);
}
}
}
&.has-alpha,
&[src*="#has-alpha"] {
&:hover {
box-shadow: none;
}
}
}
}
a img:not(.no-hover, [src*="#no-hover"]) {
cursor: pointer;
}
video:fullscreen {
box-shadow: none;
border-radius: 0;
}
video:-webkit-full-screen {
box-shadow: none;
border-radius: 0;
}
picture {
display: inline-block;
}

View file

@ -0,0 +1,13 @@
.overflow-auto {
overflow: auto;
&.overshoot {
margin-block: -1rem;
padding-block: 1rem;
}
&.overshoot-row {
margin-inline: -1rem;
padding-inline: 1rem;
}
}

View file

@ -0,0 +1,23 @@
.overshoot {
--mask: linear-gradient(
to bottom,
transparent,
black 1rem,
black calc(100% - 1rem),
transparent
);
-webkit-mask-image: var(--mask);
mask-image: var(--mask);
&-row {
--mask: linear-gradient(
to right,
transparent,
black 1rem,
black calc(100% - 1rem),
transparent
);
-webkit-mask-image: var(--mask);
mask-image: var(--mask);
}
}

View file

@ -0,0 +1,23 @@
#skip-to-content {
position: absolute;
transform: translateX(-50%) translateY(calc(-100% - 1rem));
z-index: 9999;
transition:
backdrop-filter var(--transition),
box-shadow var(--transition),
transform var(--transition-bezier);
inset-inline-start: 50vw;
border-radius: 9999px;
background-color: var(--glass-bg-1);
padding: 0.375rem 0.75rem;
width: max-content;
text-decoration: none;
&:focus-visible,
&:focus {
-webkit-backdrop-filter: var(--blur);
transform: translateX(-50%) translateY(1rem);
backdrop-filter: var(--blur);
box-shadow: var(--edge-highlight), var(--shadow);
}
}

View file

@ -0,0 +1,25 @@
span.spoiler {
filter: blur(0.25rem);
transition: filter var(--transition);
&:focus,
&:hover {
filter: none;
}
&.solid {
filter: none;
transition:
background-color var(--transition),
color var(--transition);
border-radius: var(--rounded-corner-small);
background-color: var(--fg-muted-1);
color: transparent;
&:focus,
&:hover {
background-color: transparent;
color: inherit;
}
}
}

View file

@ -0,0 +1,36 @@
@use "../abstracts/functions" as *;
table {
width: 100%;
overflow: hidden;
}
thead {
border-block-start: none;
color: var(--fg-muted-2);
font-weight: var(--font-weight-semi-bold);
}
tr {
&:nth-child(even) {
background-color: var(--bg-muted-1);
}
th,
td {
padding: 0.5rem 1rem;
}
}
tbody tr {
border-block: rpx(1) solid var(--bg-muted-2);
&:last-child {
border-block-end: none;
}
}
.overflow-auto table {
width: max-content;
min-width: 100%;
}

View file

@ -0,0 +1,17 @@
textarea {
appearance: none;
box-shadow: var(--edge-highlight);
border: none;
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
padding: 1rem;
resize: none;
font-size: inherit;
line-height: inherit;
font-family: inherit;
&::placeholder {
opacity: 1;
color: var(--fg-muted-1);
}
}

View file

@ -0,0 +1,5 @@
strong.title {
color: var(--accent-color);
font-size: var(--font-size-large);
line-height: 1;
}

View file

@ -0,0 +1,41 @@
// attr() hack from https://cyrneko.eu/css-tricks.html
@media (pointer: coarse), (hover: none) {
.tooltip,
abbr[title] {
display: inline-flex;
position: relative;
justify-content: center;
&::after {
position: absolute;
top: calc(100% + 0.25rem);
z-index: 9999;
backdrop-filter: var(--blur);
transition:
visibility var(--transition),
opacity var(--transition),
transform var(--transition-bezier);
box-shadow: var(--edge-highlight), var(--shadow);
border-radius: var(--rounded-corner-small);
background-color: var(--glass-bg-1);
padding: 0.125rem 0.375rem;
width: max-content;
content: attr(title);
-webkit-backdrop-filter: var(--blur);
transform: translateY(-0.5rem) scale(0.5);
transform-origin: top;
visibility: hidden;
opacity: 0;
max-width: calc(var(--content-width) / 3);
color: var(--fg-muted-2);
font-size: var(--font-size-small);
}
&:focus::after,
&:hover::after {
transform: none;
visibility: visible;
opacity: 1;
}
}
}

View file

@ -0,0 +1,11 @@
.visually-hidden {
position: absolute !important;
clip-path: inset(50%) !important;
margin: -1px !important;
border-width: 0 !important;
padding: 0 !important;
width: 1px !important;
height: 1px !important;
overflow: hidden !important;
white-space: nowrap !important;
}

29
sass/citrine/style.scss Normal file
View file

@ -0,0 +1,29 @@
/*
* Citrine v1.0.0 (https://citrine.daudix.one)
* Copyright 2026 David "Daudix" Lapshin and contributors
* Licensed under MIT (https://codeberg.org/daudix/citrine/raw/branch/main/LICENSE.txt)
*/
@use "base/root";
@use "base/reset";
@use "base/animations";
@use "base/base";
@use "base/layout";
@use "base/typography";
@use "components/buttons";
@use "components/card";
@use "components/crt";
@use "components/external";
@use "components/iframe";
@use "components/input";
@use "components/media";
@use "components/overflow-auto";
@use "components/overshoot";
@use "components/skip-to-content";
@use "components/spoiler";
@use "components/table";
@use "components/textarea";
@use "components/title";
@use "components/tooltip";
@use "components/visually-hidden";

View file

@ -0,0 +1,153 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
@use "../citrine/abstracts/variables" as *;
[class^="markdown-alert"] {
display: flow-root;
position: relative;
&::before {
margin-inline-start: 1.5rem;
content: var(--alert-title);
color: var(--accent-color);
font-weight: var(--font-weight-semi-bold);
}
&::after {
@include icon(pseudo);
--icon: var(--alert-icon);
position: absolute;
margin-inline-end: 0.25rem;
inset-block-start: rpx(1);
inset-inline-start: 0.75rem;
color: var(--accent-color);
font-size: 1.25rem;
}
& > :first-child {
margin-block-start: 0.25rem;
}
& > :last-child {
margin-block-end: 0;
}
}
.markdown-alert {
// GitHub flavored alerts
&-note,
&-tip,
&-important,
&-warning,
&-caution,
// Generic colors
&-red,
&-orange,
&-yellow,
&-green,
&-blue,
&-purple {
@include variables-accent(("light", "common"));
@media (prefers-color-scheme: dark) {
@include variables-accent("dark");
}
}
&-note {
&::before {
content: var(--alert-note-title);
}
&::after {
--icon: var(--icon-info);
}
}
&-tip {
&::before {
content: var(--alert-tip-title);
}
&::after {
--icon: var(--icon-lightbulb);
}
}
&-important {
&::before {
content: var(--alert-important-title);
}
&::after {
--icon: var(--icon-warning-diamond);
}
}
&-warning {
&::before {
content: var(--alert-warning-title);
}
&::after {
--icon: var(--icon-warning);
}
}
&-caution {
&::before {
content: var(--alert-caution-title);
}
&::after {
--icon: var(--icon-warning-octagon);
}
}
&-caution,
&-red {
--accent-h: var(--red-h);
--accent-s: var(--red-s);
--accent-l: var(--red-l);
--accent-color: var(--red-fg);
}
&-orange {
--accent-h: var(--orange-h);
--accent-s: var(--orange-s);
--accent-l: var(--orange-l);
--accent-color: var(--orange-fg);
}
&-warning,
&-yellow {
--accent-h: var(--yellow-h);
--accent-s: var(--yellow-s);
--accent-l: var(--yellow-l);
--accent-color: var(--yellow-fg);
}
&-tip,
&-green {
--accent-h: var(--green-h);
--accent-s: var(--green-s);
--accent-l: var(--green-l);
--accent-color: var(--green-fg);
}
&-note,
&-blue {
--accent-h: var(--blue-h);
--accent-s: var(--blue-s);
--accent-l: var(--blue-l);
--accent-color: var(--blue-fg);
}
&-important,
&-purple {
--accent-h: var(--purple-h);
--accent-s: var(--purple-s);
--accent-l: var(--purple-l);
--accent-color: var(--purple-fg);
}
}

View file

@ -0,0 +1,71 @@
@use "../citrine/abstracts/functions" as *;
.article-details {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-block-end: 2rem;
color: var(--fg-muted-2);
font-size: var(--font-size-small);
.items {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 0.25rem 0.5rem;
}
.item {
display: inline-flex;
align-items: center;
gap: 0.375rem;
border-radius: var(--rounded-corner-small);
}
a {
color: inherit;
font-weight: var(--font-weight-normal);
font-size: var(--font-size-small);
text-decoration-color: transparent;
&:hover {
text-decoration-style: solid;
text-decoration-color: var(--fg-muted-1);
}
}
.tooltip {
cursor: help;
}
.icon {
box-shadow: var(--edge-highlight);
border-radius: var(--rounded-corner-small);
background-color: var(--bg-muted-1);
padding: 0.375rem;
color: var(--accent-color);
font-size: 1.25rem;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0 0.5rem;
margin: 0;
border-block-start: rpx(1) solid var(--bg-muted-2);
padding-inline-start: 0;
padding-block-start: 0.5rem;
width: 100%;
list-style: none;
li {
display: flex;
margin-block: 0;
padding: 0;
}
a::before {
content: "#";
}
}
}

View file

@ -0,0 +1,254 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/variables" as *;
@use "../citrine/abstracts/mixins" as *;
.article-list {
display: grid;
grid-template-columns: repeat(
auto-fill,
minmax(calc(var(--content-width) / 2.5), 1fr)
);
gap: 1rem;
margin-block-start: 2rem;
&.categories article {
position: relative;
&:hover .icon:last-child {
transform: translateY(-50%) rotate(-10deg) scale(1.5);
}
.icon:last-child {
position: absolute;
transform: translateY(-50%);
z-index: -1;
transition: transform var(--transition-bezier);
inset-block-start: 50%;
inset-inline-end: -3rem;
color: var(--bg-muted-1);
font-size: 12rem;
}
}
article {
display: flex;
position: relative;
flex-direction: column;
justify-content: space-between;
box-shadow: var(--edge-highlight), var(--shadow);
border-radius: 1.25rem;
background-color: var(--bg-muted-1);
padding: 0;
overflow: hidden;
&.draft {
opacity: var(--disabled-opacity);
filter: grayscale(1);
transition-duration: var(--transition);
transition-property: opacity, filter;
&:hover {
opacity: 1;
filter: none;
}
}
&.has-banner.has-accent-color {
background-image: var(--bg-overlay), var(--blurnail);
background-position: center;
background-size: cover;
}
&.has-accent-color {
@include variables-accent(("light", "common"));
@media (prefers-color-scheme: dark) {
& {
@include variables-accent("dark");
}
}
}
& > div {
display: flex;
flex: 1;
flex-direction: column;
margin: 1rem;
& > a {
text-decoration: none;
}
}
h3 {
margin: 0;
border-inline-start: 0.25rem solid;
border-start-start-radius: 0.25rem;
border-end-start-radius: 0.25rem;
padding-inline-start: 0.5rem;
color: var(--accent-color);
font-weight: var(--font-weight-semi-bold);
text-decoration: none;
&:hover::after {
opacity: 1;
}
&::after {
opacity: 0;
transition: opacity var(--transition);
content: " →";
}
}
.description p {
margin-block-end: 0.75rem;
}
.open-post {
@include easing-mask(bottom);
display: flex;
position: relative;
justify-content: center;
align-items: center;
margin-block-end: -2rem;
box-shadow: var(--edge-highlight);
border-radius: 0;
aspect-ratio: 2 / 1;
overflow: hidden;
text-decoration: none;
&:hover {
&::before {
background-color: var(--shade-color);
}
img {
transform: var(--hover);
filter: blur(0.25rem);
}
.icon {
transform: none;
opacity: 1;
}
}
&:active img {
transform: none;
}
&::before {
position: absolute;
z-index: 1;
transition: background-color var(--transition);
inset: 0;
border-radius: inherit;
content: "";
}
img {
position: absolute;
transition-duration: var(--transition);
transition-property: transform, filter;
margin: 0;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.icon {
transform: scale(1.5);
opacity: 0;
z-index: 1;
transition:
transform var(--transition-bezier),
opacity var(--transition);
color: white;
font-size: 1.25rem;
}
}
.article-details {
flex: 1;
justify-content: flex-end;
margin-block-end: 0;
.tags {
margin-block-start: auto;
margin-block-end: -0.25rem;
}
}
}
}
.paginator {
display: flex;
justify-content: center;
align-items: center;
gap: 0.25rem;
margin-block-start: 4rem;
&:has(a.paginator-item.previous:active) .paginator-counter {
border-start-start-radius: 1rem;
border-end-start-radius: 1rem;
}
&:has(a.paginator-item.next:active) .paginator-counter {
border-start-end-radius: 1rem;
border-end-end-radius: 1rem;
}
&-item {
display: inline-block;
transition:
border-radius var(--transition),
border-start-end-radius var(--transition),
border-end-end-radius var(--transition),
border-start-start-radius var(--transition),
border-end-start-radius var(--transition),
background-color var(--transition),
transform var(--transition-bezier);
box-shadow: var(--edge-highlight);
border-radius: 1rem;
background-color: var(--bg-muted-1);
padding: 0.5rem;
color: var(--accent-color);
line-height: 0;
text-decoration: none;
&:not([aria-disabled="true"]):hover {
background-color: var(--bg-muted-2);
}
&:not([aria-disabled="true"]):active {
transform: var(--active);
border-radius: 1rem;
}
&-previous {
border-start-end-radius: var(--rounded-corner-small);
border-end-end-radius: var(--rounded-corner-small);
}
&-next {
border-start-start-radius: var(--rounded-corner-small);
border-end-start-radius: var(--rounded-corner-small);
}
}
&-counter {
@include gradient-fill;
display: inline-block;
transition: var(--transition-bezier);
transition-property:
border-start-start-radius, border-end-start-radius,
border-start-end-radius, border-end-end-radius;
border-radius: var(--rounded-corner-small);
background-color: var(--bg-muted-1);
padding: 0.25rem 0.5rem;
font-weight: var(--font-weight-semi-bold);
font-variant-numeric: tabular-nums;
}
}

View file

@ -0,0 +1,66 @@
.article-nav {
display: flex;
gap: 0.25rem;
@media only screen and (max-width: 720px) {
flex-direction: column;
}
&-item {
flex: 1;
transition:
box-shadow var(--transition),
background-color var(--transition),
transform var(--transition-bezier);
border-radius: var(--rounded-corner);
padding: 0.75rem 1rem;
min-width: 0;
text-decoration: none;
&:hover {
box-shadow: var(--edge-highlight), var(--shadow);
background-color: var(--bg-muted-1);
.article-title {
color: var(--accent-color);
}
}
&:active {
transform: var(--active);
}
&.prev {
text-align: start;
.nav-arrow::before {
content: "← ";
}
}
&.next {
text-align: end;
.nav-arrow::after {
content: " →";
}
}
}
.nav-arrow {
margin-block-end: 0.5rem;
color: var(--fg-muted-2);
font-weight: var(--font-weight-normal);
font-size: var(--font-size-small);
}
.article-title {
display: block;
transition: color var(--transition);
max-width: 100%;
overflow: hidden;
color: var(--fg-muted-1);
text-overflow: ellipsis;
white-space: nowrap;
}
}

View file

@ -0,0 +1,42 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
.audio-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-size: var(--font-size-small);
font-weight: var(--font-weight-semi-bold);
color: var(--accent-color);
background-color: var(--accent-bg-1);
border: none;
cursor: pointer;
transition:
background-color var(--transition),
transform var(--transition-bezier),
box-shadow var(--transition);
&:hover {
background-color: var(--accent-bg-2);
transform: scale(1.03);
}
&:active {
transform: var(--active);
}
&:focus-visible {
outline: 0.125rem solid var(--accent-color);
outline-offset: 0.25rem;
}
.audio-button-label {
display: none;
@media (min-width: 480px) {
display: inline;
}
}
}

View file

@ -0,0 +1,41 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
// Who doesn't like a party popper mid article?
.audio {
@include chip;
padding-block: 0;
color: var(--accent-color);
font-size: var(--font-size-medium);
&:hover {
color: var(--accent-color) !important;
.icon {
animation: speaker var(--transition-long) ease-out infinite;
@keyframes speaker {
from {
--icon: var(--icon-speaker-none);
}
50% {
--icon: var(--icon-speaker-low);
}
to {
--icon: var(--icon-speaker-high);
}
}
}
}
&:active {
transform: var(--active);
}
.icon {
vertical-align: rpx(-3);
margin-inline-start: 0.25rem;
}
}

View file

@ -0,0 +1,98 @@
@use "../citrine/abstracts/functions" as *;
body.has-banner {
background-image: var(--bg-overlay), var(--blurnail);
background-position: center;
background-size: cover;
background-attachment: fixed;
}
#banner {
--mask: linear-gradient(black calc(100% - 2rem), transparent);
-webkit-mask-image: var(--mask);
display: flex;
grid-area: banner;
justify-content: center;
mask-image: var(--mask);
margin-block-end: -2.5rem;
padding-block: 3.25rem 2.5rem;
width: 100%;
div {
position: relative;
margin-inline: 1rem;
aspect-ratio: 2 / 1;
width: 100%;
max-width: var(--content-width);
}
img {
position: absolute;
margin: 0;
inset: 0;
border-radius: var(--rounded-corner) var(--rounded-corner) 0 0 !important;
aspect-ratio: 2 / 1;
width: 100%;
object-fit: cover;
}
img:nth-of-type(1) {
opacity: var(--dim-opacity);
z-index: -1;
filter: blur(1.5rem);
animation: banner-glow-load calc(0.525s + var(--transition-long));
inset-block-start: 1rem;
@keyframes banner-glow-load {
from,
52.5% {
opacity: 0;
}
}
}
img:nth-of-type(2) {
transform-origin: bottom;
animation: banner-load calc(0.525s + var(--transition-longer));
@keyframes banner-load {
from,
52.5% {
transform: translateY(2rem) scale(0.95);
opacity: 0;
}
}
}
& + #main-content {
margin-block-start: 0;
article > h1:first-of-type,
h1:first-of-type {
margin-inline: -1rem;
margin-block: 0;
border-radius: var(--rounded-corner) var(--rounded-corner) 0 0;
padding: 1rem;
width: unset;
-webkit-backdrop-filter: blur(1rem);
backdrop-filter: blur(1rem);
animation: banner-heading calc(0.525s + var(--transition-longer));
box-shadow: inset 0 rpx(1) 0 var(--accent-highlight);
background-image: linear-gradient(
to bottom,
var(--bg-muted-1),
transparent 50%
);
background-size: auto 200%;
text-decoration: none;
@keyframes banner-heading {
from,
52.5% {
box-shadow: none;
background-position-y: 100%;
}
}
}
}
}

View file

@ -0,0 +1,459 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
#comments {
#qrcode {
float: inline-end;
cursor: help;
box-sizing: content-box;
margin-inline-start: 1rem;
margin-block-start: 3rem;
box-shadow: var(--edge-highlight), var(--shadow);
border-radius: var(--rounded-corner);
background-color: white;
padding: 0.75rem;
width: rpx(116);
height: rpx(116);
svg {
pointer-events: none;
}
@media only screen and (max-width: 720px) {
display: none;
}
}
#comments-wrapper {
display: flex;
flex-direction: column;
gap: 2rem;
margin-block-start: 2rem;
#comments-status {
color: var(--fg-muted-1);
font-weight: var(--font-weight-semi-bold);
font-size: var(--font-size-x-large);
text-align: center;
}
.comment {
display: grid;
// grid-template-columns: min-content;
grid-template-columns: auto 1fr;
grid-template-areas:
"avatar header "
"...... post "
"...... media "
"...... card "
"...... interactions";
column-gap: 1rem;
justify-items: start;
animation: slide-in-right var(--transition-bezier);
:root[dir*="rtl"] & {
animation: slide-in-left var(--transition-bezier);
}
@media only screen and (max-width: 720px) {
grid-template-areas:
"avatar header "
"post post "
"media media "
"card card "
"interactions interactions";
align-items: start;
}
&.comment-reply {
position: relative;
border-inline-start: 0.25rem solid var(--bg-muted-2);
border-start-start-radius: 0.25rem;
border-end-start-radius: 0.25rem;
padding-inline-start: 1rem;
&:has(+ .comment-reply) {
border-end-start-radius: 0;
}
& + .comment-reply {
margin-block-start: -2rem;
border-start-start-radius: 0;
padding-block-start: 2rem;
}
}
.avatar-link {
grid-area: avatar;
width: 4rem;
height: 4rem;
@media only screen and (max-width: 480px) {
width: 3rem;
height: 3rem;
}
.avatar {
transition: transform var(--transition-bezier);
margin: 0;
background-size: cover;
width: 100%;
height: 100%;
&:hover {
transform: rotate(10deg) var(--hover);
border-radius: var(--rounded-corner);
}
&:active {
transform: var(--active);
}
}
}
.mention {
@include chip;
display: inline-block;
&:hover {
background-color: var(--bg-muted-2);
}
&:active {
transform: var(--active);
}
}
.invisible {
display: none;
}
.ellipsis::after {
content: "…";
}
header {
display: flex;
grid-area: header;
flex-direction: column;
width: 100%;
min-width: 0;
color: var(--fg-muted-1);
line-height: 1;
.display,
.instance {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.display {
color: var(--fg-color);
line-height: 1.5rem;
text-decoration: none;
.emoji {
cursor: unset;
&:hover {
transform: none;
}
}
}
.instance {
margin-block-start: 0.25rem;
font-weight: var(--font-weight-semi-bold);
font-size: var(--font-size-x-small);
&.op {
cursor: help;
&::before {
@include icon(pseudo);
--icon: var(--icon-seal-check);
display: inline-block;
vertical-align: text-top;
margin-inline-end: 0.25rem;
font-size: 1rem;
}
}
}
.timestamp {
margin-block-start: 0.25rem;
a {
color: inherit;
font-size: var(--font-size-x-small);
text-decoration-color: transparent;
&:hover {
text-decoration-style: solid;
text-decoration-color: inherit;
}
&::after {
vertical-align: top;
}
}
}
}
details {
border-radius: var(--rounded-corner-small);
background-image:
linear-gradient(
to right,
transparent,
transparent 0.5rem,
var(--bg-muted-1) 0.5rem,
var(--bg-muted-1) calc(100% - 0.5rem),
transparent calc(100% - 0.5rem),
transparent
),
linear-gradient(
to right,
transparent,
transparent 0.5rem,
var(--bg-color) 0.5rem,
var(--bg-color) calc(100% - 0.5rem),
transparent calc(100% - 0.5rem),
transparent
),
repeating-linear-gradient(
45deg,
var(--contrast-color),
var(--contrast-color) 0.25rem,
var(--accent-color) 0.25rem,
var(--accent-color) 0.5rem
);
width: 100%;
summary {
border-radius: 0;
background-image: none;
}
}
main {
grid-area: post;
justify-self: stretch;
margin-block-start: 1rem;
padding: 0;
:first-child {
margin-block-start: 0;
}
:last-child {
margin-block-end: 0;
}
}
.attachments {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
grid-area: media;
gap: 0.5rem;
margin-block-start: 1rem;
img,
video {
margin: 0;
}
}
.meta-card {
grid-area: card;
transition: transform var(--transition-bezier);
margin-block-start: 1rem;
width: min(calc(var(--content-width) / 2), 100%);
font-weight: var(--font-weight-normal);
text-decoration: none;
&:hover {
img {
transform: var(--hover);
box-shadow: var(--edge-highlight), var(--shadow-raised);
border-radius: var(--rounded-corner-small);
}
figcaption {
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-2);
}
}
&:active {
transform: var(--active);
}
figure {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin: 0;
img {
margin: 0;
border-radius: var(--rounded-corner) var(--rounded-corner)
var(--rounded-corner-small) var(--rounded-corner-small);
aspect-ratio: 16 / 9;
object-fit: cover;
& + figcaption {
border-radius: var(--rounded-corner-small)
var(--rounded-corner-small) var(--rounded-corner)
var(--rounded-corner);
}
}
figcaption {
display: flex;
flex-direction: column;
gap: 0.25rem;
transition-duration: var(--transition);
transition-property: border-radius, background-color;
box-shadow: var(--edge-highlight), var(--shadow);
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
padding: 1rem;
color: var(--fg-color);
font-size: var(--font-size-medium);
text-align: start;
p {
margin: 0;
color: var(--fg-muted-2);
font-size: var(--font-size-small);
}
}
}
}
footer {
display: flex;
grid-area: interactions;
align-items: center;
gap: 0.25rem;
margin-block-start: 1rem;
width: 100%;
min-width: 0;
@media only screen and (max-width: 480px) {
flex-wrap: wrap;
}
.boosts,
.faves,
.reaction {
padding: 0.375rem 0.75rem;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.boosts,
.faves {
transition:
box-shadow var(--transition),
border-radius var(--transition),
transform var(--transition-bezier);
border-radius: rpx(18);
text-decoration: none;
.icon {
vertical-align: rpx(-2);
transition: transform var(--transition-bezier);
margin-inline-end: 0.25rem;
}
&:hover {
box-shadow: var(--edge-highlight);
text-decoration: none;
}
&:active {
transform: var(--active);
border-radius: rpx(18);
}
}
.boosts {
border-start-end-radius: var(--rounded-corner-small);
border-end-end-radius: var(--rounded-corner-small);
padding-inline-end: 0.625rem;
color: var(--purple-fg);
.icon {
--icon: var(--icon-repeat);
}
&:hover {
background-color: var(--purple-bg-1);
.icon {
transform: rotate(180deg);
}
}
}
.faves {
border-start-start-radius: var(--rounded-corner-small);
border-end-start-radius: var(--rounded-corner-small);
padding-inline-start: 0.625rem;
color: var(--yellow-fg);
.icon {
--icon: var(--icon-star);
}
&:hover {
background-color: var(--yellow-bg-1);
.icon {
transform: rotate(72deg);
}
}
}
.reactions {
display: flex;
// flex-wrap: nowrap;
gap: 0.25rem;
margin: -0.5rem -1rem;
padding: 0.5rem 1rem;
min-width: 0;
overflow-x: auto;
scrollbar-width: none;
color: var(--accent-color);
font-weight: var(--font-weight-semi-bold);
.reaction {
display: flex;
flex-shrink: 0;
align-items: center;
gap: 0.25rem;
cursor: zoom-in;
border: rpx(1) solid var(--bg-muted-2);
border-radius: rpx(19);
&:hover .emoji {
transform: scale(2);
}
.emoji {
cursor: unset;
}
span {
font-variant-numeric: tabular-nums;
}
}
}
}
}
}
}

View file

@ -0,0 +1,20 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
.deanchor {
display: inline;
font-family: var(--font-family-mono);
font-size: var(--font-size-small);
color: var(--fg-muted-2);
background-color: var(--bg-muted-1);
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
transition:
background-color var(--transition),
color var(--transition);
&:hover {
background-color: var(--bg-muted-2);
color: var(--fg-muted-1);
}
}

View file

@ -0,0 +1,21 @@
.emoji {
display: inline-block;
vertical-align: bottom;
transition: transform var(--transition-bezier);
cursor: zoom-in;
margin: 0;
box-shadow: none;
border-radius: 0;
background-color: transparent;
width: 1.5rem;
height: 1.5rem;
&:hover {
transform: scale(2);
}
&.big {
width: 3rem;
height: 3rem;
}
}

View file

@ -0,0 +1,62 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
.footnotes-list p {
margin-block: 0;
}
.footnote-reference {
display: inline-flex;
vertical-align: baseline;
transition:
background-color var(--transition),
outline-offset calc(var(--transition-long) * 3),
transform var(--transition-bezier);
margin-inline-start: 0.25rem;
outline-offset: calc(var(--content-width) / 2) !important;
border-radius: 0.6875em;
background-color: var(--fg-muted-1);
// width: 1.375em;
line-height: 1.375em;
&:hover {
background-color: var(--accent-color);
a::after {
mask-position: center rpx(18);
}
}
&:active {
transform: var(--active);
}
&:target {
animation: none !important;
outline-offset: rpx(-3) !important;
background-color: var(--accent-color);
}
a {
flex: 1;
border-radius: inherit;
padding-inline: rpx(9) rpx(6);
color: var(--bg-color);
font-size: var(--font-size-small);
font-variant-numeric: tabular-nums;
text-align: center;
text-decoration: none;
&::after {
@include icon(pseudo);
--icon: var(--icon-caret-down);
display: inline-block;
vertical-align: rpx(-7);
mask-position: center;
mask-size: 1em;
transition: mask-position var(--transition);
margin-inline-start: 0.25rem;
height: rpx(22);
}
}
}

View file

@ -0,0 +1,50 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
.fuse {
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 30rem;
.fuse-input {
width: 100%;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
border: 1px solid var(--bg-muted-2);
background-color: var(--bg-muted-1);
color: var(--fg-color);
font-size: var(--font-size-medium);
transition:
border-color var(--transition),
box-shadow var(--transition);
&:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 2px var(--accent-bg-1), 0 0 0 4px var(--accent-color);
}
}
.fuse-results {
display: flex;
flex-direction: column;
gap: 0.25rem;
.fuse-result {
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
background-color: var(--bg-muted-1);
text-decoration: none;
color: var(--fg-color);
transition:
background-color var(--transition),
padding-left var(--transition);
&:hover {
background-color: var(--bg-muted-2);
padding-left: 1rem;
}
}
}
}

View file

@ -0,0 +1,16 @@
.giallo:has(.giallo-ln) {
padding-inline: 0;
}
.giallo-l {
display: inline-block;
width: 100%;
}
.giallo-ln {
display: inline-block;
margin-right: 1ch;
min-width: 3ch;
user-select: none;
text-align: right;
}

View file

@ -0,0 +1,9 @@
@use "../citrine/abstracts/mixins" as *;
.icon {
@include icon;
&::before {
@include icon(pseudo);
}
}

View file

@ -0,0 +1,6 @@
iframe {
&.vimeo-embed,
&.youtube-embed {
aspect-ratio: 16 / 9;
}
}

View file

@ -0,0 +1,25 @@
@use "alerts";
@use "article-details";
@use "article-list";
@use "article-nav";
@use "audio";
@use "banner";
@use "comments";
@use "emoji";
@use "footnotes";
@use "giallo";
@use "icon";
@use "iframe";
@use "marquee";
@use "not-found";
@use "pre-container";
@use "search-modal";
@use "skip-to-content";
@use "statements";
@use "tag-chips";
@use "zola-anchor";
@use "zola-error";
@use "audio-button";
@use "deanchor";
@use "fuse";
@use "search-fuse";

View file

@ -0,0 +1,35 @@
.marquee {
margin-inline: -1rem;
padding-inline: 1rem;
overflow: hidden;
&.overshoot-row > * {
animation: marquee-scroll
calc((var(--text-width) + var(--container-width)) / 40 * 1s) linear
infinite;
@keyframes marquee-scroll {
0%,
10% {
transform: translateX(0);
}
45%,
55% {
transform: translateX(
calc((var(--container-width) - var(--text-width)) * 1px)
);
}
90%,
100% {
transform: translateX(0);
}
}
}
& > * {
display: inline-block;
white-space: nowrap;
}
}

View file

@ -0,0 +1,11 @@
picture#not-found {
display: block;
img {
width: 8rem;
}
}
img#not-found {
width: 8rem;
}

View file

@ -0,0 +1,141 @@
@use "../citrine/abstracts/functions" as *;
.pre-container {
position: relative;
margin: 1rem 0 1rem;
box-shadow: var(--shadow);
border-radius: var(--rounded-corner);
&::after {
position: absolute;
inset: 0;
box-shadow: var(--edge-highlight);
border-radius: inherit;
pointer-events: none;
content: "";
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
transition: border-block-end-color var(--transition);
border-radius: var(--rounded-corner) var(--rounded-corner) 0 0;
border-block-end: rpx(1) solid transparent;
background-image: linear-gradient(
to right,
transparent 50%,
var(--bg-muted-neutral-1) 75%,
transparent 100%
);
background-size: 200%;
padding: 0.25rem;
&:not(.z-l-code),
&:not(.z-d-code) {
background-color: var(--bg-muted-1);
color: var(--accent-color);
button:hover {
background-color: var(--bg-muted-1);
}
&.active {
border-block-end-color: var(--bg-muted-1);
button {
background-color: var(--bg-muted-1);
}
}
}
span {
margin-inline-start: 0.75rem;
font-weight: var(--font-weight-semi-bold);
}
button {
position: relative;
transition:
box-shadow var(--transition),
background-color var(--transition),
transform var(--transition-bezier);
cursor: pointer;
box-shadow: none;
border-radius: var(--rounded-corner-small);
background-color: transparent;
padding: 0.5rem;
color: inherit;
line-height: 0;
&:hover {
box-shadow: var(--edge-highlight);
background-color: var(--bg-muted-neutral-1);
}
&:active {
transform: var(--active);
}
&:disabled {
cursor: not-allowed;
&:active {
transform: none;
}
}
.icon {
transition:
opacity var(--transition-longer),
transform var(--transition-bezier-longer);
font-size: 1rem;
&.copy {
--icon: var(--icon-copy);
transform-origin: bottom left;
}
&.check {
--icon: var(--icon-check-square-offset);
position: absolute;
transform: scale(0.5);
transform-origin: top right;
opacity: 0;
inset: 0.5rem;
}
}
}
&.active {
animation: active-shimmer var(--transition-long);
border-block-end-color: var(--bg-muted-neutral-1);
button {
opacity: 1;
.icon.copy {
transform: scale(0.5);
opacity: 0;
}
.icon.check {
transform: none;
opacity: 1;
}
}
@keyframes active-shimmer {
to {
background-position-x: -200%;
}
}
}
}
pre {
margin: 0;
box-shadow: none;
border-radius: 0 0 var(--rounded-corner) var(--rounded-corner);
}
}

View file

@ -0,0 +1,73 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
.search-fuse {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 40rem;
margin: 0 auto;
&-input-wrap {
position: relative;
display: flex;
align-items: center;
}
&-icon {
position: absolute;
left: 0.75rem;
color: var(--fg-muted-2);
pointer-events: none;
transition:
color var(--transition),
opacity var(--transition-bezier);
opacity: 0.7;
}
&-input {
width: 100%;
padding: 0.75rem 1rem 0.75rem 2.5rem;
border-radius: 0.5rem;
border: 1px solid var(--bg-muted-2);
background-color: var(--bg-muted-1);
color: var(--fg-color);
font-size: var(--font-size-medium);
transition:
border-color var(--transition),
box-shadow var(--transition),
background-color var(--transition);
&:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 2px var(--accent-bg-1), 0 0 0 4px var(--accent-color);
}
&::placeholder {
color: var(--fg-muted-2);
}
}
&-results {
display: flex;
flex-direction: column;
gap: 0.25rem;
.search-fuse-result {
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
background-color: var(--bg-muted-1);
text-decoration: none;
color: var(--fg-color);
transition:
background-color var(--transition),
padding-left var(--transition);
&:hover {
background-color: var(--bg-muted-2);
padding-left: 1rem;
}
}
}
}

View file

@ -0,0 +1,128 @@
#search-modal {
display: flex;
position: fixed;
flex-direction: column;
align-items: center;
visibility: hidden;
opacity: 0;
z-index: 9999;
transition:
visibility var(--transition),
opacity var(--transition);
inset: 0;
background-color: var(--shade-color);
padding-block-start: 15vh;
width: 100%;
height: 100vh;
&.active {
visibility: visible;
opacity: 1;
#search-modal-content {
transform: none;
}
}
&-content {
display: flex;
flex-direction: column;
gap: 1rem;
transform: var(--hover);
backdrop-filter: var(--blur);
-webkit-backdrop-filter: var(--blur);
transition:
border-radius var(--transition),
padding var(--transition),
max-width var(--transition),
transform var(--transition-bezier-longer);
margin: -0.5rem;
box-shadow: var(--edge-highlight), var(--shadow-glass);
border-radius: 2.25rem 2.25rem 1.75rem 1.75rem;
background-color: var(--glass-bg-1);
padding: 1rem;
width: calc(100% - 2rem);
max-width: calc(var(--content-width) / 1.5);
overflow: hidden;
&:has(#search-results:empty) {
border-radius: 1.75rem;
padding: 0.5rem;
max-width: calc(var(--content-width) / 2);
}
& > div {
position: relative;
}
}
#search-input {
flex-shrink: 0;
box-shadow: var(--edge-highlight);
border-radius: 9999px;
padding: 0 1rem;
width: 100%;
height: 2.5rem;
&:placeholder-shown + div .icon {
transform: none;
opacity: var(--disabled-opacity);
pointer-events: all;
}
& + div .icon {
position: absolute;
transform: translateX(0.5rem);
opacity: 0;
transition:
opacity var(--transition),
transform var(--transition-bezier);
cursor: help;
inset-block-start: 0.625rem;
inset-inline-end: 1rem;
pointer-events: none;
color: var(--fg-muted-2);
font-size: 1.25rem;
}
}
#search-results {
display: none;
flex: 1;
flex-direction: column;
gap: 0.5rem;
margin: -1rem;
padding: 1rem;
max-height: 50vh;
overflow: auto;
scrollbar-width: thin;
.item {
display: inline-flex;
flex-direction: column;
box-shadow: var(--edge-highlight);
border-radius: var(--rounded-corner);
background-color: var(--bg-muted-1);
padding: 0.75rem 1rem;
a {
text-decoration: none;
&::after {
content: " →";
}
}
span {
margin-block-start: 0.25rem;
border-block-start: rpx(1) solid var(--bg-muted-2);
padding-block-start: 0.25rem;
color: var(--fg-muted-2);
strong {
color: var(--fg-color);
}
}
}
}
}

View file

@ -0,0 +1,23 @@
#skip-to-content {
position: absolute;
transform: translateX(-50%) translateY(calc(-100% - 1rem));
z-index: 9999;
transition:
backdrop-filter var(--transition),
box-shadow var(--transition),
transform var(--transition-bezier);
inset-inline-start: 50vw;
border-radius: 9999px;
background-color: var(--glass-bg-1);
padding: 0.375rem 0.75rem;
width: max-content;
text-decoration: none;
&:focus-visible,
&:focus {
-webkit-backdrop-filter: var(--blur);
transform: translateX(-50%) translateY(1rem);
backdrop-filter: var(--blur);
box-shadow: var(--edge-highlight), var(--shadow);
}
}

View file

@ -0,0 +1,61 @@
@use "../citrine/abstracts/functions" as *;
.statement-container {
margin: 1rem 0;
box-shadow: var(--edge-highlight), var(--shadow);
border-radius: var(--rounded-corner);
padding: 1rem;
:last-child {
margin-block-end: 0;
}
& > :nth-child(2) {
margin-block-start: 0.5rem;
}
.title {
font-weight: var(--font-weight-semi-bold);
.icon {
vertical-align: rpx(-2);
margin-inline-end: 0.25rem;
}
}
&.archive {
--accent-color: var(--purple-fg);
--accent-highlight: hsla(
var(--purple-h),
var(--purple-s),
var(--purple-l),
var(--disabled-opacity)
);
background-color: var(--purple-bg-1);
color: var(--purple-fg);
}
&.disclaimer {
--accent-color: var(--red-fg);
--accent-highlight: hsla(
var(--red-h),
var(--red-s),
var(--red-l),
var(--disabled-opacity)
);
background-color: var(--red-bg-1);
color: var(--red-fg);
}
&.trigger {
--accent-color: var(--yellow-fg);
--accent-highlight: hsla(
var(--yellow-h),
var(--yellow-s),
var(--yellow-l),
var(--disabled-opacity)
);
background-color: var(--yellow-bg-1);
color: var(--yellow-fg);
}
}

View file

@ -0,0 +1,59 @@
@use "../citrine/abstracts/functions" as *;
#tag-chips {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin: 0;
padding-inline-start: 0;
list-style: none;
li {
display: flex;
margin-block: 0;
padding-inline-start: 0;
}
a {
display: flex;
transition:
background-color var(--transition),
color var(--transition),
transform var(--transition-bezier);
box-shadow: var(--edge-highlight);
border-radius: 1rem;
background-color: var(--bg-muted-1);
padding: 0.25rem;
padding-inline-start: 0.75rem;
color: var(--fg-muted-1);
font-size: var(--font-size-small);
line-height: 1.5rem;
text-decoration: none;
&:hover {
background-color: var(--bg-muted-2);
color: var(--fg-muted-2);
}
&:active {
transform: var(--active);
}
&::before {
content: "#";
}
.counter {
transition: inherit;
margin-inline-start: 0.375rem;
box-shadow: var(--edge-highlight);
border-radius: inherit;
background-color: inherit;
aspect-ratio: 1 / 1;
height: 1.5rem;
line-height: 1.5rem;
font-variant-numeric: tabular-nums;
text-align: center;
}
}
}

View file

@ -0,0 +1,50 @@
h1,
h2,
h3,
h4,
h5,
h6 {
&:hover .zola-anchor {
transform: none;
opacity: 1;
}
&:has(.zola-anchor) {
position: relative;
}
}
.zola-anchor {
position: absolute;
transform: translateX(0.25em);
opacity: 0;
transition:
color var(--transition),
opacity var(--transition),
transform var(--transition-bezier);
inset-inline-start: -1.25em;
padding-inline-end: 0.25em;
color: var(--fg-muted-1);
line-height: 1;
@media only screen and (max-width: 720px) {
display: none;
}
&:hover {
color: var(--fg-muted-2);
}
&:active {
transform: var(--active);
}
&:focus-visible {
opacity: 1;
}
}
:target .zola-anchor {
transform: none;
opacity: var(--disabled-opacity);
}

View file

@ -0,0 +1,55 @@
// Based on https://github.com/welpo/tabi/blob/68c35b02f8c09e21ecff48f61bad6da3d96ce337/sass/parts/_zola-error.scss
body > div:last-child:has(div:last-child[style]:not([class]):not([id])) {
z-index: 9999 !important;
background-color: var(--shade-color) !important;
}
body > div:last-child > div:last-child[style]:not([class]):not([id]) {
box-shadow: var(--edge-highlight), var(--shadow-glass);
border-radius: calc(var(--rounded-corner) + 1rem) !important;
background-color: hsla(
var(--red-h),
calc(var(--red-s) - var(--red-s) * 0.7),
calc(var(--red-l) + (100% - var(--red-l)) * 0.9),
var(--dim-opacity)
) !important;
padding: 1rem !important;
width: fit-content;
max-width: calc(100% - 2rem);
-webkit-backdrop-filter: var(--blur);
backdrop-filter: var(--blur);
filter: none !important;
@media (prefers-color-scheme: dark) {
& {
background-color: hsla(
var(--red-h),
calc(var(--red-s) - var(--red-s) * 0.6),
calc(var(--red-l) - var(--red-l) * 0.8),
var(--dim-opacity)
) !important;
}
}
}
body
> div:last-child
> div:last-child[style]:not([class]):not([id])
> p[style]:first-child {
margin-block-end: 1rem !important;
color: var(--red-fg) !important;
font-weight: var(--font-weight-semi-bold) !important;
font-size: var(--font-size-large) !important;
}
body
> div:last-child
> div:last-child[style]:not([class]):not([id])
> pre[style]:last-child {
border-radius: var(--rounded-corner) !important;
background-color: var(--red-bg-1) !important;
padding: 0.75rem 1rem !important;
color: var(--red-fg) !important;
font-weight: var(--font-weight-normal) !important;
}

52
sass/fonts.scss Normal file
View file

@ -0,0 +1,52 @@
$commissioner-version: "1.012.1";
$commit-mono-version: "1.143";
:root {
--font-sans-serif: "Commissioner";
--font-monospace: "Commit Mono";
}
// Commissioner
@font-face {
font-style: normal;
font-weight: 100 900;
src: url("/fonts/commissioner.woff2?v=#{$commissioner-version}")
format("woff2");
font-family: "Commissioner";
font-display: swap;
// Fix vertical alignment,
// See https://github.com/kosbarts/Commissioner/issues/55
ascent-override: 96.3%;
descent-override: 25%;
}
@font-face {
font-style: italic;
font-weight: 100 900;
src: url("/fonts/commissioner.woff2?v=#{$commissioner-version}")
format("woff2");
font-family: "Commissioner";
font-display: swap;
font-variation-settings: "slnt" -12;
// Ditto
ascent-override: 96.3%;
descent-override: 25%;
}
// Commit Mono
@font-face {
font-style: normal;
font-weight: 100 900;
src: url("/fonts/commit-mono.woff2?v=#{$commit-mono-version}") format("woff2");
font-family: "Commit Mono";
font-display: swap;
}
@font-face {
font-style: italic;
font-weight: 100 900;
src: url("/fonts/commit-mono.woff2?v=#{$commit-mono-version}") format("woff2");
font-family: "Commit Mono";
font-display: swap;
font-variation-settings: "ital" 1;
}

140
sass/layout/_footer.scss Normal file
View file

@ -0,0 +1,140 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
#site-footer {
--padding: 1rem;
display: flex;
grid-area: footer;
gap: 1rem;
margin-block: 3.25rem 1rem;
border-block-start: rpx(1) solid var(--bg-muted-2);
padding: 1rem;
width: 100%;
min-width: 0;
max-width: calc(var(--content-width) + var(--padding) * 2);
@media only screen and (max-width: 480px) {
flex-direction: column-reverse;
}
& > div {
display: flex;
flex: 1;
flex-direction: column;
gap: 1rem;
p {
margin: 0;
}
&:nth-of-type(1) {
color: var(--fg-muted-2);
font-size: var(--font-size-small);
}
&:nth-of-type(2) {
justify-content: space-between;
align-items: flex-end;
text-align: end;
@media only screen and (max-width: 480px) {
align-items: normal;
text-align: start;
}
}
}
a.external::after {
margin-inline-start: 0.25rem;
}
.link {
@include chip;
display: inline-block;
line-height: 1.5rem;
&:hover {
background-color: var(--bg-muted-2);
}
&:active {
transform: var(--active);
}
}
// IMO it's pretty cute
.webrings {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-block: 0;
padding-inline-start: 0;
list-style: none;
}
.webring {
display: flex;
gap: 0.25rem;
margin-block: 0;
padding-inline-start: 0;
.random,
.previous,
.next {
display: inline-flex;
justify-content: center;
align-items: center;
border-radius: 9999px;
padding: 0.25rem;
font-size: 1rem;
}
}
.timestamp {
color: var(--fg-muted-1);
font-size: var(--font-size-xx-small);
font-family: var(--font-monospace);
text-transform: uppercase;
}
.socials {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
margin: 0;
padding-inline-start: 0;
list-style: none;
@media only screen and (max-width: 480px) {
justify-content: normal;
}
li {
margin-block: 0;
padding-inline-start: 0;
}
a {
display: flex;
transition:
color var(--transition),
transform var(--transition-bezier);
height: fit-content;
color: var(--fg-muted-1);
&:hover {
color: var(--fg-muted-2);
}
&:active {
transform: var(--active);
}
}
svg {
fill: currentColor;
width: 1.5rem;
height: 1.5rem;
}
}
}

View file

@ -0,0 +1,13 @@
.full-bleed {
transform: translateX(-50%);
margin-inline-start: 50%;
width: calc(100vw - var(--sidebar-width));
:root[dir*="rtl"] & {
transform: translateX(50%);
}
@media only screen and (max-width: 960px) {
width: calc(100vw - 3.5rem);
}
}

16
sass/layout/_grid.scss Normal file
View file

@ -0,0 +1,16 @@
body {
display: grid;
grid-template-rows: auto auto 1fr auto;
grid-template-columns: var(--sidebar-width) 1fr;
grid-template-areas:
"sidebar header"
"sidebar banner"
"sidebar main "
"sidebar footer";
justify-items: center;
transition: grid-template-columns var(--transition);
@media only screen and (max-width: 960px) {
grid-template-columns: 0 1fr;
}
}

347
sass/layout/_header.scss Normal file
View file

@ -0,0 +1,347 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
#site-header {
display: flex;
position: sticky;
grid-area: header;
// height: 3rem;
justify-content: space-between;
// flex-wrap: nowrap;
align-items: center;
gap: 0.25rem;
z-index: 2;
// height: 3.75rem;
inset-block-start: 0;
// padding: 0.5rem 0.75rem;
padding: 0.5rem;
width: 100%;
min-width: 0;
&::before,
&::after {
position: absolute;
inset: 0;
@include easing-mask(bottom);
inset-block-end: -100%;
pointer-events: none;
content: "";
}
&::before {
z-index: -2;
backdrop-filter: var(--blur);
-webkit-backdrop-filter: var(--blur);
}
&::after {
z-index: -1;
background-color: var(--bg-color);
}
.title {
margin-inline: 0.75rem;
min-width: 0;
overflow: hidden;
color: var(--fg-muted-2);
font-size: var(--font-size-small);
line-height: 2rem;
text-overflow: ellipsis;
white-space: nowrap;
}
.buttons {
display: flex;
flex-wrap: nowrap;
gap: 0.25rem;
margin: 0;
// margin-inline-start: auto;
box-shadow: var(--edge-highlight), var(--shadow-glass);
border-radius: 1.125rem;
background-color: var(--glass-bg-1);
padding: 0.25rem;
list-style: none;
li {
flex: 0;
margin-block: 0;
padding-inline-start: 0;
}
& > li {
height: fit-content;
line-height: 0;
}
button,
details {
box-shadow: none;
background-color: transparent;
line-height: inherit;
.icon {
margin-inline-end: 0;
box-shadow: none;
background-color: transparent;
padding: 0;
}
}
button,
summary {
border-radius: 50%;
padding: 0.375rem;
&:hover {
box-shadow: var(--edge-highlight);
background-color: var(--bg-muted-1);
color: var(--fg-muted-2);
}
&:active {
transform: var(--active);
}
}
details {
box-shadow: none;
border-radius: 0;
padding: 0;
}
button {
font-size: var(--font-size-medium);
}
summary {
&:hover {
box-shadow: var(--edge-highlight);
background-color: var(--bg-muted-1);
}
&::before {
display: none;
}
}
details {
display: block;
position: relative;
margin-block-end: 0;
&::details-content {
block-size: auto;
}
&[open] {
&::details-content {
block-size: auto;
}
summary {
color: var(--fg-muted-1);
&:hover {
color: var(--fg-muted-2);
}
}
@supports not (block-size: calc-size(auto, size)) {
summary ~ * {
animation: none;
}
}
}
}
}
}
.buttons .dropdown {
display: flex;
position: absolute;
flex-direction: column;
gap: 0.25rem;
transform-origin: top right;
z-index: 1;
backdrop-filter: var(--blur);
-webkit-backdrop-filter: var(--blur);
animation: expand-up-right var(--transition-bezier);
inset-block-start: calc(100% + 0.75rem);
inset-inline-end: -0.25rem;
box-shadow: var(--edge-highlight), var(--shadow-glass);
border-radius: calc(var(--rounded-corner) + 0.25rem);
background-color: var(--glass-bg-1);
padding: 0.25rem;
width: max-content;
min-width: 8rem;
list-style: none;
:root[dir*="rtl"] & {
transform-origin: top left;
animation: expand-up-left var(--transition-bezier);
}
li {
flex: 1;
}
a {
box-shadow: none;
border-radius: var(--rounded-corner);
background-color: transparent;
padding: 0.375rem 0.75rem;
width: 100%;
&:hover {
box-shadow: var(--edge-highlight);
background-color: var(--bg-muted-1) !important;
}
}
}
#site-sidebar-controls {
display: flex;
flex-shrink: 0;
gap: 0.25rem;
transition:
width var(--transition),
visibility var(--transition);
margin-block: 0;
box-shadow: var(--edge-highlight), var(--shadow-glass);
border-radius: 1.125rem;
background-color: var(--glass-bg-1);
padding: 0.25rem;
height: fit-content;
line-height: 0;
list-style: none;
@media only screen and (max-width: 960px) {
width: 2.25rem;
&:has(#expand-sidebar:checked) {
width: 6.25rem;
#site-sidebar-modes {
transform: none;
visibility: visible;
opacity: 1;
}
}
}
ul {
padding-inline-start: 0;
list-style: none;
}
li {
display: inline-block;
margin-block: 0;
padding-inline-start: 0;
label {
display: inline-block;
transition:
box-shadow var(--transition),
background-color var(--transition),
color var(--transition),
transform var(--transition-bezier);
cursor: pointer;
border-radius: 50%;
padding: 0.375rem;
height: fit-content;
color: var(--fg-muted-1);
line-height: 0;
&:hover {
box-shadow: var(--edge-highlight);
background-color: var(--bg-muted-1);
color: var(--fg-muted-2);
}
&:active {
transform: var(--active);
}
}
input[type="radio"] {
& + label {
position: relative;
z-index: 1;
&::before {
@include gradient-fill;
position: absolute;
opacity: 0;
z-index: -1;
transition:
opacity var(--transition),
box-shadow var(--transition),
background-color var(--transition);
inset: 0;
border-radius: inherit;
content: "";
}
}
&:checked + label {
color: var(--contrast-color);
&:hover {
box-shadow: none;
background-color: transparent;
}
&:hover::before {
box-shadow: var(--edge-highlight);
background-color: var(--accent-color);
}
&::before {
opacity: 1;
}
}
}
input[type="radio"],
input[type="checkbox"] {
&:focus-visible + label {
animation: focus-in var(--transition);
outline: 0.125rem solid var(--accent-color);
outline-offset: 0.125rem;
}
@supports not selector(:focus-visible) {
&:focus + label {
animation: focus-in var(--transition);
outline: 0.125rem solid var(--accent-color);
outline-offset: 0.125rem;
}
}
}
}
#expand-sidebar-li {
display: none;
@media only screen and (max-width: 960px) {
display: inline-block;
}
}
}
#site-sidebar-modes {
display: flex;
gap: 0.25rem;
transform-origin: left;
transition:
visibility var(--transition),
opacity var(--transition),
transform var(--transition);
@media only screen and (max-width: 960px) {
transform: translateX(-2.25rem);
visibility: hidden;
opacity: 0;
}
}

6
sass/layout/_index.scss Normal file
View file

@ -0,0 +1,6 @@
@use "footer";
@use "full-bleed";
@use "grid";
@use "header";
@use "main-content";
@use "sidebar";

View file

@ -0,0 +1,59 @@
#main-content {
--padding: 1rem;
grid-area: main;
transform-origin: 50% 50vh;
animation: fade-in-zoom var(--transition-bezier-longer);
margin-block-start: 3.25rem;
padding-inline: 1rem;
width: 100%;
min-width: 0;
max-width: calc(var(--content-width) + var(--padding) * 2);
@supports (transform-origin: 50% 50dvh) {
transform-origin: 50% 50dvh;
}
& > :first-child {
margin-block-start: 0;
}
& > :last-child {
margin-block-end: 0;
}
@keyframes fade-in-zoom {
from {
transform: scale(0.95);
opacity: 0;
}
}
}
// Don't play animation when jumping to section
:target ~ #main-content {
animation-name: none;
}
// Loading animation when going up in the sidebar
#↑:target ~ #main-content,
#up:target ~ #main-content {
animation-name: slide-in-up-longer;
}
// Loading animation when going down in the sidebar
#↓:target ~ #main-content,
#down:target ~ #main-content {
animation-name: slide-in-down-longer;
}
// Loading animation when going to the previous article
#←:target ~ #main-content,
#left:target ~ #main-content {
animation-name: slide-in-left-longer;
}
// Loading animation when going to the next article
#→:target ~ #main-content,
#right:target ~ #main-content {
animation-name: slide-in-right-longer;
}

775
sass/layout/_sidebar.scss Normal file
View file

@ -0,0 +1,775 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
#site-sidebar {
display: flex;
position: sticky;
grid-area: sidebar;
flex-direction: column;
z-index: 3;
transition-duration: var(--transition);
transition-property:
visibility, transform, inset-block-start, box-shadow, border-inline-end,
border-start-end-radius, border-end-end-radius, height;
inset-block-start: 0;
inset-inline-start: 0;
border-inline-end: rpx(1) solid var(--bg-muted-2);
width: var(--sidebar-width);
height: 100vh;
@supports (height: 100dvh) {
height: 100dvh;
}
@media only screen and (max-width: 960px) {
transform: translateX(-100%);
visibility: hidden;
inset-block-start: 3.25rem;
box-shadow: var(--edge-highlight), var(--shadow);
border-inline-end: 0 solid transparent;
border-start-end-radius: var(--rounded-corner);
border-end-end-radius: var(--rounded-corner);
height: calc(100vh - 3.75rem);
:root[dir*="rtl"] & {
transform: translateX(100%);
}
@supports (height: 100dvh) {
height: calc(100dvh - 3.75rem);
}
#site-header:has(#expand-sidebar:checked) ~ & {
transform: none;
visibility: visible;
backdrop-filter: var(--blur);
-webkit-backdrop-filter: var(--blur);
background-color: var(--glass-bg-1);
}
}
&-content {
display: flex;
position: relative;
flex: 1;
overflow: hidden;
}
}
#site-sidebar-header {
display: flex;
align-items: center;
transform-origin: rpx(22) 50%;
transition:
box-shadow var(--transition),
background-color var(--transition),
color var(--transition),
transform var(--transition-bezier);
margin: 1rem;
margin-block-end: 0;
border-radius: 1.375rem;
padding: 0.25rem;
padding-inline-end: 0.75rem;
min-width: 0;
color: var(--fg-muted-1);
text-decoration: none;
&:hover {
box-shadow: var(--edge-highlight);
background-color: var(--bg-muted-1);
color: var(--fg-muted-2);
.icon {
background-color: var(--bg-muted-2);
}
}
&:active {
transform: var(--active);
}
img {
margin: 0;
}
img,
.icon {
margin-inline-end: 0.5rem;
border-radius: 50%;
}
img {
width: 2.25rem;
height: 2.25rem;
}
.icon {
transition: background-color var(--transition);
box-shadow: var(--edge-highlight);
background-color: var(--bg-muted-1);
padding: 0.5rem;
font-size: 1.25rem;
}
div {
display: flex;
flex: 1;
flex-direction: column;
gap: 0.125rem;
min-width: 0;
line-height: 1.25;
span:nth-of-type(1) {
overflow: hidden;
font-size: var(--font-size-small);
text-overflow: ellipsis;
white-space: nowrap;
}
span:nth-of-type(2) {
font-weight: var(--font-weight-normal);
font-size: var(--font-size-x-small);
}
}
}
#sidebar-nav,
#sidebar-meta {
display: flex;
flex: 1;
flex-direction: column;
transition:
visibility var(--transition),
opacity var(--transition),
filter var(--transition),
transform var(--transition-bezier);
padding: 1rem;
width: var(--sidebar-width);
min-width: 100%;
overflow: hidden auto;
scrollbar-width: thin;
}
#site-header:has(#site-sidebar-modes #mode-meta:checked) + #site-sidebar {
#sidebar-nav {
transform: perspective(var(--content-width)) rotateY(15deg);
opacity: var(--disabled-opacity);
filter: blur(0.25rem);
}
#sidebar-meta {
transform: perspective(var(--content-width));
visibility: visible;
opacity: 1;
filter: none;
}
}
#sidebar-nav {
transform: perspective(var(--content-width));
transform-origin: left;
:root[dir*="rtl"] & {
transform-origin: right;
}
a {
display: flex;
flex: 1;
align-items: center;
transform-origin: rpx(22) 50%;
transition:
box-shadow var(--transition),
background-color var(--transition),
color var(--transition),
transform var(--transition-bezier);
border-radius: 1.375rem;
padding: 0.25rem;
padding-inline-end: 0.75rem;
overflow: hidden;
color: var(--fg-muted-1);
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
box-shadow: var(--edge-highlight);
background-color: var(--bg-muted-1);
color: var(--fg-muted-2);
.icon {
background-color: var(--bg-muted-2);
}
}
&:active {
transform: var(--active);
}
&[aria-current="page"] {
box-shadow: var(--edge-highlight);
background-color: var(--bg-muted-1);
color: var(--accent-color);
.icon {
@include gradient-fill;
text-shadow: none;
}
&:hover {
background-color: var(--bg-muted-2);
.icon {
@include gradient-fill(hover);
}
}
}
&.new .icon {
position: relative;
&::after {
position: absolute;
inset-block-start: rpx(1);
inset-inline-end: rpx(1);
border-radius: 50%;
background-color: var(--accent-color);
width: 0.75rem;
height: 0.75rem;
content: "";
}
}
}
.icon {
transition:
background-color var(--transition),
color var(--transition),
box-shadow var(--transition);
margin-inline-end: 0.5rem;
box-shadow: var(--edge-highlight);
border-radius: 50%;
background-color: var(--bg-muted-1);
padding: 0.5rem;
font-size: 1.25rem;
&.fallback {
width: 1.25rem;
font-weight: var(--font-weight-semi-bold);
font-size: var(--font-size-medium);
line-height: 1.25rem;
font-family: var(--font-monospace);
text-align: center;
&::before {
content: none;
}
}
}
details {
flex: 1;
margin-block-end: 0;
box-shadow: none;
border-radius: 0;
background-color: transparent;
padding: 0;
&[open] {
summary {
transform: none;
opacity: 1;
box-shadow: none;
background-color: transparent;
&::before {
transform: none;
}
@supports not (block-size: calc-size(auto, size)) {
& ~ * {
animation: none;
}
}
}
}
summary {
display: flex;
position: relative;
align-items: center;
border-radius: 0;
padding: 0.75rem 0;
color: var(--fg-muted-1);
font-weight: var(--font-weight-normal);
font-size: var(--font-size-small);
line-height: var(--font-size-small);
white-space: nowrap;
&:hover {
box-shadow: none;
background-color: transparent;
}
&:active {
transform: none;
}
&::before {
position: absolute;
transform: rotate(-90deg);
transition: transform var(--transition-bezier);
margin-inline-end: 0;
inset-block: 0.5rem;
inset-inline-end: 0;
font-size: 1rem;
}
.line {
flex: 1;
margin-inline: 0.5rem 1.5rem;
background-color: var(--bg-muted-2);
height: rpx(1);
}
}
ul {
margin-block: 0;
}
}
ul {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-block: auto;
padding-inline-start: 0;
list-style: none;
}
li {
display: flex;
transition: width var(--transition);
margin-block: 0;
padding-inline-start: 0;
width: 100%;
}
}
#sidebar-meta {
position: absolute;
transform: perspective(var(--content-width)) rotateY(-30deg);
transform-origin: left;
visibility: hidden;
opacity: 0;
filter: blur(0.25rem);
inset: 0;
:root[dir*="rtl"] & {
transform-origin: right;
}
&:has(> div:empty) {
.status-page {
display: flex;
}
& > div {
display: none;
}
}
& > div {
display: flex;
flex-direction: column;
gap: 1rem;
margin-block: auto;
}
.card {
background-color: var(--glass-bg-1);
}
.while-writing {
display: inherit;
position: relative;
flex-direction: inherit;
gap: inherit;
margin: calc(-0.25rem - rpx(1));
margin-block-start: 1rem;
border: rpx(1) solid var(--bg-muted-2);
border-block-start: none;
border-end-start-radius: calc(var(--rounded-corner) + 0.25rem + #{rpx(1)});
border-end-end-radius: calc(var(--rounded-corner) + 0.25rem + #{rpx(1)});
padding: 0.25rem;
.card-title {
display: flex;
position: absolute;
gap: 0.25rem;
inset-block-start: calc(var(--rounded-corner-small) * -1);
inset-inline: rpx(-1);
height: var(--rounded-corner-small);
.line {
border: rpx(1) solid var(--bg-muted-2);
border-block-end: none;
}
.line:nth-of-type(1) {
border-inline-end: none;
border-start-start-radius: var(--rounded-corner);
width: var(--rounded-corner);
}
.line:nth-of-type(2) {
flex: 1;
border-inline-start: none;
border-start-end-radius: var(--rounded-corner);
}
.title {
color: var(--fg-muted-2);
font-size: var(--font-size-small);
line-height: 0;
}
}
}
.weather {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
border-radius: 1.375rem;
padding: 0.25rem;
padding-inline-end: 1.25rem;
line-height: 1;
&.cloudy {
animation: perlin 12s linear infinite;
background-image: url("/weather/perlin.png");
@media (prefers-color-scheme: dark) {
background-image: url("/weather/perlin-dark.png");
}
}
&.showers {
animation: rain 3s linear infinite;
background-image: url("/weather/rain.svg");
@media (prefers-color-scheme: dark) {
background-image: url("/weather/rain-dark.svg");
}
}
&.snowing {
animation: snow 12s linear infinite;
background-image: url("/weather/snow.svg");
@media (prefers-color-scheme: dark) {
background-image: url("/weather/snow-dark.svg");
}
}
div {
display: flex;
align-items: center;
gap: 0.5rem;
}
.title {
color: inherit;
font-size: var(--font-size-medium);
}
.icon {
box-shadow: var(--edge-highlight);
border-radius: 50%;
background-color: var(--bg-muted-1);
padding: 0.5rem;
font-size: 1.25rem;
}
.condition {
color: var(--fg-muted-2);
font-size: var(--font-size-small);
}
@keyframes rain {
to {
background-position: 256px 320px;
}
}
@keyframes perlin {
to {
background-position: -256px -64px;
}
}
@keyframes snow {
to {
background-position: 256px 512px;
}
}
}
.music {
display: flex;
position: relative;
flex-direction: column;
gap: 0.5rem;
border-radius: 1rem;
&::after {
position: absolute;
z-index: 1;
inset: 0;
box-shadow: var(--edge-highlight);
border-radius: inherit;
max-height: 5rem;
pointer-events: none;
content: "";
}
.carousel {
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
display: flex;
margin-block: 0;
box-shadow: var(--shadow);
border-radius: inherit;
background-color: var(--glass-bg-1);
padding-inline-start: 0;
overflow-x: auto;
scroll-behavior: smooth;
scrollbar-width: none;
list-style: none;
li {
display: flex;
position: relative;
flex-shrink: 0;
gap: 1rem;
margin-block: 0;
outline: none;
padding: 0.5rem;
width: 100%;
overflow: hidden;
scroll-snap-align: start;
&.has-cover {
animation: cover-alternate 60s ease-in-out infinite;
background-image: var(--bg-overlay), var(--blurnail);
background-position: center;
background-size: 2000%;
@keyframes cover-alternate {
// X 100%
// Y ┌─────────────┐
// │ 1,9 │
// │ 8 2 │
// │ 3,7 │
// │ 6 4 │
// │ 5 │
// 100% └─────────────┘
from {
background-position: 50% 0%;
}
12.5% {
background-position: 100% 25%;
}
25% {
background-position: 50% 50%;
}
37.5% {
background-position: 100% 75%;
}
50% {
background-position: 50% 100%;
}
62.5% {
background-position: 0% 75%;
}
75% {
background-position: 50% 50%;
}
87.5% {
background-position: 0% 25%;
}
to {
background-position: 50% 0%;
}
}
.music-type {
color: var(--fg-muted-neutral-1);
}
.music-artist {
color: var(--fg-muted-neutral-2);
}
}
& > div {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-between;
gap: 0.25rem;
margin-inline-end: 0.5rem;
margin-block: 0.25rem;
min-width: 0;
min-width: 0;
line-height: 1;
}
a {
position: absolute;
z-index: 1;
inset: 0;
border-radius: inherit;
}
}
}
.music-type {
color: var(--fg-muted-1);
font-size: var(--font-size-xx-small);
}
.music-artist {
color: var(--fg-muted-2);
font-size: var(--font-size-small);
}
.title {
color: var(--fg-color);
font-size: var(--font-size-medium);
}
img {
margin: 0;
border-radius: var(--rounded-corner-small);
}
&-paginator {
display: flex;
justify-content: center;
gap: 0.25rem;
margin-inline: 1rem;
margin-block: 0;
padding-inline-start: 0;
list-style: none;
li {
display: flex;
margin-block: 0;
padding-inline-start: 0;
height: 0.75rem;
}
a {
transition:
width var(--transition),
height var(--transition),
background-color var(--transition),
transform var(--transition-bezier);
margin-block: auto;
border-radius: 0.375rem;
background-color: var(--fg-muted-1);
width: 1.5rem;
height: 0.375rem;
&:hover {
background-color: var(--fg-muted-2);
}
&:active {
transform: var(--active);
}
&.active {
background-color: var(--fg-color);
width: 0.75rem;
height: 0.75rem;
}
}
}
}
.toc {
display: flex;
flex-direction: column;
max-height: calc(var(--content-width) / 3);
@media only screen and (max-width: 720px) {
max-height: calc(var(--content-width) / 2);
}
.title {
margin-block-end: 1rem;
}
.overshoot {
flex: 1;
margin: -1rem;
padding: 1rem;
overflow: auto;
scrollbar-width: thin;
ul {
margin-block: 0;
padding-inline-start: 1rem;
color: var(--fg-muted-1);
font-size: var(--font-size-small);
}
li {
margin-block: 0;
padding-inline-start: 0;
&::marker {
color: inherit;
}
}
a {
color: inherit;
text-decoration: none;
}
}
}
.status-page {
display: none;
margin-block: auto;
text-align: center;
.title,
p {
color: var(--fg-muted-2);
}
.icon {
margin-inline: auto;
color: var(--fg-muted-1);
font-size: 3rem;
}
p {
margin-block: 0;
}
}
}

View file

@ -0,0 +1,44 @@
@use "../citrine/abstracts/functions" as *;
@use "../citrine/abstracts/mixins" as *;
#site-sidebar-header {
transform-origin: rpx(18) 50%;
border-radius: var(--rounded-corner);
img,
.icon {
border-radius: var(--rounded-corner-small);
}
img {
width: 2rem;
height: 2rem;
}
.icon {
padding: 0.375rem;
}
div {
gap: 0;
line-height: 1.125;
}
}
#sidebar-nav {
a {
transform-origin: rpx(18) 50%;
border-radius: var(--rounded-corner);
padding: 0.25rem;
&.new .icon::after {
inset-block-start: rpx(-2);
inset-inline-end: rpx(-2);
}
}
.icon {
border-radius: var(--rounded-corner-small);
padding: 0.375rem;
}
}

27
sass/mods/_flat.scss Normal file
View file

@ -0,0 +1,27 @@
@use "../citrine/abstracts/functions" as *;
:root {
--color-opacity-1: 0.15;
--color-opacity-2: 0.25;
--edge-highlight: 0 0 0 transparent;
--shadow: 0 0 0 transparent;
--shadow-raised: 0 0 0 transparent;
--shadow-glass: 0 0 0 transparent;
--shadow-glow: 0 0 0 transparent;
}
.buttons .suggested,
#site-nav a.active .icon {
box-shadow: none;
background-image: none;
background-color: var(--accent-color);
text-shadow: none;
}
.buttons .suggested:hover {
opacity: var(--dim-opacity);
}
.crt {
outline: rpx(1) solid var(--bg-muted-2);
}

27
sass/mods/_pill-tags.scss Normal file
View file

@ -0,0 +1,27 @@
@use "../citrine/abstracts/mixins" as *;
.article-details .tags {
gap: 0.25rem;
margin-block-end: 0 !important;
a {
@include chip;
display: flex;
color: var(--fg-muted-1);
font-weight: var(--font-weight-semi-bold);
line-height: 1.5rem;
&:hover {
background-color: var(--bg-muted-2);
color: var(--fg-muted-2);
}
&:active {
transform: var(--active);
}
&::before {
content: none;
}
}
}

View file

@ -0,0 +1,30 @@
// UNFINISHED, AND LIKELY WILL NEVER BE
@use "../citrine/abstracts/functions" as *;
#site-nav {
a,
label,
button,
summary {
outline: rpx(1) solid var(--bg-muted-2);
box-shadow: var(--edge-highlight), var(--shadow);
background-image: linear-gradient(
to bottom,
var(--bg-muted-2),
var(--bg-muted-1) 50%,
transparent 50%,
var(--bg-muted-2)
);
.icon {
background-image: conic-gradient(
var(--bg-muted-2),
var(--bg-muted-1) 33%,
transparent,
var(--bg-muted-2) 66%,
transparent,
var(--bg-muted-2)
);
}
}
}

40
sass/style.scss Normal file
View file

@ -0,0 +1,40 @@
/*
* Ametrine v0.1.0-alpha (https://ametrine.daudix.one)
* Copyright 2025 David "Daudix" Lapshin and contributors
* Licensed under MIT (https://codeberg.org/daudix/ametrine/raw/branch/main/LICENSE.txt)
*/
@use "layout/footer";
@use "layout/full-bleed";
@use "layout/grid";
@use "layout/header";
@use "layout/main-content";
@use "layout/sidebar";
@use "components/alerts";
@use "components/article-details";
@use "components/article-list";
@use "components/article-nav";
@use "components/audio";
@use "components/banner";
@use "components/comments";
@use "components/emoji";
@use "components/footnotes";
@use "components/giallo";
@use "components/icon";
@use "components/iframe";
@use "components/marquee";
@use "components/not-found";
@use "components/pre-container";
@use "components/search-modal";
@use "components/skip-to-content";
@use "components/statements";
@use "components/tag-chips";
@use "components/zola-anchor";
@use "components/zola-error";
// Mods
// @use "mods/compact-sidebar";
// @use "mods/flat";
// @use "mods/pill-tags";
// @use "mods/skeuomorphic"; // UNFINISHED, AND LIKELY WILL NEVER BE

9
scripts/audio-button.js Normal file
View file

@ -0,0 +1,9 @@
const audioButtons = document.querySelectorAll(".audio");
audioButtons.forEach((button) => {
button.addEventListener("click", (event) => playAudio(button.dataset.audio));
});
function playAudio(url) {
new Audio(url).play();
}

Some files were not shown because too many files have changed in this diff Show more