23 Commits

Author SHA1 Message Date
5200952b19 first drone test commit
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2025-07-31 17:53:05 -07:00
45fce5fe76 update README and get rid of astro embed 2025-06-16 21:02:43 -07:00
ff14276559 Merge branch 'main' of https://git.coopcloud.tech/RTM/rtm-website 2025-06-13 17:21:34 -07:00
75baf0fdfd update computer icon and add commented out calendar page 2025-06-13 17:21:26 -07:00
05fc21daa2 add details for book club 2025-06-09 20:23:35 -07:00
ed1c796f49 make the computer/people icon numbers not hard coded 2025-05-22 21:01:57 -07:00
449f1abd4d 1. remove ../public from asset links
2. add book club page
2025-05-22 20:36:44 -07:00
e197ed4e28 deployment configs and instructions 2025-05-15 19:57:46 -07:00
4ec3c3eafa polishing! 2025-05-08 19:07:23 -07:00
895501032e Merge branch 'main' of https://git.coopcloud.tech/RTM/rtm-website 2025-05-01 20:51:03 -07:00
e52390db7c movin pictures around 2025-05-01 20:51:01 -07:00
be5bb755c0 format the points of unity 2025-05-01 20:49:06 -07:00
04d3b17d75 fix computer and people icons 2025-05-01 20:44:27 -07:00
8b535c9bae highlight the current page on the navbar 2025-05-01 20:25:42 -07:00
470a4cad86 add the sign up button 2025-05-01 19:59:21 -07:00
217f605041 navbar code cleanup 2025-05-01 19:55:42 -07:00
247d2c9bf2 h1 title styling 2025-05-01 19:55:04 -07:00
c0be749c02 uppercase page names 2025-05-01 19:44:25 -07:00
1cb415f829 Redirect to home page when clicking RTM 2025-05-01 19:26:46 -07:00
a69ef055ac adding tailwind docs to readme 2025-05-01 19:26:05 -07:00
60844a118f making UI work 2025-05-01 19:22:40 -07:00
e4a6dde7ff Merge branch 'main' of https://git.coopcloud.tech/RTM/rtm-website 2025-05-01 18:51:17 -07:00
e0ddafb005 add instruction to readme 2025-04-24 20:32:21 -07:00
86 changed files with 175 additions and 1415 deletions

View File

@ -1,24 +1,10 @@
---
kind: pipeline
name: default
type: docker
name: test
steps:
- name: build and publish docker image
image: plugins/docker
settings:
repo: git.coopcloud.tech/rtm/rtmwebsite
tags:
- main
- latest
- 0.0.8
platform: linux/amd64
username:
from_secret: USERNAME
password:
from_secret: PASSWORD
registry: git.coopcloud.tech
trigger:
branch:
- main
event:
- push
- name: greeting
image: alpine
commands:
- echo hello
- echo world

5
.gitignore vendored
View File

@ -24,7 +24,4 @@ pnpm-debug.log*
.idea/
# Emacs
*~
# Drone
.drone.secrets
*~

View File

