feat: de-duplicate about page lists
All checks were successful
Docker / build-and-push-image (push) Successful in 2m17s

This commit is contained in:
Troy 2025-07-19 20:42:21 +01:00
parent 8e42e17da5
commit a4fc110eb3
Signed by: troy
GPG key ID: DFC06C02ED3B4711
10 changed files with 149 additions and 223 deletions

View file

@ -0,0 +1,51 @@
---
import { Image } from "astro:assets";
import Link from "@components/Link.astro";
interface InformationItem {
image: ImageMetadata;
location: string;
main: string;
link?: string;
date: string;
date_opt?: string;
}
interface Props {
information: InformationItem[];
}
const { information } = Astro.props;
---
{
information.map((item) => (
<li class="bg-tertiary/10 flex w-full items-center gap-2 rounded-sm p-3 transition-transform duration-300 hover:-translate-y-1">
<Image
src={item.image}
alt={item.location}
class="h-auto max-w-12 rounded-sm"
loading="eager"
/>
<div>
<p class="text-secondary font-medium">{item.main}</p>
{item.link ? (
<Link
href={item.link}
class="text-secondary/70 decoration-tertiary text-sm underline hover:no-underline"
>
{item.location}
</Link>
) : (
<p class="text-secondary/70 text-sm">{item.location}</p>
)}
</div>
<div class="grow text-right">
<p class="text-tertiary font-mono text-sm text-nowrap">{item.date}</p>
{item.date_opt ? (
<p class="text-secondary/70 text-xs">{item.date_opt}</p>
) : null}
</div>
</li>
))
}

View file

@ -1,40 +0,0 @@
---
import { Icon } from "astro-icon/components";
type Props = {
institution: String;
qualification: String;
grades: Array<String>;
isOpen?: boolean;
};
const { institution, qualification, grades, isOpen = false } = Astro.props;
---
<div class="grid">
<details open={isOpen === true ? "open" : null} class="group">
<summary
class="flex cursor-pointer items-center justify-between py-3 font-bold"
>
<p
class="text-tertiary group-hover:text-secondary group-open:text-secondary group-open:group-hover:text-tertiary m-0 font-semibold text-balance transition-colors"
>
{qualification}
</p>
<span class="transition group-open:rotate-180">
<Icon
name="mdi:chevron-down"
class="text-tertiary group-open:text-secondary group-hover:text-secondary group-open:group-hover:text-tertiary h-6 w-auto transition-colors"
/>
</span>
</summary>
<div class="p-4">
<p class="text-tertiary my-0 mb-2 font-semibold">
{institution}
</p>
<ol class="list-inside list-disc">
{grades.map((grade) => <li class="text-tertiary">{grade}</li>)}
</ol>
</div>
</details>
</div>

View file

@ -1,5 +1,5 @@
<div
class="prose prose-neutral dark:prose-invert prose-lg prose-img:max-h-[90svh] prose-img:rounded-sm prose-img:w-auto prose-img:max-w-full prose-video:max-h-[90svh] prose-video:rounded-sm prose-video:w-auto prose-video::max-w-full prose-a:hover:no-underline prose-a:decoration-2 prose-a:underline-offset-2"
class="prose prose-neutral dark:prose-invert prose-lg prose-img:max-h-[90svh] prose-img:rounded-sm prose-img:w-auto prose-img:max-w-full prose-video:max-h-[90svh] prose-video:rounded-sm prose-video:w-auto prose-video::max-w-full prose-a:hover:no-underline prose-a:decoration-2 prose-a:underline-offset-2 prose-a:decoration-tertiary"
>
<slot />
</div>

View file

