85 Commits

Author SHA1 Message Date
8eb8c2c5e8 Simplify deployment instructions
The deployment instructions were out of date and duplicated elsewhere, so I've updated the documentation on the rtm-config repo and the how-to-get-access page in our collectives and just pointed the instructions there.
2026-04-27 00:32:11 +00:00
ff3e5987a5 allow zooming when reading zines 2026-03-30 20:58:57 -07:00
B
f89122b65c Merge pull request 'fixing out of order pages' (#5) from strawberry/discotech-101-pages-out-of-order into main
Reviewed-on: #5
2026-03-17 02:21:04 +00:00
7782fc9118 fixing out of order pages 2026-03-16 19:19:54 -07:00
b3d26f3a58 Merge pull request 'Adding Signal 101 Zine and Making Some Capitalization/Naming Fixes for Other Zines' (#4) from strawberry/signal-101-zine into main
Reviewed-on: #4
2026-02-26 04:58:24 +00:00
ac2018468c updates to zine data file 2026-02-25 20:56:16 -08:00
760dfa6afc signal 101 printable 2026-02-25 20:55:40 -08:00
4a1c7cfbfb rename printable 2026-02-25 20:55:01 -08:00
ec11f7bb54 rename De-monopoly Cheatsheet to Brief Notes from a De-Monopoly DiscoTech 2026-02-25 20:54:23 -08:00
e81b3af413 adding signal 101 zine pages 2026-02-25 20:52:21 -08:00
c20b6aade0 add a new zine 2026-02-02 15:56:16 -08:00
2f43a5ca6b 1. fix copyleft year and color in darkmode 2. remove duplicate ourprojects title 2026-01-26 21:48:58 -08:00
64d332a89b remove duplicate entries in the projects page 2026-01-21 08:30:13 -08:00
4ae505665f Change printable zine permission so they can be downloaded; rename flipbookviewer to zineviewer 2026-01-21 08:20:25 -08:00
78cf96919d Fixing all the stuff I effed up in rebasing 2026-01-19 22:44:08 -08:00
14041875a3 pet calendar updates 2026-01-19 22:44:08 -08:00
715aaed730 Add all the tutorial links babyyyy 2026-01-19 22:44:08 -08:00
f763b1ae70 Create pet calendar page 2026-01-19 22:44:08 -08:00
3f02ccd080 rename ../ourprojectspages/... to just .../projects/... in the page route 2026-01-19 22:44:08 -08:00
22550a7afa add zines to our projects 2026-01-19 22:44:08 -08:00
c50000feae update bookclub page; rename What Were Working On to Our Projects in the code 2026-01-19 22:44:08 -08:00
8c8e048466 Merge pull request 'Create pet calendar page' (#3) from js/petcal into main
Reviewed-on: #3
2026-01-20 06:42:35 +00:00
69b6133fb0 Merge branch 'main' into js/petcal 2026-01-19 22:13:06 -08:00
763786f5bf Fixing all the stuff I effed up in rebasing 2026-01-19 21:47:23 -08:00
5193c400b4 pet calendar updates 2026-01-19 21:37:30 -08:00
6079272b8c Add all the tutorial links babyyyy 2026-01-19 21:37:13 -08:00
2df8d27809 Create pet calendar page 2026-01-19 21:36:10 -08:00
4e0436f3f2 rename ../ourprojectspages/... to just .../projects/... in the page route 2026-01-19 21:35:51 -08:00
c22727adc7 add zines to our projects 2026-01-19 21:35:34 -08:00
1af1c17d2c update bookclub page; rename What Were Working On to Our Projects in the code 2026-01-19 21:34:04 -08:00
f51b65e13c Update Our Points of Unity 2026-01-19 21:32:54 -08:00
64f03449b7 make navbar buttons less dark in light mode 2026-01-19 21:32:54 -08:00
097496ff94 1. Rename What we're working on to our projects 2. make them look clickable 2026-01-19 21:32:54 -08:00
dd13c8cfcc remove stale alternatives 2026-01-19 21:32:54 -08:00
f82c72ec24 Add all the tutorial links babyyyy 2026-01-19 21:22:02 -08:00
340239f990 rename ../ourprojectspages/... to just .../projects/... in the page route 2026-01-18 22:38:15 -08:00
01fd965261 add zines to our projects 2026-01-18 21:56:55 -08:00
cf2073ee70 update bookclub page; rename What Were Working On to Our Projects in the code 2026-01-18 21:02:11 -08:00
1f10a73ecf Update Our Points of Unity 2026-01-18 20:33:54 -08:00
7333cc31c8 make navbar buttons less dark in light mode 2026-01-15 21:18:37 -08:00
e7f08b8ff7 Create pet calendar page 2026-01-15 21:10:16 -08:00
4be4395300 1. Rename What we're working on to our projects 2. make them look clickable 2026-01-15 20:50:38 -08:00
baa5edf104 remove stale alternatives 2026-01-15 20:31:54 -08:00
522728c0e4 Merge pull request 'updating readme with deployment info' (#2) from strawberry/readme-deployment-info-update into main
Reviewed-on: #2
Reviewed-by: ammaratef45 <ammaratef45@proton.me>
2025-10-17 04:22:59 +00:00
d04dfb16f5 addressing feedback 2025-10-16 21:22:14 -07:00
d235a73867 adding instructions for clone of sootie-config 2025-10-14 12:18:01 -07:00
adb852907a updating readme with deployment info 2025-10-14 12:12:01 -07:00
ca9f5fecba adding discussion guide to book club 2025-10-13 22:33:46 -07:00
2559410fb3 adjusting book club pic width 2025-10-13 20:12:52 -07:00
a4d6196259 add details to readme 2025-10-13 19:52:16 -07:00
3bb5b66b2f update version to 0.0.8 2025-10-13 19:17:42 -07:00
a6541cb62c fixing horizontal sizing issue in navbar 2025-10-09 19:54:42 -07:00
efac99eb98 updating book club page 2025-10-09 19:52:03 -07:00
ba1256bdfe update README with deployment instruction
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-01 15:02:50 -07:00
ec6c20848f update tags 2025-09-01 13:31:39 -07:00
7d22c335fd strike through big tech option 2025-09-01 13:26:42 -07:00
92af7f9213 remove this website from what we are working on + add alternatives page 2025-09-01 13:17:24 -07:00
88f6919e73 add alt page 2025-08-19 16:08:26 -07:00
65556ad238 update to 0.0.6 version
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 22:10:29 -07:00
2c1ce61529 update version to 0.0.3
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 21:56:41 -07:00
fb6b3465be add a heart symbol to points of unity
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 21:53:41 -07:00
4c78c777c6 add build badge
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 21:45:32 -07:00
66900162c5 add .drone.yml
Some checks failed
continuous-integration/drone/push Build is failing
2025-07-31 21:40:50 -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
19 changed files with 370 additions and 198 deletions

View File

@ -55,7 +55,7 @@ version=<specify-version>
docker build --platform linux/amd64 -t git.coopcloud.tech/rtm/rtmwebsite:$version .
```
## Push the image to gitea registery
## Push the image to gitea registry
Check out [this documentation](https://docs.gitea.com/next/usage/packages/container) for how to login with gitea registery.
@ -66,63 +66,19 @@ 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
## Deploy changes to resisttechmonopolies.online
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 coop cloud tooling [private recipe](https://git.coopcloud.tech/RTM/rtm-astro-recipe) to deploy this website to our [fleet](https://git.coopcloud.tech/RTM/rtm-config) of lil cat-named machines.
You will need to have wget (`brew install wget` on mac) and [abra](https://docs.coopcloud.tech/abra/) installed.
Read the "Fleet Setup and access" collectives page on our RTM nextcloud to get the `rtm-config` repo set up with the `rtm-astro-recipe` submodule and install the `abra` command line tool!
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:
Then, in your `rtm-config` repo update the RTM website image version to the one you just built and published by running:
``` bash
git clone https://git.coopcloud.tech/RTM/sootie-config.git .
# DON'T FORGET THE . AT THE END OF THE COMMAND
$ abra app config resisttechmonopolies.online # Change VERSION to the docker image you just pushed
$ abra app deploy -f resisttechmonopolies.online # Re-deploy the RTM website, now with your changes!
$ git add abra/servers/laylotta.resisttechmonopolies.online/resisttechmonopolies.online
$ git commit -m 'Updated website to x.x.x' # Publish this change to the rtm-config repo either via direct commit or a PR
```
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
abra app deploy resisttechmonopolies.online
```
Done! Thank you for your contributions 🏋️⚡📖!

29
package-lock.json generated
View File

@ -26,6 +26,7 @@
"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",
@ -5176,6 +5177,12 @@
"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",
@ -5669,6 +5676,15 @@
"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",
@ -10291,6 +10307,11 @@
"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",
@ -10542,6 +10563,14 @@
"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,6 +27,7 @@
"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",

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,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,19 +1,21 @@
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 }: ZineCardProps) {
const href = `/projects/zines/${zine.id}`;
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">
<a
href={href}
<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">
@ -27,19 +29,23 @@ export function ZineCard({ zine }: ZineCardProps) {
{zine.description && (
<p className="text-sm text-muted-foreground">{zine.description}</p>
)}
</a>
</button>
<div className="flex gap-2 mt-2">
<Button variant="outline" size="sm" asChild>
<a href={href}>Read Online</a>
<Button variant="outline" size="sm" onClick={onClick}>
Read Online
</Button>
<Button variant="outline" size="sm" asChild>
<Button
variant="outline"
size="sm"
asChild
onClick={handleDownload}
>
<a href={zine.printablePdf} download>
<Download />
<Download className="h-4 w-4 mr-1" />
Printable
</a>
</Button>
<ShareZineButton zineId={zine.id} />
</div>
</div>
);

View File

@ -1,12 +1,54 @@
import { zines } from "../../data/zines";
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} />
))}
</div>
<>
<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}
/>
)}
</DialogContent>
</Dialog>
</>
);
}