@ -1,5 +1,3 @@
[![Build Status](https://build.coopcloud.tech/api/badges/RTM/rtm-website/status.svg)](https://build.coopcloud.tech/RTM/rtm-website)
# hello friends !
@ -48,8 +46,6 @@ npx astro dev
## Build in the docker image (make sure you have Docker installed!)
Set the version to the next [semantic version](https://semver.org/) after the version posted at [our co-op cloud's packages site](https://git.coopcloud.tech/RTM/-/packages) under "rtmwebsite":
``` bash
version=<specify-version>
docker build --platform linux/amd64 -t git.coopcloud.tech/rtm/rtmwebsite:$version .
@ -64,63 +60,10 @@ Check out [this documentation](https://docs.gitea.com/next/usage/packages/contai
docker push git.coopcloud.tech/rtm/rtmwebsite:$version
```
At [our co-op cloud's packages site](https://git.coopcloud.tech/RTM/-/packages), click on "rtmwebsite" and check that the version mentioned is the version you specified!
## Update recipe
We use a [private recipe](https://git.coopcloud.tech/RTM/rtm-astro-recipe) to deploy this website. This step needs Wireguard to be activated (download Wireguard and ask Sootie's owner to create a config for you and give you Docker permissions). The following examples will assume your name in Sootie's config is "blueberry"!
We use a [private recipe](https://git.coopcloud.tech/RTM/rtm-astro-recipe) to deploy this website, you will need to update the version in the compose.yml file and redoploy.
You will need to have wget (`brew install wget` on mac) and [abra](https://docs.coopcloud.tech/abra/) installed.
Create an SSH key to use with Sootie with the following command. Take note of the file where you save the key. The following examples will assume it is saved to `rtm` and that the `.ssh` directory is in your home directory (which you can find with the command `echo $HOME`).
```ssh-keygen -t ed25519```
Run the following commands to install the SSH key to Sootie as an authorized key:
```
ssh-copy-id -i $HOME/.ssh/rtm.pub blueberry@resisttechmonopolies.online
ssh -i $HOME/.ssh/rtm 'blueberry@resisttechmonopolies.online'
```
In the `$HOME/.ssh/config` file (which you may have to create if it does not exist), paste the following:
```
Host resisttechmonopolies.online
Hostname resisttechmonopolies.online
User blueberry
UseKeychain yes
IdentityFile ~/.ssh/rtm
```
You should now be able to SSH into Sootie with just the command `ssh resisttechmonopolies.online`
Run the following command (outside of the terminal in which you ran ssh in the previous step)
```abra server add resisttechmonopolies.online```
Clone the `sootie-config` repo into your `$HOME/.abra/servers/resisttechmonopolies.online` directory:
``` bash
git clone https://git.coopcloud.tech/RTM/sootie-config.git .
# DON'T FORGET THE . AT THE END OF THE COMMAND
```
Clone the `rtm-astro-recipe` repo into your `$HOME/.abra/recipes` directory:
```git clone https://git.coopcloud.tech/RTM/rtm-astro-recipe.git```
Update the version number to the latest in
``` bash
.abra/servers/resisttechmonopolies.online/resisttechmonopolies.online.env
```
Then
``` bash
abra app undeploy resisttechmonopolies.online
# wait 10 seconds

29
package-lock.json generated
View File

@ -26,7 +26,6 @@
"nanostores": "^0.11.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-pageflip": "^2.0.3",
"tailwind-merge": "^2.5.5",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
@ -5177,12 +5176,6 @@
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
},
"node_modules/page-flip": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/page-flip/-/page-flip-2.0.7.tgz",
"integrity": "sha512-96lQFUUz7r/LZzEUZJ3yBIMEKU9+m8HMFDzTvTdD6P7Ag/wXINjp9n0W7b4wanwnDbQETo4uNUoL3zMqpFxwGA==",
"license": "MIT"
},
"node_modules/parse-latin": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz",
@ -5676,15 +5669,6 @@
"react": "^19.0.0"
}
},
"node_modules/react-pageflip": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/react-pageflip/-/react-pageflip-2.0.3.tgz",
"integrity": "sha512-k81mHhRvUM52y8jyzTCh5t4O0lepkLhp+XGSUzq2C3uD+iW99Cv0jfRlqFCjZbD5N3jKkIFr7/3giucoXKDP3Q==",
"license": "MIT",
"dependencies": {
"page-flip": "latest"
}
},
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@ -10307,11 +10291,6 @@
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
},
"page-flip": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/page-flip/-/page-flip-2.0.7.tgz",
"integrity": "sha512-96lQFUUz7r/LZzEUZJ3yBIMEKU9+m8HMFDzTvTdD6P7Ag/wXINjp9n0W7b4wanwnDbQETo4uNUoL3zMqpFxwGA=="
},
"parse-latin": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz",
@ -10563,14 +10542,6 @@
"scheduler": "^0.25.0"
}
},
"react-pageflip": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/react-pageflip/-/react-pageflip-2.0.3.tgz",
"integrity": "sha512-k81mHhRvUM52y8jyzTCh5t4O0lepkLhp+XGSUzq2C3uD+iW99Cv0jfRlqFCjZbD5N3jKkIFr7/3giucoXKDP3Q==",
"requires": {
"page-flip": "latest"
}
},
"react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",

View File

@ -27,7 +27,6 @@
"nanostores": "^0.11.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-pageflip": "^2.0.3",
"tailwind-merge": "^2.5.5",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
@ -41,4 +40,4 @@
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.6.9"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 KiB

BIN
public/assets/book-club.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 968 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 935 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 970 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1007 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@ -1,24 +0,0 @@
---
export interface Props {
title: string;
bigTech: string;
alternatives: Array<{ name: string; description: string; url: string }>;
bgClass: string;
}
const { title, bigTech, alternatives, bgClass } = Astro.props;
---
<div class={`border rounded-lg p-6 shadow-md ${bgClass} text-gray-900`}>
<h2 class="text-2xl font-semibold mb-4">{title}</h2>
<p class="text-base mb-4"><span class="line-through">Big Tech Option: {bigTech}</span></p>
<ul class="space-y-4">
{alternatives.map(alt => (
<li>
<h3 class="text-lg font-bold underline">{alt.name}</h3>
<p>{alt.description}</p>
<a href={alt.url} class="text-blue-500 hover:text-blue-700">Website</a>
</li>
))}
</ul>
</div>

View File

@ -4,10 +4,10 @@ export default function Footer() {
return (
<div
className={cn(
"flex h-12 w-screen flex-col place-items-center justify-center bg-white dark:bg-black dark:text-white",
"flex h-12 w-screen flex-col place-items-center justify-center bg-emerald-50",
)}
>
<h2>Copyleft {new Date().getFullYear()}</h2>
<h2>Copyleft 2025</h2>
</div>
);
}

View File

@ -10,49 +10,45 @@ export default function Navbar({ activePage = "" }: NavbarProps) {
<div className={cn("flex flex-row justify-between pb-8 pl-4 pr-4 pt-8")}>
<div>
<strong className={cn("sm:text-2xl md:text-4xl")}>
<a
className={cn(
activePage === "Home" && "border-b-2 border-black-500",
)}
href="/">Resist Tech Monopolies</a>
<a
className={cn(
activePage === "Home" && "border-b-2 border-black-500",
)}
href="/">Resist Tech Monopolies</a>
</strong>
</div>
<strong className={cn("sm:text-1xl flex gap-10 md:text-4xl")}>
<a
href="/PointsOfUnity/"
className={cn(
"rounded-full px-6 py-2 transition-colors",
"bg-gray-200 text-green-700 hover:bg-gray-300",
"dark:bg-gray-800 dark:text-green-400 dark:hover:bg-gray-700",
activePage === "PointsOfUnity" && "ring-2 ring-green-500 ring-offset-2",
"text-green-500",
activePage === "PointsOfUnity" && "border-b-2 border-green-500",
)}
>
Points of Unity
</a>
<a
href="/projects/"
href="/WhatWereWorkingOn/"
className={cn(
"rounded-full px-6 py-2 transition-colors",
"bg-gray-200 text-red-700 hover:bg-gray-300",
"dark:bg-gray-800 dark:text-red-400 dark:hover:bg-gray-700",
activePage === "OurProjects" && "ring-2 ring-red-500 ring-offset-2",
"text-red-500",
activePage === "WhatWereWorkingOn" && "border-b-2 border-red-500",
)}
>
Our Projects
What We're Working On
</a>
</strong>
</div>
<div className={cn("flex flex-row justify-between pb-8")}>
<div className="w-full grid grid-flow-col">
{Array.from({ length: 12 }).map((_, index) => (
<div className="w-full grid grid-flow-col auto-cols-[76px] gap-8">
{Array.from({ length: 20 }).map((_, index) => (
<div key={index} className="flex justify-center">
<img
src={index % 2 === 0 ? "/assets/computer.png" : "/assets/people.png"}
alt={index % 2 === 0 ? "computer icon" : "people icon"}
width="30"
height="30"
<img
src={index % 2 === 0 ? "/assets/computer.png" : "/assets/people.png"}
alt={index % 2 === 0 ? "computer icon" : "people icon"}
width="30"
height="30"
/>
</div>
))}

View File

@ -1,40 +0,0 @@
import { useEffect, useState } from "react";
import { Check, Share2 } from "lucide-react";
import { Button } from "../ui/button";
interface ShareZineButtonProps {
zineId: string;
}
export function ShareZineButton({ zineId }: ShareZineButtonProps) {
const [copied, setCopied] = useState(false);
useEffect(() => {
if (!copied) return;
const timer = setTimeout(() => setCopied(false), 1500);
return () => clearTimeout(timer);
}, [copied]);
const handleClick = async (e: React.MouseEvent) => {
e.stopPropagation();
const url = `${window.location.origin}/projects/zines/${zineId}`;
await navigator.clipboard.writeText(url);
setCopied(true);
};
return (
<Button variant="outline" size="sm" onClick={handleClick}>
{copied ? (
<>
<Check />
Copied!
</>
) : (
<>
<Share2 />
Share
</>
)}
</Button>
);
}

View File

@ -1,54 +0,0 @@
import { Download } from "lucide-react";
import type { Zine } from "../../data/zines";
import { Button } from "../ui/button";
import { ShareZineButton } from "./ShareZineButton";
interface ZineCardProps {
zine: Zine;
onClick: () => void;
}
export function ZineCard({ zine, onClick }: ZineCardProps) {
const handleDownload = (e: React.MouseEvent) => {
e.stopPropagation();
};
return (
<div className="flex flex-col items-center gap-2 rounded-lg border p-4">
<button
onClick={onClick}
className="group flex flex-col items-center gap-2 transition-all focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
>
<div className="aspect-[3/4] w-48 overflow-hidden rounded bg-muted">
<img
src={zine.coverImage}
alt={`Cover of ${zine.title}`}
className="h-full w-full object-cover transition-transform group-hover:scale-105"
/>
</div>
<h3 className="text-lg font-medium">{zine.title}</h3>
{zine.description && (
<p className="text-sm text-muted-foreground">{zine.description}</p>
)}
</button>
<div className="flex gap-2 mt-2">
<Button variant="outline" size="sm" onClick={onClick}>
Read Online
</Button>
<Button
variant="outline"
size="sm"
asChild
onClick={handleDownload}
>
<a href={zine.printablePdf} download>
<Download className="h-4 w-4 mr-1" />
Printable
</a>
</Button>
<ShareZineButton zineId={zine.id} />
</div>
</div>
);
}

View File

@ -1,55 +0,0 @@
import { useState } from "react";
import { zines, type Zine } from "../../data/zines";
import { ZineCard } from "./ZineCard";
import { ZineViewer } from "./ZineViewer";
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
} from "../ui/dialog";
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
export function ZineGrid() {
const [selectedZine, setSelectedZine] = useState<Zine | null>(null);
const handleOpenZine = (zine: Zine) => {
setSelectedZine(zine);
};
const handleCloseZine = () => {
setSelectedZine(null);
};
return (
<>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{zines.map((zine) => (
<ZineCard
key={zine.id}
zine={zine}
onClick={() => handleOpenZine(zine)}
/>
))}
</div>
<Dialog open={selectedZine !== null} onOpenChange={handleCloseZine}>
<DialogContent className="max-w-4xl">
<VisuallyHidden.Root>
<DialogTitle>{selectedZine?.title}</DialogTitle>
<DialogDescription>
Interactive flipbook viewer for {selectedZine?.title}
</DialogDescription>
</VisuallyHidden.Root>
{selectedZine && (
<ZineViewer
pages={selectedZine.pages}
title={selectedZine.title}
zineId={selectedZine.id}
/>
)}
</DialogContent>
</Dialog>
</>
);
}

View File

@ -1,248 +0,0 @@
import React, { useRef, useState, useCallback, forwardRef, useEffect } from "react";
import HTMLFlipBook from "react-pageflip";
import { ChevronLeft, ChevronRight, ZoomIn, ZoomOut, Minimize2 } from "lucide-react";
import { Button } from "../ui/button";
import { ShareZineButton } from "./ShareZineButton";
interface PageProps {
src: string;
pageNumber: number;
}
const Page = forwardRef<HTMLDivElement, PageProps>(({ src, pageNumber }, ref) => {
return (
<div ref={ref} className="page bg-white">
<img
src={src}
alt={`Page ${pageNumber}`}
className="h-full w-full object-contain"
/>
</div>
);
});
Page.displayName = "Page";
interface ZineViewerProps {
pages: string[];
title: string;
zineId?: string;
}
const MIN_ZOOM = 1;
const MAX_ZOOM = 2.5;
const ZOOM_STEP = 0.5;
export function ZineViewer({ pages, title, zineId }: ZineViewerProps) {
const flipBookRef = useRef<any>(null);
const [currentPage, setCurrentPage] = useState(0);
const totalPages = pages.length;
const [zoom, setZoom] = useState(1);
const [pan, setPan] = useState({ x: 0, y: 0 });
const [isPanning, setIsPanning] = useState(false);
const panStart = useRef({ x: 0, y: 0 });
const panOffset = useRef({ x: 0, y: 0 });
const isZoomed = zoom > MIN_ZOOM;
const onFlip = useCallback((e: any) => {
setCurrentPage(e.data);
}, []);
const goToPrevPage = () => {
flipBookRef.current?.pageFlip()?.flipPrev();
};
const goToNextPage = () => {
flipBookRef.current?.pageFlip()?.flipNext();
};
const handleZoomIn = () => {
setZoom((z) => Math.min(z + ZOOM_STEP, MAX_ZOOM));
};
const handleZoomOut = () => {
setZoom((z) => {
const next = Math.max(z - ZOOM_STEP, MIN_ZOOM);
if (next <= MIN_ZOOM) setPan({ x: 0, y: 0 });
return next;
});
};
const handleZoomReset = () => {
setZoom(MIN_ZOOM);
setPan({ x: 0, y: 0 });
};
const handlePointerDown = (e: React.PointerEvent) => {
if (!isZoomed) return;
setIsPanning(true);
panStart.current = { x: e.clientX, y: e.clientY };
panOffset.current = { x: pan.x, y: pan.y };
(e.target as HTMLElement).setPointerCapture?.(e.pointerId);
e.preventDefault();
};
const handlePointerMove = (e: React.PointerEvent) => {
if (!isPanning) return;
const dx = (e.clientX - panStart.current.x) / zoom;
const dy = (e.clientY - panStart.current.y) / zoom;
setPan({
x: panOffset.current.x + dx,
y: panOffset.current.y + dy,
});
};
const handlePointerUp = () => {
setIsPanning(false);
};
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "ArrowLeft") {
goToPrevPage();
} else if (e.key === "ArrowRight") {
goToNextPage();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, []);
if (pages.length === 0) {
return (
<div className="flex h-64 items-center justify-center text-muted-foreground">
No pages available
</div>
);
}
return (
<div className="flex flex-col items-center gap-4">
<h2 className="text-xl font-semibold">{title}</h2>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={handleZoomOut}
disabled={zoom <= MIN_ZOOM}
aria-label="Zoom out"
>
<ZoomOut className="h-4 w-4" />
</Button>
<span className="text-sm text-muted-foreground w-12 text-center">
{Math.round(zoom * 100)}%
</span>
<Button
variant="outline"
size="icon"
onClick={handleZoomIn}
disabled={zoom >= MAX_ZOOM}
aria-label="Zoom in"
>
<ZoomIn className="h-4 w-4" />
</Button>
{isZoomed && (
<Button
variant="outline"
size="icon"
onClick={handleZoomReset}
aria-label="Reset zoom"
>
<Minimize2 className="h-4 w-4" />
</Button>
)}
{zineId && <ShareZineButton zineId={zineId} />}
</div>
<div
className="flipbook-container"
style={{
overflow: "hidden",
cursor: isZoomed ? (isPanning ? "grabbing" : "grab") : "default",
}}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
onPointerCancel={handlePointerUp}
>
<div
style={{
transform: `scale(${zoom}) translate(${pan.x}px, ${pan.y}px)`,
transformOrigin: "center center",
transition: isPanning ? "none" : "transform 0.2s ease",
pointerEvents: isZoomed ? "none" : "auto",
}}
>
{/* @ts-ignore - react-pageflip types are incomplete */}
<HTMLFlipBook
ref={flipBookRef}
width={350}
height={500}
size="stretch"
minWidth={280}
maxWidth={500}
minHeight={400}
maxHeight={700}
maxShadowOpacity={0.5}
showCover={true}
mobileScrollSupport={!isZoomed}
onFlip={onFlip}
className="flipbook"
style={{}}
startPage={0}
drawShadow={true}
flippingTime={600}
usePortrait={true}
startZIndex={0}
autoSize={true}
clickEventForward={true}
useMouseEvents={!isZoomed}
swipeDistance={30}
showPageCorners={!isZoomed}
disableFlipByClick={false}
>
{pages.map((page, index) => (
<Page key={index} src={page} pageNumber={index + 1} />
))}
</HTMLFlipBook>
</div>
</div>
<div className="flex items-center gap-4">
<Button
variant="outline"
size="icon"
onClick={goToPrevPage}
disabled={currentPage === 0}
aria-label="Previous page"
>
<ChevronLeft className="h-4 w-4" />
</Button>
<span className="text-sm text-muted-foreground">
Page {currentPage + 1} of {totalPages}
</span>
<Button
variant="outline"
size="icon"
onClick={goToNextPage}
disabled={currentPage >= totalPages - 1}
aria-label="Next page"
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
<p className="text-xs text-muted-foreground">
{isZoomed
? "Drag to pan. Use arrow buttons to turn pages"
: "Click the page corners or use arrow buttons to turn pages"}
</p>
</div>
);
}

View File

@ -1,113 +0,0 @@
export interface Zine {
id: string;
title: string;
description: string;
coverImage: string;
pages: string[];
printablePdf: string;
}
export const zines: Zine[] = [
{
id: "internet-for-the-people",
title: "Internet For the People",
description: "",
coverImage: "/assets/zines/internet-for-the-people/cover.jpg",
pages: [
"/assets/zines/internet-for-the-people/page-1.jpg",
"/assets/zines/internet-for-the-people/page-2.jpg",
"/assets/zines/internet-for-the-people/page-3.jpg",
"/assets/zines/internet-for-the-people/page-4.jpg",
"/assets/zines/internet-for-the-people/page-5.jpg",
"/assets/zines/internet-for-the-people/page-6.jpg",
"/assets/zines/internet-for-the-people/page-7.jpg",
"/assets/zines/internet-for-the-people/page-8.jpg",
],
printablePdf: "/assets/zines/internet-for-the-people/printable.pdf",
},
{
id: "de-monopoly-discotech-101",
title: "De-Monopoly DiscoTech 101",
description: "",
coverImage: "/assets/zines/de-monopoly-discotech-101/cover.jpg",
pages: [
"/assets/zines/de-monopoly-discotech-101/page-1.jpg",
"/assets/zines/de-monopoly-discotech-101/page-2.jpg",
"/assets/zines/de-monopoly-discotech-101/page-3.jpg",
"/assets/zines/de-monopoly-discotech-101/page-4.jpg",
"/assets/zines/de-monopoly-discotech-101/page-5.jpg",
"/assets/zines/de-monopoly-discotech-101/page-6.jpg",
"/assets/zines/de-monopoly-discotech-101/page-7.jpg",
"/assets/zines/de-monopoly-discotech-101/page-8.jpg",
],
printablePdf: "/assets/zines/de-monopoly-discotech-101/printable.pdf",
},
{
id: "de-monopoly-discotech-101-japanese",
title: "De-Monopoly DiscoTech 101 (Japanese)",
description: "",
coverImage: "/assets/zines/de-monopoly-discotech-101-japanese/cover.jpg",
pages: [
"/assets/zines/de-monopoly-discotech-101-japanese/page-1.jpg",
"/assets/zines/de-monopoly-discotech-101-japanese/page-2.jpg",
"/assets/zines/de-monopoly-discotech-101-japanese/page-3.jpg",
"/assets/zines/de-monopoly-discotech-101-japanese/page-4.jpg",
"/assets/zines/de-monopoly-discotech-101-japanese/page-5.jpg",
"/assets/zines/de-monopoly-discotech-101-japanese/page-6.jpg",
"/assets/zines/de-monopoly-discotech-101-japanese/page-7.jpg",
"/assets/zines/de-monopoly-discotech-101-japanese/page-8.jpg",
],
printablePdf: "/assets/zines/de-monopoly-discotech-101-japanese/printable.pdf",
},
{
id: "brief-notes-from-a-de-monopoly-discotech",
title: "Brief Notes from a De-Monopoly DiscoTech",
description: "",
coverImage: "/assets/zines/brief-notes-from-a-de-monopoly-discotech/cover.jpg",
pages: [
"/assets/zines/brief-notes-from-a-de-monopoly-discotech/cover.jpg",
"/assets/zines/brief-notes-from-a-de-monopoly-discotech/page-1.jpg",
"/assets/zines/brief-notes-from-a-de-monopoly-discotech/page-2.jpg",
"/assets/zines/brief-notes-from-a-de-monopoly-discotech/page-3.jpg",
"/assets/zines/brief-notes-from-a-de-monopoly-discotech/page-4.jpg",
"/assets/zines/brief-notes-from-a-de-monopoly-discotech/page-5.jpg",
"/assets/zines/brief-notes-from-a-de-monopoly-discotech/page-6.jpg",
"/assets/zines/brief-notes-from-a-de-monopoly-discotech/page-7.jpg",
],
printablePdf: "/assets/zines/brief-notes-from-a-de-monopoly-discotech/printable.pdf",
},
{
id: "signal-101",
title: "Signal 101",
description: "",
coverImage: "/assets/zines/signal-101/cover.jpg",
pages: [
"/assets/zines/signal-101/cover.jpg",
"/assets/zines/signal-101/page-1.jpg",
"/assets/zines/signal-101/page-2.jpg",
"/assets/zines/signal-101/page-3.jpg",
"/assets/zines/signal-101/page-4.jpg",
"/assets/zines/signal-101/page-5.jpg",
"/assets/zines/signal-101/page-6.jpg",
"/assets/zines/signal-101/page-7.jpg",
],
printablePdf: "/assets/zines/signal-101/printable.pdf",
},
{
id: "signal-201",
title: "Signal 201",
description: "Advanced usage of signal",
coverImage: "/assets/zines/signal-201/cover.jpg",
pages: [
"/assets/zines/signal-201/cover.jpg",
"/assets/zines/signal-201/page-1.jpg",
"/assets/zines/signal-201/page-2.jpg",
"/assets/zines/signal-201/page-3.jpg",
"/assets/zines/signal-201/page-4.jpg",
"/assets/zines/signal-201/page-5.jpg",
"/assets/zines/signal-201/page-6.jpg",
"/assets/zines/signal-201/page-7.jpg",
],
printablePdf: "/assets/zines/signal-201/printable.pdf",
},
];

View File

@ -13,66 +13,39 @@ import "../styles/globals.css";
<h1 class="text-3xl font-semibold">Points of Unity</h1>
<p class="mb-4">
Our community agreements and decision-making process describe <em>how</em> we work together. The points of unity are the broader philosophies that we're working towards as a group. Perhaps people aren't 100% aligned individually, but this is what the group as a whole believes in when showing up to the work.
</p>
<h2 class="text-2xl font-semibold mt-10 mb-2">Grounding</h2>
<p class="mb-4">
We situate ourselves in our context - as Seattle residents and as users and creators of the technologies we aim to resist. We are seeing a long-spanning rise of fascism within national governments here and abroad. In parallel is the increasing power of tech companies over our lives through surveillance and the provisioning of everyday needs, from employment to how we get our internet utilities in the first place. There are rising income disparities across the nation, but also more specifically across everyone who can be considered a "tech worker": from a CEO, to a software engineer to a tech campus cafeteria worker to a child enslaved to mine cobalt in the Congo.
We situate ourselves in our context - as Seattle residents and as
users and creators of the technologies we aim to resist. We are seeing
a long-spanning rise of fascism within all levels of governance, from
Seattle city council to national governments here and abroad.
</p>
<p class="mb-4">
We know that technology intersects with and amplifies systems of oppression, from anti-Black racism to classism, ableism, cisheteropatriarchy, etc. Big tech permeates all aspects of life in Seattle, but to name a few ways:
</p>
<ul class="list-disc pl-8 mb-4 space-y-2">
<li>The rise of a <a href="https://stopsurveillancecity.wordpress.com/" class="font-bold underline hover:text-gray-600">Surveillance City</a> in Seattle facilitates the tracking of out-of-state people seeking abortions, the kidnapping of migrants, and the increased surveillance and criminalization of sex workers, the visibly unhoused, and poor and racialized communities.</li>
<li>The <strong>environmental racism</strong> and community impacts of <strong>data centers</strong>, both from the AI bubble and the demand for more data processing that comes with surveillance capitalism.</li>
<li><strong>The AI genocide</strong> in Palestine abetted by Google, Amazon, Boeing and Microsoft, all local Seattle employers.</li>
<li>Rising <strong>gentrification</strong> and <strong>income inequality</strong> that comes from financialization of real estate and the gentrifying waves of tech workers moving into the city.</li>
</ul>
<p class="mb-4">
Some of us are employed by Big Tech companies, and we are all living on stolen Coast Salish land covered by the broken Treaty of Point Elliot of 1855; we understand that we are contributing - directly or indirectly - to these oppressions, and seek to use our skills towards more liberatory ends.
</p>
<h2 class="text-2xl font-semibold mt-10 mb-2">Theory of Change</h2>
<p class="mb-4">
We are committed to fostering our collective understanding of resisting tech monopolies and building a constellation of alternative technologies that exist outside of exploitative, oppressive systems. We seek out radical possibilities by building connections across our communities with others struggling to resist tech monopolies and reject work that expands the reach of these monopolies.
In parallel is the increasing power of tech companies over our lives
through surveillance and the provisioning of everyday needs, from
employment to how we get our internet utilities in the first place.
There are rising income disparities across the nation, but also more
specifically across everyone who can be considered a "tech worker":
from a CEO to a software engineer to a contractor to a child slave
mining cobalt in the Congo.
</p>
<p class="mb-4">
We don't need to passively wait around for somebody to come implement these solutions. Making improvements to our lives and our communities paves the way for collective liberation. Dismantling smaller elements of a system is practice for uprooting the whole and replacing it with a constellation of liberatory alternatives.
</p>
<h2 class="text-2xl font-semibold mt-10 mb-2">Role of Technology</h2>
<p class="mb-4">
"We believe our communities and organizing efforts can and should harness the possibilities of new technology." (<a href="https://mijente.net/our-dna/" class="underline hover:text-gray-600">Mijente</a>) However, we don't innovate for the sake of innovation, but aim to create with intention, listen to the perspectives of those we hope to serve (including ourselves), and hold each other accountable in this work.
</p>
<h2 class="text-2xl font-semibold mt-10 mb-2">Our Position in the Movement</h2>
<p class="mb-4">
We don't see ourselves as a vanguard organization - that is, we make no claims to being the sole authority on resisting tech monopolies and we will never tell a front-line community or group of tech users what they actually need. Everyone benefits from the work of resisting tech monopolies, and for that reason we do the work for ourselves as much as we do it for anybody we are in solidarity with.
Technology contributes to gentrification both through the tech-enabled
financialization of real estate as well as the gentrifying waves of
tech workers moving into cities. Increased technical sophisication of
militaries and carceral systems have created the first "AI genocide"
in Palestine; increased militarization of borders such as the
US-Mexico and the surveillance of migrants; increased police brutality
worldwide, all disparately impacting people of the global majority.
</p>
<p class="mb-4">
However we also recognize our own privilege in being highly-skilled tech workers: because of this we center the voices of the people most vulnerable to various tech monopolies, and aim to share our expertise so that others can also become agents in co-creating technologies of liberation and resistance.
</p>
<h2 class="text-2xl font-semibold mt-10 mb-2">Friendly Vibes</h2>
<p class="mb-4">
Holding all of the above, we're also a group of friends here for the scrappy, hacky vibes. We don't take our work too seriously and we're here to work joyfully. This is first and foremost a community.
</p>
<h2 class="text-2xl font-semibold mt-10 mb-2">Acknowledgments</h2>
<p class="mb-4">
These points of unity were inspired by the Prison Library Solidarity Network, the Coalition of Anti-Racist Whites, Ruha Benjamin's work on Abolitionist Tech, and Mijente.
All of these crises are enabled and worsened by technology companies.
As tech workers of various kinds living in the imperial core, on
stolen Coast Salish land covered by the broken Treaty of Point Elliot
of 1855, we understand how our labor is contributing - directly or
indirectly - to these oppressions, and seek to use our skills towards
more liberatory ends.
</p>
</div>
</div>

View File

@ -0,0 +1,71 @@
---
import Navbar from "../components/Navbar";
import Footer from "../components/Footer";
import Layout from "../layouts/Layout.astro";
import "../styles/globals.css";
---
<Layout>
<main class="flex min-h-screen flex-col justify-between">
<div>
<Navbar client:load activePage="WhatWereWorkingOn" />
<div class="pl-4 pr-4">
<h1 class="pb-4 pt-4 text-3xl font-semibold">What We're Working On</h1>
<div class="flex flex-row gap-8">
<!-- <div class="justify-items-center">
<img
src="/assets/calendar.png"
alt="stack of books"
width="250"
class="border pb-4"
/>
<a
href="/WhatWereWorkingOnPages/Calendar/"
class="underline decoration-solid">Calendar</a
>
</div> -->
<div class="justify-items-center">
<img
src="/assets/pihole.jpeg"
alt="pihole parts"
width="250"
class="border pb-4"
/>
<a
href="/WhatWereWorkingOnPages/Pihole/"
class="underline decoration-solid">Pihole Workshop</a
>
</div>
<div class="justify-items-center">
<img
src="/assets/this-website.png"
alt="screenshot of this website including screenshot of this website including screenshot of this website..."
width="250"
class="border pb-4"
/>
<a href="/" class="underline decoration-solid">This website!</a>
</div>
<div class="justify-items-center">
<img
src="/assets/book-club.png"
alt="stack of books"
width="250"
class="border pb-4"
/>
<a
href="/WhatWereWorkingOnPages/BookClub/"
class="underline decoration-solid">Book Club</a
>
</div>
</div>
</div>
</div>
<Footer />
</main>
</Layout>

View File

@ -0,0 +1,48 @@
---
import Navbar from "../../components/Navbar";
import Footer from "../../components/Footer";
import Layout from "../../layouts/Layout.astro";
import "../../styles/globals.css";
---
<Layout>
<main class="flex min-h-screen flex-col justify-between">
<div>
<Navbar client:load activePage="WhatWereWorkingOn" />
<div class="pl-4 pr-4">
<h1 class="pb-4 pt-4 text-3xl font-semibold">Book Club</h1>
<p>Join our book club where we read and discuss books about technology, power, and resistance.</p>
<div class="flex flex-col gap-8">
<div class="border p-6 rounded-lg">
<h2 class="text-lg font-semibold mb-4 underline">Current Book</h2>
<div class="flex flex-col gap-4">
<div class="flex items-center gap-4">
<p class="italic font-semibold">Common Circuits</p>
<p class="text-sm text-gray-500">by Luis Felipe R. Murillo</p>
</div>
<p class="text-purple-600">Next meeting: 6/27</p>
<p class="text-sm">Reading for next meeting: Introduction + Chapter 1 + Chapter 2</p>
</div>
</div>
<div class="border p-6 rounded-lg">
<h2 class="text-2xl font-semibold mb-4">Past Books</h2>
<div class="flex flex-col gap-4">
<ul class="list-disc pl-6">
<li>Internet for the People</li>
</ul>
</div>
</div>
<div class="border p-6 rounded-lg">
<a href="/" class="text-blue-500 hover:text-blue-700">
<h2 class="text-2xl font-semibold mb-4">Join Us</h2>
</a>
</div>
</div>
</div>
</div>
<Footer />
</main>
</Layout>

View File

@ -11,7 +11,7 @@ import "../../styles/globals.css";
<Navbar />
<div class="pl-4 pr-4">
<h1 class="text-3xl font-semibold">Pihole</h1>
</div>
</div>
<Footer />

View File

@ -1,93 +0,0 @@
---
import Navbar from "../components/Navbar";
import Footer from "../components/Footer";
import Layout from "../layouts/Layout.astro";
import "../styles/globals.css";
---
<Layout>
<main class="flex min-h-screen flex-col justify-between">
<div>
<Navbar client:load activePage="OurProjects" />
<div class="pl-4 pr-4">
<h1 class="pb-4 pt-4 text-3xl font-semibold">Our Projects</h1>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
<!-- <div class="justify-items-center">
<img
src="/assets/calendar.png"
alt="stack of books"
width="250"
class="border pb-4"
/>
<a
href="/projects/calendar/"
class="underline decoration-solid">Calendar</a
>
</div> -->
<div class="justify-items-center">
<a href="/projects/bookclub/">
<img
src="/assets/book-club.jpg"
alt="stack of books"
width="280"
class="border"
/>
<p class="underline decoration-solid">Book Club</p>
</a>
</div>
<div class="justify-items-center">
<a href="/projects/alternatives/">
<img
src="/assets/computer.png"
alt="computer"
width="250"
class="border"
/>
<p class="underline decoration-solid">Big Tech Alternatives</p>
</a>
</div>
<div class="justify-items-center">
<a href="/projects/zines/">
<img
src="/assets/zines-thumbnail.png"
alt="zines"
width="250"
class="border object-cover w-[250px] h-[250px]"
/>
<p class="underline decoration-solid">Zines</p>
</a>
</div>
<div class="justify-items-center">
<a href="/projects/PetCalendar/">
<img
src="/assets/pet-calendar-2026-cover.jpg"
alt="photo collage of cats and dogs"
width="500"
class="border"
/>
<p class="underline decoration-solid">Pet Calendar</p>
</a>
</div>
<div class="justify-items-center">
<a href="/projects/pihole/">
<img
src="/assets/pihole.jpeg"
alt="pihole parts"
width="250"
class="border"
/>
<p class="underline decoration-solid">Pihole Workshop</p>
</a>
</div>
</div>
</div>
</div>
<Footer />
</main>
</Layout>

View File

@ -1,343 +0,0 @@
---
import Navbar from "../../components/Navbar";
import Footer from "../../components/Footer";
import Layout from "../../layouts/Layout.astro";
import "../../styles/globals.css";
---
<Layout>
<main class="flex min-h-screen flex-col justify-between">
<div>
<Navbar client:load activePage="WhatWereWorkingOn" />
<div class="pl-4 pr-4">
<h1 class="pb-4 pt-4 text-3xl font-semibold">Pet Calendar</h1>
<div>We created a pet calendar with 12 beautiful pictures of RTM's
animal friends.
<p class="pb-2">Each month, our pets will share with you with a different way you can
Resist Big Tech Monopolies!</p>
<p class="pb-2"> If you'd like to request a calendar, email us at besties@resisttechmonopolies.online</p>
<div class="justify-items-center">
<img
src="/assets/pet-calendar-2026-cover.jpg"
alt="pihole parts"
width="500"
class="border"
/>
</div>
<div>
</div>
</div>
<h1 class="pb-4 pt-4 text-2xl font-semibold">2026 Monthly RTM Tutorials</h1>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="January">January</h2>
<div>
<p class="mb-2"> Try out Mastadon for algorithmless social media that won't sell your data!</p>
<a
class="underline mb-2"
href="https://joinmastodon.org/"
>Join Mastadon!
</a >
<p class="mb-2"> Difficulty rating: 🌶️/5
</p>
</div>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="February">February</h2>
<div>
<p class="mb-2"> Learn about hosting your own instances of tech services to protect your data!</p>
<a
class="underline mb-2"
href="https://www.xda-developers.com/tips-for-self-hosting-beginners/"
>Beginner's Guide to Self-Hosting!
</a >
<p class="mb-2"> Difficulty rating: 🌶️🌶️🌶️🌶️/5
</p>
</div>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="March">March</h2>
<div>
<p class="mb-2"> Try out Calibre to read Amazon-free e-books!</p>
<a
class="underline mb-2"
href="https://tutorials.mediaket.net/software-tutorials/calibre-how-to-use-it.html"
>Get Started with Calibre!
</a >
<p class="mb-2"> Difficulty rating: 🌶️🌶️/5
</p>
</div>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="April">April</h2>
<div>
<p class="mb-2"> Thinking of switching off Windows or MacOS? Try Linux
for a super customizable, privacy-first operating system!
</p>
<a
class="underline mb-2"
href="https://www.geeksforgeeks.org/linux-unix/30-days-of-linux/"
>30 Days of Linux for Beginners!
</a >
<p class="mb-2"> Difficulty rating: 🌶️🌶️🌶️🌶️/5
</p>
</div>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="May">May</h2>
<div>
<p class="mb-2"> Try going outside and repping some protest fashion this Spring!</p>
<a
class="underline mb-2"
href="https://graziamagazine.com/me/articles/history-keffiyeh-palestine/"
>Learn the history and meaning of the keffiyeh!
</a >
<p class="mb-2"> Difficulty rating: 🌶️/5
</p>
</div>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="June">June</h2>
<div>
<p class="mb-2"> Try out Ente for a privacy-friendly, open-source photos hosting platform!</p>
<a
class="underline mb-2"
href="https://web.archive.org/web/20260108185516/https://www.androidauthority.com/ente-photos-hands-on-3542198/"
>In-depth overeview of Ente Photos migration!
</a >
<p class="mb-2"> Difficulty rating: 🌶️/5
</p>
</div>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="July">July</h2>
<div>
<p class="mb-2"> Try installing your own Pi Hole on your home wifi network to block ad and malware servers!</p>
<a
class="underline mb-2"
href="https://protasm.com/blogs/news/an-extensive-tutorial-on-how-to-setup-a-pi-hole"
>Extensive PiHole Tutorial!
</a >
<p class="mb-2"> Difficulty rating: 🌶️🌶️🌶️🌶️🌶️/5
</p>
</div>
<hr class="mb-4">
<p class="mb-2"> Alternative: Install an adblocker!</p>
<a
class="underline mb-2"
href="https://adblockplus.org/"
>Install Adblock Plus!
</a >
<p class="mb-2"> Difficulty rating: 🌶️/5
</p>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="August">August</h2>
<div>
<p class="mb-2"> Try out DuckDuckGo as a privacy-friendly alternative to Google search and Chrome!</p>
<a
class="underline mb-2"
href="https://duckduckgo.com/"
>Download Duck Duck Go!
</a >
<p class="mb-2"> Difficulty rating: 🌶️/5
</p>
</div>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="September">September</h2>
<div>
<p class="mb-2"> Instead of relying on surveillance for safety, try making friends with your neighbors!</p>
<a
class="underline mb-2"
href="https://www.youtube.com/watch?v=1-Xl6dhDV3Q"
> How to Build Radical Community!
</a >
<p class="mb-2"> Difficulty rating: 🌶️🌶️🌶️/5
</p>
(hey making friends in Seattle is hard, okay?)
</div>
<hr class="mb-4">
<p class="mb-2"> Alternative: Neighbor Unions (buliding community power!)</p>
<a
class="underline mb-2"
href="https://social-ecology.org/wp/neighbor-union-cohort/"
>Learn about Neighbor Unions!
</a >
<p class="mb-2"> Difficulty rating: 🌶️🌶️🌶️/5
</p>
</div>
</div>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="October">October</h2>
<div>
<p class="mb-2"> Try out JellyFin to host your own media server
and share with friends. You can break up with Netflix, Hulu, Disney+, and more!
</p>
<a
class="underline mb-2"
href="https://jellyfin.org/docs/general/quick-start/"
>Quickstart for JellyFin!
</a >
<p class="mb-2"> Difficulty rating: 🌶️🌶️🌶️🌶️/5
</p>
</div>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="November">November</h2>
<div>
<p class="mb-2"> Try out Meet.coop as an open-source, privacy-friendly, and renewable energy-powered alternative to Zoom and Hangouts! </p>
<a
class="underline mb-2"
href="https://www.meet.coop/"
>Make a Meet.coop account!
</a >
<p class="mb-2"> Difficulty rating: 🌶️🌶️/5
</p>
</div>
<hr class="mb-4">
<p class="mb-2"> Free alternative: Jitsi</p>
<a
class="underline mb-2"
href="https://meet.jit.si/"
>Start a Meeting with Jitsi!
</a >
<p class="mb-2"> Difficulty rating: 🌶️/5
</p>
</div>
</div>
</div>
</div>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold" id="December">December</h2>
<div>
<p class="mb-2"> Try out Radicale as a privacy-friendly, open-source alternative to Google Calendar and Outlook!</p>
<a
class="underline mb-2"
href="https://radicale.org/master.html#simple-5-minute-setup"
>Setting up Radicale Tutorial!
</a >
<p class="mb-2"> Difficulty rating: 🌶️🌶️🌶️🌶️🌶️/5
</p>
</div>
<hr class="mb-4">
<p class="mb-2"> Alternative: Proton Calendar</p>
<a
class="underline mb-2"
href="https://calendar.proton.me/"
>Switch to Proton Calendar!
</a >
<p class="mb-2"> Difficulty rating: 🌶️/5
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<Footer />
</main>
</Layout>

View File

@ -1,99 +0,0 @@
---
import Navbar from "../../components/Navbar";
import Footer from "../../components/Footer";
import Layout from "../../layouts/Layout.astro";
import "../../styles/globals.css";
import Card from "../../components/Card.astro";
---
<Layout>
<main class="flex min-h-screen flex-col justify-between">
<div>
<Navbar client:load activePage="OurProjects" />
<div class="pl-4 pr-4">
<h1 class="pb-4 pt-4 text-3xl font-semibold">Big Tech Alternatives</h1>
<p class="mb-4">Explore open-source and privacy-focused alternatives to popular big tech software.</p>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<Card
title="Browsers"
bigTech="Chrome (Google)"
alternatives={[
{ name: 'Firefox', description: 'Privacy-focused browser.', url: 'https://www.mozilla.org/firefox' },
]}
bgClass="bg-blue-50"
/>
<Card
title="Cloud Storage"
bigTech="Google Drive"
alternatives={[
{ name: 'Nextcloud', description: 'Self-hosted file sync and share.', url: 'https://nextcloud.com' }
]}
bgClass="bg-indigo-50"
/>
<Card
title="Email"
bigTech="Gmail (Google)"
alternatives={[
{ name: 'ProtonMail', description: 'Encrypted email.', url: 'https://protonmail.com' },
]}
bgClass="bg-yellow-50"
/>
<Card
title="Maps"
bigTech="Google Maps"
alternatives={[
{ name: 'OpenStreetMap', description: 'Community-driven maps.', url: 'https://www.openstreetmap.org' }
]}
bgClass="bg-teal-50"
/>
<Card
title="Messaging"
bigTech="WhatsApp (Meta), Slack"
alternatives={[
{ name: 'Signal', description: 'Encrypted messaging (alternative to WhatsApp).', url: 'https://signal.org' },
{ name: 'Element', description: 'Matrix-based chat (alternative to Slack).', url: 'https://element.io' }
]}
bgClass="bg-red-50"
/>
<Card
title="Search Engines"
bigTech="Google Search"
alternatives={[
{ name: 'DuckDuckGo', description: 'Privacy-protecting search.', url: 'https://duckduckgo.com' },
]}
bgClass="bg-green-50"
/>
<Card
title="Social Media"
bigTech="Twitter (X), Facebook (Meta)"
alternatives={[
{ name: 'Mastodon', description: 'Decentralized social network (alternative to Twitter).', url: 'https://joinmastodon.org' },
]}
bgClass="bg-purple-50"
/>
<Card
title="Video Platforms"
bigTech="YouTube (Google)"
alternatives={[
{ name: 'PeerTube', description: 'Decentralized video hosting.', url: 'https://joinpeertube.org' },
]}
bgClass="bg-pink-50"
/>
</div>
</div>
</div>
<Footer />
</main>
</Layout>

View File

@ -1,47 +0,0 @@
---
import Navbar from "../../components/Navbar";
import Footer from "../../components/Footer";
import Layout from "../../layouts/Layout.astro";
import "../../styles/globals.css";
---
<Layout>
<main class="flex min-h-screen flex-col justify-between">
<div>
<Navbar client:load activePage="OurProjects" />
<div class="px-4">
<h1 class="pb-4 pt-4 text-3xl font-semibold">Book Club</h1>
<p class="mb-4">
We read and discuss books about technology, power, and resistance. If
you have book suggestions, fill out the interest form on our <a
href="/"
class="underline">website homepage</a
> and let us know when we get in contact with you!
</p>
<div class="flex flex-col gap-8">
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold">Current Book</h2>
<div>
Race After Technology - Ruha Benjamin (see <a
class="underline"
href="https://www.dropbox.com/scl/fi/jnzvtiry7jn3xrts2n703/RAT-Discussion-Guide.pdf?rlkey=pq6ovaeydhcm8yi2u7lzzmiuj&e=2&dl=0"
>discussion guide
</a>)
</div>
</div>
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-2xl font-semibold">Past Books</h2>
<ul class="list-disc pl-6">
<li>Race After Technology - Ruha Benjamin</li>
<li>Common Circuits - Luis Felipe R. Murillo</li>
<li>Internet for the People - Ben Tarnoff</li>
</ul>
</div>
</div>
</div>
</div>
<Footer />
</main>
</Layout>

View File

@ -1,44 +0,0 @@
---
import Navbar from "../../components/Navbar";
import Footer from "../../components/Footer";
import Layout from "../../layouts/Layout.astro";
import { ZineGrid } from "../../components/zines/ZineGrid";
import "../../styles/globals.css";
---
<Layout>
<main class="flex min-h-screen flex-col justify-between">
<div>
<Navbar client:load activePage="OurProjects" />
<div class="px-4">
<h1 class="pb-4 pt-4 text-3xl font-semibold">Zines</h1>
<p class="mb-6">
We create and share zines about technology, power, and resistance. You are welcome to download the printable version to fold and distribute!
</p>
<ZineGrid client:load />
</div>
</div>
<Footer />
</main>
</Layout>
<style>
:global(.flipbook-container) {
display: flex;
justify-content: center;
align-items: center;
min-height: 520px;
}
:global(.flipbook) {
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
:global(.page) {
background-color: white;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -1,64 +0,0 @@
---
import Navbar from "../../../components/Navbar";
import Footer from "../../../components/Footer";
import Layout from "../../../layouts/Layout.astro";
import { ZineViewer } from "../../../components/zines/ZineViewer";
import { zines, type Zine } from "../../../data/zines";
import "../../../styles/globals.css";
export async function getStaticPaths() {
return zines.map((zine) => ({
params: { id: zine.id },
props: { zine },
}));
}
interface Props {
zine: Zine;
}
const { zine } = Astro.props;
---
<Layout>
<main class="flex min-h-screen flex-col justify-between">
<div>
<Navbar client:load activePage="OurProjects" />
<div class="px-4">
<a
href="/projects/zines"
class="inline-block pb-4 pt-4 text-sm underline"
>
← Back to all zines
</a>
<ZineViewer
client:load
pages={zine.pages}
title={zine.title}
zineId={zine.id}
/>
</div>
</div>
<Footer />
</main>
</Layout>
<style>
:global(.flipbook-container) {
display: flex;
justify-content: center;
align-items: center;
min-height: 520px;
}
:global(.flipbook) {
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
:global(.page) {
background-color: white;
display: flex;
align-items: center;
justify-content: center;
}
</style>