@ -1,7 +1,6 @@
---
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
import { Icon } from "astro-icon/components";
type Props = {
collection: CollectionEntry<"projects">;
@ -32,12 +31,9 @@ const { collection } = Astro.props;
{
collection.data.highlight ? (
<div class="relative transition-all duration-300 ease-in-out group-hover:opacity-0">
<div class="absolute bottom-5 left-5 flex w-fit items-center gap-1 rounded-full bg-green-900/20 p-1 transition-transform duration-300 hover:scale-102 hover:-rotate-2">
<Icon
name="mdi:plus-circle-outline"
class="h-auto w-6 rounded-full bg-green-900/70 p-0.5 text-green-400"
/>
<p>New!</p>
<div class="absolute bottom-5 left-5 flex w-fit items-center gap-0.5 rounded-full p-1 transition-transform duration-300 hover:scale-102 hover:-rotate-2">
<span class="mr-1 h-1.5 w-1.5 rounded-full bg-green-600 dark:bg-green-400" />
<p class="font-medium text-white">New</p>
</div>
</div>
) : null

View file

@ -63,7 +63,7 @@ Many of the product images included on the site were taken by myself specificall
## Branding
Along with the switch of platform, we came to the decision that the domain and overall branding would need to be updated to to go along with the rest of the work being done. I felt it was important however that the original red colour of <span class="bg-[#dd3e3e] rounded-sm p-0.5 text-black dark:text-white">#dd3e3e</span> was kept as it was a key part of the brand from all the way back in 2007.
Along with the switch of platform, we came to the decision that the domain and overall branding would need to be updated to to go along with the rest of the work being done. I felt it was important however that the original red colour of <span class="bg-[#dd3e3e] rounded-sm p-0.5">#dd3e3e</span> was kept as it was a key part of the brand from all the way back in 2007.
For the domain, we have gone with [camouflagestore.uk](https://camouflagestore.uk) (and its equavalent .co.uk tld). This was chosen as the location of the store is a key factor in its identity, and having this represented from the get-go meant a lot to the client. The legacy domain of [camouflage-store.com](https://camouflage-store.com) has also been kept since it has also been with Steve since 2007 and holds significant personal value.

View file

@ -27,7 +27,7 @@ const { title, description, image, date, updated, tags } = Astro.props;
tags={tags}
/>
<body
class="bg-primary text-secondary items mx-auto my-0 flex w-full max-w-[65ch] flex-col justify-start gap-6 p-4 md:my-6"
class="text-secondary items bg-primary mx-auto my-0 flex w-full max-w-[65ch] flex-col justify-start gap-6 p-4 md:my-6"
>
<Header />
<main transition:animate="fade" class="flex flex-col gap-6">

View file

@ -5,6 +5,7 @@ import { Image } from "astro:assets";
import { Icon } from "astro-icon/components";
import Link from "@components/Link.astro";
import { createSlug } from "@lib/utils";
import AboutList from "@components/AboutList.astro";
import me from "@assets/me.jpg";
import camoicon from "@assets/camouflage-store.png";
@ -32,7 +33,7 @@ const projects = [
id: 2,
name: "Sinkie Soldiers",
description:
"Keep control of the castle, but more importantly: your armour.",
"Sinkie Soldiers is a local co-op versus game in which you battle against your friends for control of the castle! Your goal is to break down the integrity of your enemy's armour and finish them off to claim the victory.",
tags: ["Unreal Engine", "Blender", "GIMP", "FL Studio", "Inkscape"],
link: "/projects/sinkie-soldiers",
print_link: "https://troylusty.com/projects/sinkie-soldiers",
@ -80,31 +81,31 @@ const sortedProjects = [...projects].sort((a, b) => a.id - b.id);
const experience = [
{
id: 1,
name: "Camouflage Store",
location: "Camouflage Store",
image: camoicon,
role: "E-commerce management",
main: "E-commerce management",
date: "2020 - Now",
link: "/projects/camouflage-store",
},
{
id: 2,
name: "Nisa",
location: "Nisa",
image: nisaicon,
role: "Promotional graphic design",
main: "Promotional graphic design",
date: "2022",
},
{
id: 3,
name: "Paignton Picture House Trust",
location: "Paignton Picture House Trust",
image: pphicon,
role: "Photogrammetrist (Volunteering)",
main: "Photogrammetrist (Volunteering)",
date: "2023",
},
{
id: 4,
name: "WebBoss",
location: "WebBoss",
image: webbossicon,
role: "Website mock-up templates (Work experience)",
main: "Website mock-up templates (Work experience)",
date: "2019",
},
];
@ -113,37 +114,69 @@ const sortedExperience = [...experience].sort((a, b) => a.id - b.id);
const education = [
{
id: 1,
name: "University of Plymouth",
location: "University of Plymouth",
image: uopicon,
course: "BA (Hons) Game Arts and Design",
main: "BA (Hons) Game Arts and Design",
date: "2024 - 2025",
grade: "First Class Honours",
date_opt: "First Class Honours",
},
{
id: 2,
name: "University Centre South Devon",
location: "University Centre South Devon",
image: ucsdicon,
course: "FdA Games and Interactive Design",
main: "FdA Games and Interactive Design",
date: "2022 - 2024",
},
{
id: 3,
name: "South Devon College",
location: "South Devon College",
image: sdcicon,
course:
"UAL Level 3 Extended Diploma in Creative Media Production and Technology",
main: "UAL Level 3 Extended Diploma in Creative Media Production and Technology",
date: "2020 - 2022",
},
{
id: 4,
name: "King Edward VI Community College",
location: "King Edward VI Community College",
image: keviccicon,
course:
"Art & Design BTEC, Computer Science A-level, 10 GCSEs, Creative iMedia Level 2",
main: "Art & Design BTEC, Computer Science A-level, 10 GCSEs, Creative iMedia Level 2",
date: "2014 - 2020",
},
];
const sortedEducation = [...education].sort((a, b) => a.id - b.id);
const links = [
{
id: 1,
label: "Website",
icon: "mdi:web",
href: "/",
},
{
id: 2,
label: "Email",
icon: "mdi:email",
href: `mailto:${SITE.EMAIL}`,
},
{
id: 3,
label: "Git",
icon: "mdi:git",
href: "https://code.threepop.com/explore",
},
{
id: 4,
label: "Steam",
icon: "mdi:steam",
href: "https://store.steampowered.com/developer/troy",
},
{
id: 5,
label: "LinkedIn",
icon: "mdi:linkedin",
href: "https://linkedin.com/in/troylusty",
},
];
const sortedLinks = [...links].sort((a, b) => a.id - b.id);
---
<Layout title={SITE.TITLE} description={ABOUT.DESCRIPTION}>
@ -153,7 +186,7 @@ const sortedEducation = [...education].sort((a, b) => a.id - b.id);
class="animate-reveal flex flex-col text-start text-3xl font-semibold opacity-0 sm:block"
>
<span class="text-secondary text-nowrap">{SITE.AUTHOR}</span><span
class="text-tertiary sm:ml-2">Digital designer.</span
class="text-tertiary sm:ml-2">Digital designer</span
>
</h1>
<p class="text-secondary/70 max-w-md text-pretty">
@ -161,47 +194,31 @@ const sortedEducation = [...education].sort((a, b) => a.id - b.id);
class="decoration-tertiary inline-flex items-center gap-x-1.5 align-baseline leading-none hover:underline hover:decoration-2 hover:underline-offset-2"
href="https://www.google.com/maps/place/Devon"
>
<Icon name="mdi:earth" class="h-3 w-3" />
<Icon name="mdi:earth" class="h-5 w-auto" />
Devon, United Kingdom, GMT
</Link>
</p><div class="text-secondary/70 flex gap-x-1 pt-1 text-sm print:hidden">
<Link
href="/"
aria-label="Website"
class="bg-button text-secondary hover:bg-button-active flex w-fit flex-row items-center gap-1 justify-self-center rounded-full p-2 text-center text-sm text-nowrap capitalize transition-colors duration-300"
>
<Icon name="mdi:web" class="h-4 w-4" />
</Link>
<Link
href={`mailto:${SITE.EMAIL}`}
aria-label="Email"
class="bg-button text-secondary hover:bg-button-active flex w-fit flex-row items-center gap-1 justify-self-center rounded-full p-2 text-center text-sm text-nowrap capitalize transition-colors duration-300"
>
<Icon name="mdi:email" class="h-4 w-4" />
</Link>
<Link
href="https://code.threepop.com/explore"
aria-label="Git"
class="bg-button text-secondary hover:bg-button-active flex w-fit flex-row items-center gap-1 justify-self-center rounded-full p-2 text-center text-sm text-nowrap capitalize transition-colors duration-300"
>
<Icon name="mdi:git" class="h-4 w-4" />
</Link>
<Link
href="https://store.steampowered.com/developer/troy"
aria-label="Steam"
class="bg-button text-secondary hover:bg-button-active flex w-fit flex-row items-center gap-1 justify-self-center rounded-full p-2 text-center text-sm text-nowrap capitalize transition-colors duration-300"
>
<Icon name="mdi:steam" class="h-4 w-4" />
</Link>
</p>
<div class="text-secondary/70 flex gap-x-2 text-sm print:hidden">
{
sortedLinks.map((link) => (
<Link
href={link.href}
aria-label={link.label}
class="text-tertiary hover:text-secondary items-center transition-colors duration-300"
>
<Icon name={link.icon} class="h-5 w-auto" />
</Link>
))
}
</div>
<p
class="text-secondary/70 hidden max-w-md items-center text-pretty print:flex"
class="text-secondary/70 hidden max-w-md items-center text-pretty print:inline-flex"
>
<Link
class="inline-flex items-center gap-x-1.5 align-baseline leading-none hover:underline"
class="decoration-tertiary inline-flex items-center gap-x-1.5 align-baseline leading-none"
href={`mailto:${SITE.EMAIL}`}
>
<Icon name="mdi:email" class="h-3 w-3" />
<Icon name="mdi:email" class="h-5 w-auto" />
{SITE.EMAIL}
</Link>
</p>
@ -236,85 +253,19 @@ const sortedEducation = [...education].sort((a, b) => a.id - b.id);
</p>
</section>
<section class="animate-reveal opacity-0 [animation-delay:0.2s] print:hidden">
<div
class="flex flex-col-reverse justify-between gap-5 rounded-sm bg-yellow-200/20 p-6 transition-transform duration-300 hover:scale-102 md:flex-row dark:bg-yellow-900/20"
>
<div class="flex flex-col gap-4">
<h3 class="text-secondary text-3xl font-medium">
Ready to make something cool?
</h3>
<p class="text-secondary/70">
Do you think I'd be a good fit to help out on a project you're working
on, or maybe just want to chat? <Link
href={`mailto:${SITE.EMAIL}`}
class="decoration-tertiary underline hover:no-underline"
>
Send me an email!</Link
>
</p>
</div>
<div class="flex items-center">
<Icon
name="mdi:handshake"
class="h-26 w-auto rounded-full bg-yellow-200/70 p-1 text-yellow-600 dark:bg-yellow-900/70 dark:text-yellow-400"
/>
</div>
</div>
</section>
<section
id="education"
class="animate-reveal opacity-0 [animation-delay:0.3s]"
>
<h2 class="text-2xl font-semibold">Education</h2>
<ol class="mt-3 flex flex-col gap-3">
<AboutList information={sortedEducation} />
</ol>
<h3 class="mt-3 text-xl font-semibold">Other education</h3>
<div class="mt-3 flex flex-col gap-3">
{
sortedEducation.map((education) => (
<div class="bg-tertiary/10 flex items-center justify-between gap-2 rounded-sm p-3 transition-transform duration-300 hover:-translate-y-1">
<div class="flex items-center gap-2">
<Image
src={education.image}
alt={education.name}
class="h-auto max-w-12 rounded-sm"
loading="eager"
/>
<p class="font-medium">
{education.course},
<span class="text-secondary/70">{education.name}</span>
</p>
</div>
<div class="text-right">
<p class="font-mono text-sm font-light text-nowrap">
{education.date}
</p>
{education.grade ? (
<p class="font-mono text-xs font-light">{education.grade}</p>
) : null}
</div>
</div>
))
}
</div>
<div class="flex flex-wrap gap-2">
<div
class="mt-3 flex w-fit items-center gap-2 rounded-full bg-green-200/20 p-2 transition-transform duration-300 hover:scale-102 hover:-rotate-2 dark:bg-green-900/20"
>
<Icon
name="mdi:check-decagram-outline"
class="h-auto w-8 rounded-full bg-green-200/70 p-0.5 text-green-600 dark:bg-green-900/70 dark:text-green-400"
/>
<p>Full drivers licence (A & B)</p>
</div>
<div
class="mt-3 flex w-fit items-center gap-2 rounded-full bg-cyan-200/20 p-2 transition-transform duration-300 hover:scale-102 hover:-rotate-2 dark:bg-cyan-900/20"
>
<Icon
name="mdi:water"
class="h-auto w-8 rounded-full bg-cyan-200/70 p-0.5 text-cyan-600 dark:bg-cyan-900/70 dark:text-cyan-400"
/>
<p>Lifesaving certification</p>
</div>
<ul class="text-secondary/70 list-inside list-disc text-lg font-medium">
<li>Full drivers licence (Category A & B)</li>
</ul>
</div>
</section>
@ -323,38 +274,9 @@ const sortedEducation = [...education].sort((a, b) => a.id - b.id);
class="animate-reveal opacity-0 [animation-delay:0.4s]"
>
<h2 class="text-2xl font-semibold">Experience</h2>
<div class="mt-3 flex flex-col gap-3">
{
sortedExperience.map((experience) => (
<div class="bg-tertiary/10 flex items-center justify-between gap-2 rounded-sm p-3 transition-transform duration-300 hover:-translate-y-1">
<div class="flex items-center gap-2">
<Image
src={experience.image}
alt={experience.name}
class="h-auto max-w-12 rounded-sm"
loading="eager"
/>
<p class="font-medium">
{experience.role},
{experience.link ? (
<Link
href={experience.link}
class="text-secondary/70 decoration-tertiary underline hover:no-underline"
>
{experience.name}
</Link>
) : (
<span class="text-secondary/70">{experience.name}</span>
)}
</p>
</div>
<p class="font-mono text-sm font-light text-nowrap">
{experience.date}
</p>
</div>
))
}
</div>
<ol class="mt-3 flex flex-col gap-3">
<AboutList information={sortedExperience} />
</ol>
</section>
<section
@ -369,7 +291,7 @@ const sortedEducation = [...education].sort((a, b) => a.id - b.id);
<div class="flex flex-col space-y-1.5">
<div class="space-y-1">
<>
<h3 class="mt-0 text-base font-semibold tracking-tight">
<h3 class="text-secondary mt-0 font-medium tracking-tight">
<Link
href={project.link}
class="decoration-tertiary inline-flex items-center gap-1 font-medium underline hover:no-underline"

View file

@ -32,15 +32,12 @@
:root {
color-scheme: light dark;
--primary: light-dark(var(--color-neutral-50), var(--color-neutral-950));
--secondary: light-dark(var(--color-neutral-950), var(--color-neutral-50));
--tertiary: light-dark(var(--color-neutral-500), var(--color-neutral-500));
--primary: light-dark(oklch(0.855 0.01 264), oklch(0.145 0.01 264));
--secondary: light-dark(oklch(0.145 0.01 264), oklch(0.855 0.01 264));
--tertiary: light-dark(oklch(0.556 0.01 264), oklch(0.556 0.01 264));
--button: light-dark(var(--color-neutral-200), var(--color-neutral-800));
--button-active: light-dark(
var(--color-neutral-300),
var(--color-neutral-700)
);
--button: light-dark(oklch(0.731 0.01 264), oklch(0.269 0.01 264));
--button-active: light-dark(oklch(0.629 0.01 264), oklch(0.371 0.01 264));
}
@media (prefers-color-scheme: dark) {