website

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

Home.tsx (11825B)


      1 import { useEffect } from "react";
      2 
      3 import htmlIcon from "../assets/file-type-html.svg";
      4 import jsIcon from "../assets/file-type-js.svg";
      5 import pythonIcon from "../assets/brand-python.svg";
      6 import terminalIcon from "../assets/terminal-2.svg";
      7 import alertTriangle from "../assets/alert-triangle.svg";
      8 import infoCircle from "../assets/info-circle.svg";
      9 
     10 export default function Home() {
     11 	useEffect(() => {
     12 		document.title = "Portfolio - Tom Honour";
     13 	}, []);
     14 	return (
     15 		<>
     16 			<a href="https://tomhonour.neocities.org">
     17 				<button className="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700">
     18 					Return to home
     19 				</button>
     20 			</a>
     21 			<div className="m-auto grid max-w-7xl gap-4 font-sans sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
     22 				{projects.map((project) => (
     23 					<div className="mx-2 w-96 max-w-[calc(100vw-calc(var(--spacing)*4))] place-self-center rounded-2xl shadow-xl">
     24 						<div className="max-h-48 overflow-hidden">
     25 							<img src={project.image} alt={project.alt} className="w-full" />
     26 						</div>
     27 						<div className="p-4">
     28 							{!project.metadata.maintained && (
     29 								<div className="flex items-center bg-amber-100 p-4 text-orange-800">
     30 									<img src={alertTriangle} className="me-2" />
     31 									This project {project.metadata.old && "is old and"} isn't
     32 									being maintained.
     33 								</div>
     34 							)}
     35 							{project.metadata.comingSoon && (
     36 								<div className="flex items-center bg-sky-100 p-4 text-blue-800">
     37 									<img src={infoCircle} className="me-2" />
     38 									Coming soon...
     39 								</div>
     40 							)}
     41 							<div className="my-2">
     42 								<div className="me-2 inline">
     43 									{project.metadata.icons.includes("terminal") && (
     44 										<img src={terminalIcon} />
     45 									)}
     46 									{project.metadata.icons.includes("javascript") && (
     47 										<img src={jsIcon} />
     48 									)}
     49 									{project.metadata.icons.includes("html") && (
     50 										<img src={htmlIcon} />
     51 									)}
     52 									{project.metadata.icons.includes("python") && (
     53 										<img src={pythonIcon} />
     54 									)}
     55 								</div>
     56 								<h2 className="m-0 inline">{project.title}</h2>
     57 							</div>
     58 							<p className="text-gray-500">{project.body}</p>
     59 							<div className="flex justify-around text-center no-underline">
     60 								{project.btn1 && (
     61 									<a className="no-underline" href={project.btn1.link}>
     62 										{project.btn1.name}
     63 									</a>
     64 								)}
     65 								{project.btn2 && (
     66 									<a className="no-underline" href={project.btn2.link}>
     67 										{project.btn2.name}
     68 									</a>
     69 								)}
     70 								{project.btn3 && (
     71 									<a className="no-underline" href={project.btn3.link}>
     72 										{project.btn3.name}
     73 									</a>
     74 								)}
     75 							</div>
     76 						</div>
     77 					</div>
     78 				))}
     79 			</div>
     80 		</>
     81 	);
     82 }
     83 
     84 const projects = [
     85 	{
     86 		alt: "letterboxd logo",
     87 		image: "/letterboxd.png",
     88 		title: "Letterboxd Won't Watch Button (browser extension)",
     89 		body: "Fade the posters of films you won't watch. Uses the browser extension storage API.",
     90 		btn1: {
     91 			name: "View code",
     92 			link: "https://tomhonour.neocities.org/git/boxd-add-on/files.html",
     93 		},
     94 		btn2: {
     95 			name: "Get extension",
     96 			link: "https://addons.mozilla.org/en-GB/firefox/addon/letterboxd-wont-watch-button/",
     97 		},
     98 		metadata: {
     99 			maintained: false,
    100 			old: true,
    101 			icons: ["javascript"],
    102 		},
    103 	},
    104 	{
    105 		alt: "Vala logo",
    106 		image: "/vala.png",
    107 		title: "Unnamed Vala Project",
    108 		body: "Vala is an object-oriented programming language with a self-hosting compiler that generates C code and uses the GObject type system.",
    109 		metadata: {
    110 			maintained: true,
    111 			old: false,
    112 			comingSoon: true,
    113 			icons: [""],
    114 		},
    115 		btn1: {
    116 			name: "Read my blog post",
    117 			link: "/blog/setting-up-a-vala-development-environment",
    118 		},
    119 		btn2: {
    120 			name: "Go to site",
    121 			link: "https://vala.dev",
    122 		},
    123 	},
    124 	{
    125 		alt: "Deno logo",
    126 		image: "/deno.png",
    127 		title: "Deno Sandbox command line tool",
    128 		body: 'Programmatically create git patches for changes on a remote file system (a deno sandbox). Let\'s you modify a git repo safely - for example, when using a "coding agent" (aka RCE agent) - and bring those changes to your machine in the form of a patch file.',
    129 		metadata: {
    130 			maintained: false,
    131 			old: true,
    132 			comingSoon: false,
    133 			icons: ["javascript"],
    134 		},
    135 		btn1: {
    136 			name: "Read my blog post (coming soon)",
    137 			link: "",
    138 		},
    139 		btn2: {
    140 			name: "Read code",
    141 			link: "https://tomhonour.neocities.org/git/denosandbox/file/sandbox.js",
    142 		},
    143 	},
    144 	{
    145 		alt: "yt-dlp logo",
    146 		image: "/yt-dlp.png",
    147 		title: "A new video extractor for yt-dlp (in code review)",
    148 		body: "yt-dlp can download videos from thousands of sites (not just YouTube)! I found a random site which yt-dlp didn't support, and created a video extractor for it.",
    149 		metadata: {
    150 			maintained: false,
    151 			old: true,
    152 			comingSoon: false,
    153 			icons: ["python"],
    154 		},
    155 		btn1: {
    156 			name: "Read code",
    157 			link: "https://github.com/yt-dlp/yt-dlp/pull/15045/files",
    158 		},
    159 	},
    160 	{
    161 		alt: "OpenFrontIO logo",
    162 		image: "/openfront.png",
    163 		title: "OpenFrontIO Teams Game Notifier",
    164 		body: "Get a notification whenever a public OpenFront teams game (2 or 3 teams) is available/about to start.",
    165 		metadata: {
    166 			maintained: false,
    167 			old: true,
    168 			comingSoon: false,
    169 			icons: ["terminal"],
    170 		},
    171 		btn1: {
    172 			name: "Read code",
    173 			link: "https://tomhonour.neocities.org/git/old-scripts/file/openfront",
    174 		},
    175 	},
    176 	{
    177 		alt: "ytfzf logo",
    178 		image: "/ytfzf.png",
    179 		title: "y (wrapper for ytfzf and yt-dlp)",
    180 		body: "Download audio or video, and browse channels and search using command line options. Uses ytfzf, yt-dlp, and mpv.",
    181 		btn1: {
    182 			name: "Read bash version",
    183 			link: "https://tomhonour.neocities.org/git/old-scripts/file/y",
    184 		},
    185 		btn2: {
    186 			name: "Read JavaScript version",
    187 			link: "https://tomhonour.neocities.org/git/old-scripts/file/node/y.mjs",
    188 		},
    189 		metadata: {
    190 			maintained: false,
    191 			old: true,
    192 			icons: ["terminal", "javascript"],
    193 		},
    194 	},
    195 	{
    196 		alt: "mpv and filmon logos",
    197 		image: "/filmon.png",
    198 		title: "TV using mpv, IPC, socat, and filmon",
    199 		body: "After about 60 seconds of free TV, filmon returns a 403 status code. This script circumvents this restriction by fetching a new stream and directing it to the same mpv window.",
    200 		btn1: {
    201 			name: "Read bash version",
    202 			link: "https://tomhonour.neocities.org/git/old-scripts/file/filmon.sh",
    203 		},
    204 		btn2: {
    205 			name: "Read JavaScript version",
    206 			link: "https://tomhonour.neocities.org/git/old-scripts/file/node/filmon.mjs",
    207 		},
    208 		// btn3: {
    209 		// 	name: "View video",
    210 		// 	link: "/Send_multiple_videos_to_one_mpv_window_using_IPC_and_a_socket__re-upload_.mp4",
    211 		// },
    212 		metadata: {
    213 			maintained: false,
    214 			old: true,
    215 			icons: ["terminal", "javascript"],
    216 		},
    217 	},
    218 	// {
    219 	// 	alt: "Screenshot of fares tracker",
    220 	// 	image: "/fares_tracker.png",
    221 	// 	title: "Advance Train Tickets Fare Tracking",
    222 	// 	body: "See changes in train ticket fares between now and the end of the booking window.",
    223 	// 	metadata: {
    224 	// 		maintained: false,
    225 	//old: true,
    226 	// 		icons: ["html", "javascript"]
    227 	// 	},
    228 	// 	btn1: {
    229 	// 		name: "Visit",
    230 	// 		link: "/portfolio/fares"
    231 	// 	},
    232 	// 	btn2: {
    233 	// 		name: "Read frontend code",
    234 	// 		link: "https://git.sr.ht/~tomhonour/filmsbytom/tree/master/item/portfolio/Fares.tsx"
    235 	// 	},
    236 	// 	btn3: {
    237 	// 		name: "Read backend code",
    238 	// 		link: "https://git.sr.ht/~tomhonour/filmsbytom/tree/master/item/backend/routes/fares.ts"
    239 	// 	}
    240 	// },
    241 	{
    242 		alt: "Archi and JavaScipt logos.",
    243 		image: "/jarchi-utils.png",
    244 		title: "jarchi-utils",
    245 		body: "Pure JavaScript implementations of jArchi methods.",
    246 		metadata: {
    247 			maintained: false,
    248 			old: true,
    249 			icons: ["javascript"],
    250 		},
    251 		btn1: {
    252 			name: "Go to package",
    253 			link: "https://www.npmjs.com/package/jarchi-utils",
    254 		},
    255 		btn2: {
    256 			name: "View project",
    257 			link: "https://tomhonour.neocities.org/git/jarchi-utils/files.html",
    258 		},
    259 	},
    260 	// {
    261 	// 	alt: "Wood Job! movie poster in windowboxd",
    262 	// 	image: "/woodjob.png",
    263 	// 	title: "windowboxd",
    264 	// 	body: "Alternative frontend for letterboxd. Requires no JavaScript. Inspired by invidious and nitter.",
    265 	// 	metadata: {
    266 	// 		maintained: true,
    267 	//old: true,
    268 	// 		icons: ["html", "javascript"]
    269 	// 	},
    270 	// 	btn1: {
    271 	// 		name: "Go to repository",
    272 	// 		link: "https://github.com/5-tom/windowboxd"
    273 	// 	}
    274 	// },
    275 	// {
    276 	// 	alt: "pipes",
    277 	// 	image: "/littlepipelines.png",
    278 	// 	title: "LittlePipelines (Contributor to)",
    279 	// 	body: "Learn to code through pull request reviews.",
    280 	// 	metadata: {
    281 	// 		maintained: true,
    282 	//old: true,
    283 	// 		comingSoon: true,
    284 	// 		icons: ["html", "javascript"]
    285 	// 	}
    286 	// },
    287 	{
    288 		alt: "kodi file structure",
    289 		image: "/kodi.png",
    290 		title: "Organise movies to the Kodi recommendation",
    291 		body: "Kodi recommends that movie libraries be organised like: 'Dredd (2012)' > 'Dredd (2012).mkv'. This script batch renames and batch moves your movies to conform to Kodi's recommendation.",
    292 		btn1: {
    293 			name: "Read bash version",
    294 			link: "https://tomhonour.neocities.org/git/old-scripts/file/kodi",
    295 		},
    296 		btn2: {
    297 			name: "Read JavaScript version",
    298 			link: "https://tomhonour.neocities.org/git/old-scripts/file/node/kodi.mjs",
    299 		},
    300 		btn3: {
    301 			name: "Read recommendation",
    302 			link: "https://kodi.wiki/view/Naming_video_files/Movies",
    303 		},
    304 		metadata: {
    305 			maintained: false,
    306 			old: true,
    307 			icons: ["terminal", "javascript"],
    308 		},
    309 	},
    310 	{
    311 		alt: "GitHub Copilot for VSCodium logos",
    312 		image: "/copilot.png",
    313 		title: "Install GitHub Copilot for VSCodium (snap)",
    314 		body: "The Copilot extension can't be installed for VSCodium without a little bit of hacking!",
    315 		btn1: {
    316 			name: "Read code",
    317 			link: "https://tomhonour.neocities.org/git/old-scripts/file/copilot.sh",
    318 		},
    319 		metadata: {
    320 			maintained: false,
    321 			old: true,
    322 			icons: ["terminal"],
    323 		},
    324 	},
    325 	{
    326 		alt: "GJS logo",
    327 		image: "/jsgnome.png",
    328 		title: "Unnamed GJS Project",
    329 		body: "GJS is a language binding for JavaScript and GTK. It lets you write native Linux desktop apps with JavaScript.",
    330 		metadata: {
    331 			maintained: true,
    332 			old: false,
    333 			comingSoon: true,
    334 			icons: ["javascript"],
    335 		},
    336 		btn1: {
    337 			name: "Watch talk",
    338 			link: "https://www.youtube.com/watch?v=YqmmK16rIm4",
    339 		},
    340 		btn2: {
    341 			name: "Read README",
    342 			link: "https://gitlab.gnome.org/GNOME/gjs/-/blob/master/doc/README.md",
    343 		},
    344 		btn3: {
    345 			name: "GTK4 + GJS Book",
    346 			link: "https://rmnvgr.gitlab.io/gtk4-gjs-book/",
    347 		},
    348 	},
    349 	// {
    350 	// 	alt: "pipes",
    351 	// 	image: "/dataducts.jpg",
    352 	// 	title: "DataDucts",
    353 	// 	body: "Webmaster for a digital transformation company.",
    354 	// 	metadata: {
    355 	// 		maintained: true,
    356 	//old: true,
    357 	// 		comingSoon: true,
    358 	// 		icons: ["html", "javascript"]
    359 	// 	}
    360 	// },
    361 	// {
    362 	// 	alt: "Model of 3D man with magnifying glass",
    363 	// 	image: "/3d_man.jpeg",
    364 	// 	title: "Tab over words in text and copy to clipboard (terminal)",
    365 	// 	body: "Like less and man's jump to next word, this tool lets you cycle over the words in a text file. After pressing Enter, the highlighted word is copied to your clipboard.",
    366 	// 	btn1: {
    367 	// 		name: "View code",
    368 	// 		link: "https://git.sr.ht/~tomhonour/old-scripts/tree/master/item/node/copy-word.mjs",
    369 	// 	},
    370 	// 	// btn2: {
    371 	// 	// 	name: "View video",
    372 	// 	// 	link: "/Terminal_tab_over_words_and_copy_to_clipboard.mp4",
    373 	// 	// },
    374 	// 	metadata: {
    375 	// 		maintained: false,
    376 	// 		old: true,
    377 	// 		icons: ["terminal", "javascript"],
    378 	// 	},
    379 	// },
    380 	{
    381 		alt: "Photo of Tom Honour",
    382 		image: "/photo.jpeg",
    383 		title: "This site",
    384 		body: "A static homepage, a react portfolio, and a Hugo blog.",
    385 		btn1: {
    386 			name: "View code",
    387 			link: "https://tomhonour.neocities.org/git/website/files.html",
    388 		},
    389 		// btn2: {
    390 		// 	name: "View video",
    391 		// 	link: "/Terminal_tab_over_words_and_copy_to_clipboard.mp4",
    392 		// },
    393 		metadata: {
    394 			maintained: true,
    395 			old: false,
    396 			icons: ["html", "javascript"],
    397 		},
    398 	},
    399 ];