initial commit

This commit is contained in:
laura 2025-11-06 21:45:58 -03:00
commit e2466b202a
Signed by: w
GPG key ID: BCD2117C99E69817
50 changed files with 4356 additions and 0 deletions

22
components/Button.tsx Normal file
View file

@ -0,0 +1,22 @@
/**
* Copyright (c) 2025 favewa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { ComponentChildren } from "preact";
export interface ButtonProps {
id?: string;
onClick?: () => void;
children?: ComponentChildren;
disabled?: boolean;
}
export function Button(props: ButtonProps) {
return (
<button
{...props}
class="px-2 py-1 border-gray-500 border-2 rounded-sm bg-white hover:bg-gray-200 transition-colors"
/>
);
}

21
components/Empty.tsx Normal file
View file

@ -0,0 +1,21 @@
/**
* Copyright (c) 2025 favewa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export default function Empty({ path }: any) {
return (
<>
<a href="/"> go back</a>
<br /> <br />
<div style={{ color: "var(--theme-foreground-alt)" }}>
<span>
at@localhost<strong></strong>:<strong>~/reports</strong>$ ls
</span>
<p>
ls: cannot access <strong>'{path}'</strong>: No such file or directory
</p>
</div>
</>
);
}

36
components/Fm.tsx Normal file
View file

@ -0,0 +1,36 @@
/**
* Copyright (c) 2025 favewa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { tracks } from "../utils/fm.ts";
export function Fm() {
if (!tracks) return;
const content = tracks().slice(0, 4);
return (
<>
<section id="media">
<h2>Recently listened</h2>
<ul class="fm-recent-tracks">
{content.map((track) => (
<li class="fm-recent" key={track.artist + track.name}>
{track.cover && <img class="cover" src={track.cover} alt="" />}
<div class="meta">
<strong class="title">{track.name}</strong>
<span>{track.artist}</span>
{track.loved && <span class="loved"></span>}
</div>
</li>
))}
<a class="fm-more" href="https://last.fm/user/favewa">
Check out more on Last.fm
</a>
</ul>
</section>
</>
);
}

20
components/Header.tsx Normal file
View file

@ -0,0 +1,20 @@
/**
* Copyright (c) 2025 favewa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export function Header(props: any) {
return (
<header {...props}>
<h1 class="typecycle">
~
<span>favewa</span>
<span>lívia</span>
<span>laura</span>
</h1>
<h2>
she/her free software advocate linguistics enthusiast ˶ ˶ sა
</h2>
</header>
);
}

24
components/Links.tsx Normal file
View file

@ -0,0 +1,24 @@
/**
* Copyright (c) 2025 favewa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export default function Links(
props: { selected: "home" | "reports" | "misc" },
) {
return (
<nav>
<ul>
<li class={props.selected === "home" ? "selected" : ""}>
<a href="/">home</a>
</li>
<li class={props.selected === "reports" ? "selected" : ""}>
<a href="/reports">reports</a>
</li>
<li class={props.selected === "misc" ? "selected" : ""}>
<a href="/misc">misc</a>
</li>
</ul>
</nav>
);
}

View file

@ -0,0 +1,67 @@
/**
* Copyright (c) 2025 favewa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export interface ProjectCardProps {
name: string;
author: string;
url: string;
description: string;
languageColor?: string;
languageName?: string;
}
export default function ProjectCard({
name,
author,
url,
description,
languageColor,
languageName,
}: ProjectCardProps) {
return (
<a
href={url}
class="project-card"
target="_blank"
rel="noopener noreferrer"
>
<span class="external-icon" aria-label="Open externally">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<title>Open Externally</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6" />
<path d="M11 13l9 -9" />
<path d="M15 4h5v5" />
</svg>
</span>
<div class="author">
<strong>{author}</strong>/{name}
</div>
<p class="description">{description}</p>
<div class="info">
{languageColor && (
<span
class="language"
style={{ "--lang-color": languageColor } as any}
/>
)}
{languageName}
</div>
</a>
);
}

41
components/Reports.tsx Normal file
View file

@ -0,0 +1,41 @@
/**
* Copyright (c) 2025 favewa
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { Report } from "../utils/atproto.ts";
export interface ReportsProps {
reports: Report[];
}
export default function Reports({ reports }: ReportsProps) {
return (
<>
<header>
<h1>$reports</h1>
<h2>thoughts, ramblings, and occasional coherence</h2>
</header>
<ul class="reports">
{reports.map((report) => (
<li key={report.rkey}>
<a href={`/reports/${report.rkey}`} class="report">
<span class="date">{report.createdAt}</span>
<h3>{report.title}</h3>
<p class="excerpt">{report.excerpt}</p>
{report.tags && (
<div class="tags">
{report.tags.map((tag) => (
<span key={tag} class="tag">{tag}</span>
))}
</div>
)}
<span class="arrow"></span>
</a>
</li>
))}
</ul>
</>
);
}