View File

@ -0,0 +1,245 @@
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";
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;
}
const MIN_ZOOM = 1;
const MAX_ZOOM = 2.5;
const ZOOM_STEP = 0.5;
export function ZineViewer({ pages, title }: 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>
)}
</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

@ -93,21 +93,4 @@ export const zines: Zine[] = [
],
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

@ -22,3 +22,23 @@ import "../../styles/globals.css";
<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,70 +0,0 @@
---
import { Download } from "lucide-react";
import Navbar from "../../../components/Navbar";
import Footer from "../../../components/Footer";
import Layout from "../../../layouts/Layout.astro";
import { ShareZineButton } from "../../../components/zines/ShareZineButton";
import { buttonVariants } from "../../../components/ui/button";
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>
<div class="mx-auto max-w-3xl">
<h1 class="text-2xl font-semibold">{zine.title}</h1>
{zine.description && (
<p class="mt-1 text-sm text-muted-foreground">{zine.description}</p>
)}
<div class="mt-4 flex gap-2">
<ShareZineButton client:load zineId={zine.id} />
<a
href={zine.printablePdf}
download
class={buttonVariants({ variant: "outline", size: "sm" })}
>
<Download />
Printable
</a>
</div>
<div class="flex flex-col gap-4 py-6">
{zine.pages.map((page, i) => (
<img
src={page}
alt={`Page ${i + 1} of ${zine.title}`}
loading={i === 0 ? "eager" : "lazy"}
class="w-full h-auto rounded shadow-sm bg-white"
/>
))}
</div>
</div>
</div>
</div>
<Footer />
</main>
</Layout>