diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..445bf48 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/.example.env b/.example.env new file mode 100644 index 0000000..f8f50fa --- /dev/null +++ b/.example.env @@ -0,0 +1,3 @@ +WEBMENTION_API_KEY= +WEBMENTION_URL= +WEBMENTION_PINGBACK=#optional \ No newline at end of file diff --git a/.gitignore b/.gitignore index a128d25..6a0ee8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,38 +1,28 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# build output +dist/ +.output/ # dependencies -data -node_modules -.pnp -.pnp.js -tmp/ +node_modules/ -# testing -coverage - -# next.js -.next/ -out/ -build - -# misc -.DS_Store -*.pem - -# debug +# logs npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* -# local env files + +# environment variables .env -.env.local -.env.development.local -.env.test.local -.env.production.local +.env.production -# turbo -.turbo +# macOS-specific files +.DS_Store -# vercel +# misc +*.pem +.cache +.astro +tmp/ .vercel +.env*.local diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..8fdd954 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ebf613f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +*.min.js +node_modules + +# cache-dirs +**/.cache + +pnpm-lock.yaml +dist \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..67da383 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,23 @@ +/** @type {import("@types/prettier").Options} */ +export default { + printWidth: 100, + semi: true, + singleQuote: false, + tabWidth: 2, + useTabs: true, + plugins: ["prettier-plugin-astro", "prettier-plugin-tailwindcss" /* Must come last */], + overrides: [ + { + files: "**/*.astro", + options: { + parser: "astro", + }, + }, + { + files: ["*.mdx", "*.md"], + options: { + printWidth: 80, + }, + }, + ], +}; diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 8b38db9..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "explorer.excludeGitIgnore": false, - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#95ee3a", - "activityBar.background": "#95ee3a", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#3290ed", - "activityBarBadge.foreground": "#e7e7e7", - "commandCenter.border": "#15202b99", - "sash.hoverBorder": "#95ee3a", - "statusBar.background": "#7ce114", - "statusBar.foreground": "#15202b", - "statusBarItem.hoverBackground": "#62b210", - "statusBarItem.remoteBackground": "#7ce114", - "statusBarItem.remoteForeground": "#15202b", - "titleBar.activeBackground": "#7ce114", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#7ce11499", - "titleBar.inactiveForeground": "#15202b99" - }, - "peacock.color": "#7ce114" -} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b772f71 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,47 @@ + + +# prad.nu + +## Purpose +Personal CV / portfolio site for Prad Nukala (https://prad.nu). A static [Astro](https://astro.build) v6 site (forked from the Astro Cactus theme) organised into four content sections — **Projects**, **Experience**, **Writing**, **Speaking** — where Experience entries double as a cross-linking taxonomy. See `README.md` for the human-facing overview. + +## Key Files +| File | Description | +|------|-------------| +| `package.json` | Dependencies and scripts (pnpm). | +| `astro.config.ts` | Astro integrations (MDX, sitemap, icon, robots, webmanifest, expressive-code), markdown remark/rehype pipeline, Satori OG fonts. | +| `tailwind.config.ts` | Tailwind v4 config (most styling is in `src/styles/global.css`). | +| `tsconfig.json` | TypeScript config; defines the `@/*` → `src/*` path alias. | +| `biome.json` | Biome linter/formatter config (`pnpm check` / `pnpm lint`). | +| `mise.toml` / `pnpm-workspace.yaml` | Toolchain + workspace config. | +| `README.md` | Project purpose, content model, frontmatter reference. | + +## Subdirectories +| Directory | Purpose | +|-----------|---------| +| `src/` | All application source (see `src/AGENTS.md`). | +| `public/` | Static assets served as-is (see `public/AGENTS.md`). | + +## For AI Agents + +### Working In This Directory +- Package manager is **pnpm**. Output is **static** (`output: 'static'`, `./dist`) — no SSR adapter. +- Import from source via the `@/` alias (e.g. `@/site.config`), not long relative paths. +- Most site-wide config (title, url, `socialLinks`, `streamOrigin`, `menuLinks`) lives in `src/site.config.ts`. + +### Testing Requirements +- `pnpm build` is the primary correctness gate — it runs `astro check`-level content validation and fails on bad frontmatter, broken collection refs, or type errors. +- `pnpm check` runs `astro check` + Biome. `pnpm dev` serves locally for visual checks. + +### Common Patterns +- Content lives in Content Collections; schema is defined once in `src/content.config.ts`. +- `is:inline` is required on ` + + + + diff --git a/src/components/Section.tsx b/src/components/Section.tsx deleted file mode 100644 index 4a2fc73..0000000 --- a/src/components/Section.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useId } from 'react' - -export function Section({ - title, - children, -}: { - title: string - children: React.ReactNode -}) { - let id = useId() - - return ( -
-
-

- {title} -

-
{children}
-
-
- ) -} diff --git a/src/components/SimpleLayout.tsx b/src/components/SimpleLayout.tsx deleted file mode 100644 index 2023a38..0000000 --- a/src/components/SimpleLayout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Container } from '@/components/Container' - -export function SimpleLayout({ - title, - intro, - children, -}: { - title: string - intro: string - children?: React.ReactNode -}) { - return ( - -
-

- {title} -

-

- {intro} -

-
- {children &&
{children}
} -
- ) -} diff --git a/src/components/SkipLink.astro b/src/components/SkipLink.astro new file mode 100644 index 0000000..dae6bad --- /dev/null +++ b/src/components/SkipLink.astro @@ -0,0 +1,3 @@ +skip to content + diff --git a/src/components/SocialIcons.tsx b/src/components/SocialIcons.tsx deleted file mode 100644 index ae7daab..0000000 --- a/src/components/SocialIcons.tsx +++ /dev/null @@ -1,44 +0,0 @@ -export function TwitterIcon(props: React.ComponentPropsWithoutRef<'svg'>) { - return ( - - ) -} - -export function MediumIcon(props: React.ComponentPropsWithoutRef<'svg'>) { - return ( - - ) -} - -export function InstagramIcon(props: React.ComponentPropsWithoutRef<'svg'>) { - return ( - - ) -} - -export function GitHubIcon(props: React.ComponentPropsWithoutRef<'svg'>) { - return ( - - ) -} - -export function LinkedInIcon(props: React.ComponentPropsWithoutRef<'svg'>) { - return ( - - ) -} diff --git a/src/components/TagList.astro b/src/components/TagList.astro new file mode 100644 index 0000000..9501d70 --- /dev/null +++ b/src/components/TagList.astro @@ -0,0 +1,27 @@ +--- +import { slugifyTag } from "@/data/tags"; + +interface Props { + tags: string[]; + class?: string; +} + +const { tags, class: className } = Astro.props; +--- + +{ + tags.length > 0 && ( + + ) +} diff --git a/src/components/ThemeProvider.astro b/src/components/ThemeProvider.astro new file mode 100644 index 0000000..0aad79e --- /dev/null +++ b/src/components/ThemeProvider.astro @@ -0,0 +1,48 @@ +{/* Inlined to avoid FOUC. This is a parser blocking script. */} + diff --git a/src/components/ThemeToggle.astro b/src/components/ThemeToggle.astro new file mode 100644 index 0000000..8316886 --- /dev/null +++ b/src/components/ThemeToggle.astro @@ -0,0 +1,90 @@ + + + + + diff --git a/src/components/YouTube.astro b/src/components/YouTube.astro new file mode 100644 index 0000000..d3715f2 --- /dev/null +++ b/src/components/YouTube.astro @@ -0,0 +1,19 @@ +--- +interface Props { + id: string; + title?: string; +} + +const { id, title = "YouTube video player" } = Astro.props; +--- + +
+ +
diff --git a/src/components/blog/AGENTS.md b/src/components/blog/AGENTS.md new file mode 100644 index 0000000..10994a4 --- /dev/null +++ b/src/components/blog/AGENTS.md @@ -0,0 +1,32 @@ + + + +# blog + +## Purpose +Components for rendering Writing (the `writing` collection) — post mastheads, list previews, table of contents, and webmentions. (Folder is named `blog` for historical reasons; it serves the `/writing` section.) + +## Key Files +| File | Description | +|------|-------------| +| `Masthead.astro` | Post header: cover image, title, date, reading time, updated badge, and `ExperienceTags` (linked experience chips — replaces the old tag links). | +| `PostPreview.astro` | One-line writing entry (date + linked title) used in lists; links to `/writing/`. | +| `TOC.astro` / `TOCHeading.astro` | Table of contents built from rendered headings. | + +## Subdirectories +| Directory | Purpose | +|-----------|---------| +| `webmentions/` | Webmention display components (see `webmentions/AGENTS.md`). | + +## For AI Agents + +### Working In This Directory +- These consume `CollectionEntry<"writing">`. +- `Masthead` renders `data.experiences` via `ExperienceTags`; it no longer links freeform `tags` to a `/tags` route (that route was removed). + +## Dependencies + +### Internal +- `@/components/ExperienceTags.astro`, `@/components/FormattedDate.astro`, `@/utils/generateToc`. + + diff --git a/src/components/blog/Masthead.astro b/src/components/blog/Masthead.astro new file mode 100644 index 0000000..1dcc881 --- /dev/null +++ b/src/components/blog/Masthead.astro @@ -0,0 +1,62 @@ +--- +import { Image } from "astro:assets"; +import type { CollectionEntry } from "astro:content"; +import ExperienceTags from "@/components/ExperienceTags.astro"; +import FormattedDate from "@/components/FormattedDate.astro"; +import TagList from "@/components/TagList.astro"; + +interface Props { + content: CollectionEntry<"writing">; + readingTime: string; +} + +const { + content: { data }, + readingTime, +} = Astro.props; + +const dateTimeOptions: Intl.DateTimeFormatOptions = { + month: "long", +}; +--- + +{ + data.coverImage && ( +
+ {data.coverImage.alt} +
+ ) +} +{data.draft ? (Draft) : null} +

+ {data.title} +

+
+

+ /{" "} + {readingTime} +

+ { + data.updatedDate && ( + + Updated: + + + ) + } +
+{ + !!data.experiences?.length && ( +
+ +
+ ) +} + diff --git a/src/components/blog/PostPreview.astro b/src/components/blog/PostPreview.astro new file mode 100644 index 0000000..5eea64f --- /dev/null +++ b/src/components/blog/PostPreview.astro @@ -0,0 +1,35 @@ +--- +import type { CollectionEntry } from "astro:content"; +import type { HTMLTag, Polymorphic } from "astro/types"; +import FormattedDate from "@/components/FormattedDate.astro"; + +type Props = Polymorphic<{ as: Tag }> & { + post: CollectionEntry<"writing">; + withDesc?: boolean; +}; + +const { as: Tag = "div", post, withDesc = false } = Astro.props; +const { externalUrl, publisher } = post.data; +const href = externalUrl ?? `/writing/${post.id}/`; +--- + + + + {post.data.draft && (Draft) } + + {post.data.title} + + { + externalUrl && ( + + {" ↗ "} + {publisher ?? "External"} + + ) + } + +{withDesc && {post.data.description}} diff --git a/src/components/blog/TOC.astro b/src/components/blog/TOC.astro new file mode 100644 index 0000000..2a45124 --- /dev/null +++ b/src/components/blog/TOC.astro @@ -0,0 +1,22 @@ +--- +import type { MarkdownHeading } from "astro"; +import { generateToc } from "@/utils/generateToc"; +import TOCHeading from "./TOCHeading.astro"; + +interface Props { + headings: MarkdownHeading[]; +} + +const { headings } = Astro.props; + +const toc = generateToc(headings); +--- + +
+ Table of Contents + +
diff --git a/src/components/blog/TOCHeading.astro b/src/components/blog/TOCHeading.astro new file mode 100644 index 0000000..b9dd486 --- /dev/null +++ b/src/components/blog/TOCHeading.astro @@ -0,0 +1,27 @@ +--- +import type { TocItem } from "@/utils/generateToc"; + +interface Props { + heading: TocItem; +} + +const { + heading: { children, depth, slug, text }, +} = Astro.props; +--- + +
  • 2 ? "ms-2" : ""}`}> + {text} + { + !!children.length && ( +
      + {children.map((subheading) => ( + + ))} +
    + ) + } +
  • diff --git a/src/components/blog/webmentions/AGENTS.md b/src/components/blog/webmentions/AGENTS.md new file mode 100644 index 0000000..346dec3 --- /dev/null +++ b/src/components/blog/webmentions/AGENTS.md @@ -0,0 +1,26 @@ + + + +# webmentions + +## Purpose +Display [webmentions](https://webmention.io/) (likes and comments) fetched for a post. + +## Key Files +| File | Description | +|------|-------------| +| `index.astro` | Entry component; gates rendering on the `WEBMENTION_URL` env var and fetches mentions. | +| `Likes.astro` | Renders "like" webmentions. | +| `Comments.astro` | Renders comment/reply webmentions. | + +## For AI Agents + +### Working In This Directory +- Webmentions are optional and only active when the `WEBMENTION_*` client env vars are set (see `astro:env/client` usage in `BaseHead.astro`). Fetch/caching logic lives in `@/utils/webmentions.ts`. + +## Dependencies + +### Internal +- `@/utils/webmentions.ts`, `@/types` (webmention types). + + diff --git a/src/components/blog/webmentions/Comments.astro b/src/components/blog/webmentions/Comments.astro new file mode 100644 index 0000000..838c562 --- /dev/null +++ b/src/components/blog/webmentions/Comments.astro @@ -0,0 +1,87 @@ +--- +import { Image } from "astro:assets"; +import { Icon } from "astro-icon/components"; +import type { WebmentionsChildren } from "@/types"; + +interface Props { + mentions: WebmentionsChildren[]; +} + +const { mentions } = Astro.props; + +const validComments = ["mention-of", "in-reply-to"]; + +const comments = mentions.filter( + (mention) => validComments.includes(mention["wm-property"]) && mention.content?.text, +); +--- + +{ + !!comments.length && ( +
    +

    + {comments.length} Mention{comments.length > 1 ? "s" : ""} +

    +
      + {comments.map((mention) => ( +
    • + {mention.author?.photo && mention.author.photo !== "" ? ( + mention.author.url && mention.author.url !== "" ? ( + + {mention.author?.name} + + ) : ( + {mention.author?.name} + ) + ) : null} +
      +
      +

      + {mention.author?.name} +

      + + + +
      +

      + {mention.content?.text} +

      +
      +
    • + ))} +
    +
    + ) +} diff --git a/src/components/blog/webmentions/Likes.astro b/src/components/blog/webmentions/Likes.astro new file mode 100644 index 0000000..7862c43 --- /dev/null +++ b/src/components/blog/webmentions/Likes.astro @@ -0,0 +1,52 @@ +--- +import { Image } from "astro:assets"; +import type { WebmentionsChildren } from "@/types"; + +interface Props { + mentions: WebmentionsChildren[]; +} + +const { mentions } = Astro.props; +const MAX_LIKES = 10; + +const likes = mentions.filter((mention) => mention["wm-property"] === "like-of"); +const likesToShow = likes + .filter((like) => like.author?.photo && like.author.photo !== "") + .slice(0, MAX_LIKES); +--- + +{ + !!likes.length && ( +
    +

    + {likes.length} + {likes.length > 1 ? " People" : " Person"} liked this +

    + {!!likesToShow.length && ( +
      + {likesToShow.map((like) => ( +
    • + + + {like.author!.name} + + +
    • + ))} +
    + )} +
    + ) +} diff --git a/src/components/blog/webmentions/index.astro b/src/components/blog/webmentions/index.astro new file mode 100644 index 0000000..232b4f3 --- /dev/null +++ b/src/components/blog/webmentions/index.astro @@ -0,0 +1,23 @@ +--- +import { getWebmentionsForUrl } from "@/utils/webmentions"; +import Comments from "./Comments.astro"; +import Likes from "./Likes.astro"; + +const url = new URL(Astro.url.pathname, Astro.site); + +const webMentions = await getWebmentionsForUrl(`${url}`); + +// Return if no webmentions +if (!webMentions.length) return; +--- + +
    +

    Webmentions for this post

    +
    + + +
    +

    + Responses powered by{" "} + Webmentions +

    diff --git a/src/components/layout/AGENTS.md b/src/components/layout/AGENTS.md new file mode 100644 index 0000000..843ed21 --- /dev/null +++ b/src/components/layout/AGENTS.md @@ -0,0 +1,30 @@ + + + +# layout + +## Purpose +Site-wide chrome rendered on every page by `src/layouts/Base.astro`. + +## Key Files +| File | Description | +|------|-------------| +| `Header.astro` | Top nav: avatar logo, site title, `menuLinks` nav, search, theme toggle, mobile menu button. | +| `Footer.astro` | Three centered rows — nav links, social icon row (from `socialLinks`), copyright — with a top border. | + +## For AI Agents + +### Working In This Directory +- **Header logo positioning:** the avatar is `position: absolute` and overflows left into the page margin. It is centered on the **header** block (not just the title) — this only works because the logo's containing block is the `
    ` (which is `relative`). Do **not** put a `filter`/`transform`/`position: relative` on the wrapping ``, or it becomes the containing block and the logo mis-centers. The grayscale/hover effect therefore lives on the ``, not the ``. +- Logo geometry was tuned against the header center; if you change the logo size (`sm:size-36`) or header padding (`sm:ps-28`), re-verify vertical centering and the left overflow (`sm:-start-[48px]`). +- `menuLinks` and `socialLinks` come from `src/site.config.ts` — edit there, not here. + +### Testing Requirements +- Verify visually at desktop and mobile widths (the logo is inline on mobile, absolute/overflowing on `sm+`). Chrome DevTools MCP works for measuring element rects. + +## Dependencies + +### Internal +- `@/site.config` (`menuLinks`, `socialLinks`, `siteConfig`), `astro-icon` for social/RSS icons. + + diff --git a/src/components/layout/Footer.astro b/src/components/layout/Footer.astro new file mode 100644 index 0000000..29445c9 --- /dev/null +++ b/src/components/layout/Footer.astro @@ -0,0 +1,45 @@ +--- +import { Icon } from "astro-icon/components"; +import { menuLinks, siteConfig, socialLinks } from "@/site.config"; + +const year = new Date().getFullYear(); +--- + + diff --git a/src/components/layout/Header.astro b/src/components/layout/Header.astro new file mode 100644 index 0000000..d849cd3 --- /dev/null +++ b/src/components/layout/Header.astro @@ -0,0 +1,104 @@ +--- +import Search from "@/components/Search.astro"; +import ThemeToggle from "@/components/ThemeToggle.astro"; +import { menuLinks } from "@/site.config"; +import { siteConfig } from "../../site.config"; +--- + +
    +
    + + {`${siteConfig.title} + {siteConfig.title} + + +
    + + + + + +
    + + diff --git a/src/components/project/AGENTS.md b/src/components/project/AGENTS.md new file mode 100644 index 0000000..f7b1a3a --- /dev/null +++ b/src/components/project/AGENTS.md @@ -0,0 +1,25 @@ + + + +# project + +## Purpose +Rendering for the `projects` collection. + +## Key Files +| File | Description | +|------|-------------| +| `Project.astro` | Polymorphic component for a project. In `isPreview` mode it renders a compact card linking to `/projects/`; in full mode it shows the title, date, `link`/`repo` buttons, rendered body, and `ExperienceTags`. | + +## For AI Agents + +### Working In This Directory +- Prop is `project: CollectionEntry<"projects">` (note: the source dir is `src/content/projects`). +- Used by `src/pages/projects/[...page].astro` (preview list), `[...slug].astro` (full), and the homepage. + +## Dependencies + +### Internal +- `@/components/ExperienceTags.astro`, `@/components/FormattedDate.astro`. + + diff --git a/src/components/project/Project.astro b/src/components/project/Project.astro new file mode 100644 index 0000000..25d23fd --- /dev/null +++ b/src/components/project/Project.astro @@ -0,0 +1,67 @@ +--- +import { type CollectionEntry, getEntries, render } from "astro:content"; +import type { HTMLTag, Polymorphic } from "astro/types"; +import TagList from "@/components/TagList.astro"; + +type Props = Polymorphic<{ as: Tag }> & { + project: CollectionEntry<"projects">; + isPreview?: boolean | undefined; +}; + +const { as: Tag = "div", project, isPreview = false } = Astro.props; +const { Content } = await render(project); +const { title, startDate, endDate, link, repo, tags, experiences } = project.data; +const range = `${startDate.getFullYear()} – ${endDate ? endDate.getFullYear() : "Present"}`; +const expEntries = experiences.length ? await getEntries(experiences) : []; +--- + +
    + + { + isPreview ? ( + + {title} + + ) : ( + <>{title} + ) + } + +

    + {range}{expEntries.length > 0 && ( + ({expEntries.map((exp, i) => ( + {i > 0 && ", "}{exp.data.title} + ))}) + )} +

    + { + !isPreview && (link || repo) && ( +
    + {link && ( + + Visit → + + )} + {repo && ( + + Source → + + )} +
    + ) + } +
    + +
    + { + !isPreview && tags.length > 0 && ( + + ) + } +
    diff --git a/src/content.config.ts b/src/content.config.ts new file mode 100644 index 0000000..8d3fd62 --- /dev/null +++ b/src/content.config.ts @@ -0,0 +1,105 @@ +import { defineCollection, reference } from "astro:content"; +import { glob } from "astro/loaders"; +import { z } from "astro/zod"; + +function removeDupsAndLowerCase(array: string[]) { + return [...new Set(array.map((str) => str.toLowerCase()))]; +} + +const titleSchema = z.string().max(60); + +const baseSchema = z.object({ + title: titleSchema, +}); + +// Experiences double as a taxonomy: projects, writing & speaking reference them +// by id so each experience page can render everything filed under it. +const experienceRefs = z.array(reference("experience")).default([]); + +// /writing section. +const writing = defineCollection({ + loader: glob({ base: "./src/content/writing", pattern: "**/*.{md,mdx}" }), + schema: ({ image }) => + baseSchema.extend({ + description: z.string(), + coverImage: z + .object({ + alt: z.string(), + src: image(), + }) + .optional(), + draft: z.boolean().default(false), + ogImage: z.string().optional(), + tags: z.array(z.string()).default([]).transform(removeDupsAndLowerCase), + experiences: experienceRefs, + // External articles (PESOS): link out to the publisher instead of a local page. + externalUrl: z.url().optional(), + publisher: z.string().optional(), // e.g. "Fast Company", "Medium" + publishDate: z + .string() + .or(z.date()) + .transform((val) => new Date(val)), + updatedDate: z + .string() + .optional() + .transform((str) => (str ? new Date(str) : undefined)), + pinned: z.boolean().default(false), + }), +}); + +// /projects section. +const projects = defineCollection({ + loader: glob({ base: "./src/content/projects", pattern: "**/*.{md,mdx}" }), + schema: ({ image }) => + baseSchema.extend({ + description: z.string().optional(), + coverImage: z + .object({ + alt: z.string(), + src: image(), + }) + .optional(), + link: z.url().optional(), + repo: z.url().optional(), + // Skills double as tags for cross-project categorization. + tags: z.array(z.string()).default([]).transform(removeDupsAndLowerCase), + experiences: experienceRefs, + startDate: z.coerce.date(), + endDate: z.coerce.date().optional(), // omit for ongoing projects + }), +}); + +// Experience = resume entries that also act as the taxonomy for the site. +const experience = defineCollection({ + loader: glob({ base: "./src/content/experience", pattern: "**/*.{md,mdx}" }), + schema: baseSchema.extend({ + organization: z.string(), + role: z.string().optional(), + location: z.string().optional(), + url: z.url().optional(), + description: z.string().optional(), + // Skills double as site-wide tags (see /tags). + tags: z.array(z.string()).default([]).transform(removeDupsAndLowerCase), + startDate: z.coerce.date(), + endDate: z.coerce.date().optional(), // omit for current/ongoing + }), +}); + +// Speaking = talks/presentations rendered with an embedded YouTube player. +const speaking = defineCollection({ + loader: glob({ base: "./src/content/speaking", pattern: "**/*.{md,mdx}" }), + schema: baseSchema.extend({ + description: z.string(), + youtubeId: z.string(), // YouTube video ID + event: z.string().optional(), + draft: z.boolean().default(false), + tags: z.array(z.string()).default([]).transform(removeDupsAndLowerCase), + experiences: experienceRefs, + publishDate: z + .string() + .or(z.date()) + .transform((val) => new Date(val)), + }), +}); + +export const collections = { writing, projects, experience, speaking }; diff --git a/src/content/AGENTS.md b/src/content/AGENTS.md new file mode 100644 index 0000000..fdf86e1 --- /dev/null +++ b/src/content/AGENTS.md @@ -0,0 +1,27 @@ + + + +# content + +## Purpose +Markdown/MDX content for the four collections. Each subdirectory is a collection; the filename is the entry id/slug. Schemas live in `src/content.config.ts`. + +## Subdirectories +| Directory | Collection key | Route | Notes | +|-----------|----------------|-------|-------| +| `projects/` | `projects` | `/projects` | Things built. Fields: `title`, `startDate`, `endDate?` (omit = ongoing), `description?`, `link?`, `repo?`, `tags?` (skills; browsable site-wide at `/skills/`), `experiences?`. Sorted by `endDate` desc, ongoing first. | +| `writing/` | `writing` | `/writing` | Posts. Fields incl. `description`, `coverImage?`, `ogImage?`, `tags?`, `experiences?`, `draft`, `pinned`, `externalUrl?`/`publisher?` (PESOS — entry links out to the publisher, no local detail page). | +| `experience/` | `experience` | `/experience` | Resume entries + taxonomy. Fields: `title`, `organization`, `startDate`, `endDate?`, `role?`, `location?`, `url?`, `description?`. | +| `speaking/` | `speaking` | `/speaking` | Talks. Fields: `title`, `description`, `publishDate`, `youtubeId` (YouTube video ID), `event?`, `tags?`, `experiences?`, `draft`. | + +## For AI Agents + +### Working In This Directory +- **Experience taxonomy:** entries in `projects`, `writing`, and `speaking` reference experiences via an `experiences: []` array (validated by `reference("experience")`). The id is the experience filename without extension (e.g. `experiences: ["sonr"]` → `experience/sonr.md`). Adding a ref to a non-existent experience fails the build. +- `draft: true` (writing/speaking) hides an entry from production builds. +- After editing frontmatter, `pnpm build` validates it against the schema. + +### Common Patterns +- Some `writing/` samples (`markdown-elements`, `testing/`) are leftover theme demos showcasing markdown rendering — safe to delete once real content exists. + + diff --git a/src/content/experience/britetwist.md b/src/content/experience/britetwist.md new file mode 100644 index 0000000..9a9099d --- /dev/null +++ b/src/content/experience/britetwist.md @@ -0,0 +1,15 @@ +--- +title: "Britetwist" +organization: "Britetwist" +role: "iOS Developer" +location: "Ashburn, VA, USA" +tags: ["Executive Leadership", "iOS Development", "Game Development", "C#", "Unity", "Product Management", "User Interface Design"] +startDate: "2016-07-01" +endDate: "2019-05-01" +description: "Founded an independent mobile app studio, driving end-to-end product development—from ideation and UI/UX design to full-stack programming—for three successful applications." +--- + +- Founded an independent mobile app studio, driving end-to-end product development—from ideation and UI/UX design to full-stack programming—for three successful applications. +- Scaled the company's portfolio to over 1 million total downloads, encompassing a peer-to-peer volunteer network (VolunTree) and highly engaging mobile games (Space Bolt, Yuē). +- Achieved peak user engagement metrics, including 15,000 Daily Active Users (DAU) for Space Bolt and 1,250 Monthly Active Users (MAU) for VolunTree. +- Fostered strong developer community engagement by authoring technical architecture and game development articles that accumulated over 40,000 reads. diff --git a/src/content/experience/hobsons.md b/src/content/experience/hobsons.md new file mode 100644 index 0000000..c67f054 --- /dev/null +++ b/src/content/experience/hobsons.md @@ -0,0 +1,14 @@ +--- +title: "Hobsons" +organization: "Hobsons" +role: "Software Engineering Intern" +location: "Arlington, VA, USA" +tags: ["Java", "AngularJS", "Spring Boot", "REST API", "Backend Development", "Algorithms"] +startDate: "2018-05-01" +endDate: "2018-08-01" +description: "Architected a robust backend monitoring system using Java and the Spring Boot framework to track data synchronization between CommonApp documents and the Naviance platform." +--- + +- Architected a robust backend monitoring system using Java and the Spring Boot framework to track data synchronization between CommonApp documents and the Naviance platform. +- Built an internal frontend dashboard using JavaScript and AngularJS to seamlessly consume, display, and filter REST API JSON responses. +- Empowered the sales team to independently troubleshoot and answer customer QA inquiries by creating self-serve tools, drastically reducing response times from several days to just a few minutes. diff --git a/src/content/experience/leidos.md b/src/content/experience/leidos.md new file mode 100644 index 0000000..8c7dbc8 --- /dev/null +++ b/src/content/experience/leidos.md @@ -0,0 +1,14 @@ +--- +title: "Leidos" +organization: "Leidos" +role: "Mobile App Developer" +location: "Herndon, VA, USA" +tags: ["Flutter", "Dart", "Cross-Platform Development", "Mobile App Development", "Agile Methodologies", "Mobile Security"] +startDate: "2020-08-01" +endDate: "2021-05-01" +description: "Engineered and deployed features for the official Customs and Border Protection (CBP) Mobile Passport application, a high-visibility tool utilized by millions of travelers to expedite entry into the United States." +--- + +- Engineered and deployed features for the official Customs and Border Protection (CBP) Mobile Passport application, a high-visibility tool utilized by millions of travelers to expedite entry into the United States. +- Leveraged the Flutter framework and Dart to build and maintain a seamless, high-performance cross-platform experience for both iOS and Android users. +- Collaborated within a highly regulated, hybrid agile environment to ensure the application met strict federal security, privacy, and accessibility standards. diff --git a/src/content/experience/sonr.md b/src/content/experience/sonr.md new file mode 100644 index 0000000..63549e6 --- /dev/null +++ b/src/content/experience/sonr.md @@ -0,0 +1,19 @@ +--- +title: "Sonr" +organization: "Sonr" +role: "Co-Founder / CEO" +location: "New York, USA" +url: "https://sonr.io" +tags: ["Executive Leadership", "Startup Fundraising", "Cosmos SDK", "React Native", "Kubernetes", "Docker", "Google Cloud", "Terraform", "Ansible", "Protocol Buffers", "Decentralized Identifiers (DIDs)", "WebAuthn", "WebAssembly (WASM)", "IPFS", "Matrix Protocol", "Technology Strategy", "DevOps", "Linux Administration"] +startDate: "2021-03-01" +description: "Founded the startup and successfully led executive fundraising efforts, securing $4.7M in capital to build a peer-to-peer identity and asset management system leveraging decentralized identifiers (DIDs), WebAuthn, and IPFS." +--- + +> Founder building Decentralized Identity | W3C Working Group Member for DIDs, WebAuthn, and WASM | 6000+ Github Contributions 2021-2023 + +- Founded the startup and successfully led executive fundraising efforts, securing $4.7M in capital to build a peer-to-peer identity and asset management system leveraging decentralized identifiers (DIDs), WebAuthn, and IPFS. +- Directed technical strategy and led a cross-functional engineering team in the co-development of a React Native mobile chat application powered by the decentralized Matrix protocol. +- Architected a custom Layer 1 blockchain using the Cosmos SDK, optimizing data serialization and network communication with Protobufs. +- Orchestrated advanced DevOps and server deployments across Google Cloud (GCP), managing complex network infrastructure using Kubernetes (K8s), Docker, Terraform, Ansible, and comprehensive Linux administration. +- Drove massive open-source community growth, achieving the 4th highest global GitHub contributions (Oct 2021 – Oct 2022) for the platform's MVP, and successfully onboarding over 120 first-time blockchain developers to the Motor SDKs during DevNet. +- Actively shaped global web standards as a W3C Working Group Member for DIDs, WebAuthn, and WebAssembly (WASM). diff --git a/src/content/experience/unissant.md b/src/content/experience/unissant.md new file mode 100644 index 0000000..3de36ae --- /dev/null +++ b/src/content/experience/unissant.md @@ -0,0 +1,16 @@ +--- +title: "Unissant" +organization: "Unissant" +role: "Data Science Intern" +location: "Herndon, VA, USA" +tags: ["Machine Learning", "Data Science", "AWS Lambda", "React.js", "AWS SageMaker", "Python", "Serverless Computing"] +startDate: "2019-05-01" +endDate: "2019-08-01" +description: "Engineered and deployed a machine learning fraud detection model utilizing AWS SageMaker to identify irregular patterns in healthcare data." +--- + +- Engineered and deployed a machine learning fraud detection model utilizing AWS SageMaker to identify irregular patterns in healthcare data. + +- Developed a custom, client-facing web application using React, integrated with serverless AWS Lambda functions to interact with the predictive model in real-time. + +- Delivered a successful proof-of-concept (POC) to a consulting client, demonstrating the firm's technical capabilities in advanced data management and automated fraud prevention. diff --git a/src/content/projects/appchain.md b/src/content/projects/appchain.md new file mode 100644 index 0000000..0272d48 --- /dev/null +++ b/src/content/projects/appchain.md @@ -0,0 +1,14 @@ +--- +title: "Layer-1 Blockchain & Decentralized Identity Ecosystem" +startDate: "2022-01-01" +description: "Engineered a revolutionary \"Gasless Onboarding\" pipeline by writing custom Cosmos AnteHandlers to intercept and validate WebAuthn (Passkeys/Biometrics) credentials directly at the mempool level, completely abstracting away seed phrases and transaction fees for new users." +repo: "https://github.com/sonr-io/sonr" +tags: ["Go", "Kubernetes", "WebAuthn", "Decentralized Identifiers (DIDs)", "IPFS", "Distributed Networking", "Blockchain Architecture"] +experiences: ["sonr"] +--- + +- Engineered a revolutionary "Gasless Onboarding" pipeline by writing custom Cosmos AnteHandlers to intercept and validate WebAuthn (Passkeys/Biometrics) credentials directly at the mempool level, completely abstracting away seed phrases and transaction fees for new users. +- Designed a capability-based authorization system integrating User-Controlled Authorization Networks (UCAN) and Decentralized Web Nodes (DWN) to provide granular, time-bound, and delegatable access to IPFS-backed encrypted data vaults. +- Bridged Web3 ecosystems by implementing full EVM compatibility alongside CosmWasm smart contracts, supporting multi-chain token mappings and cross-chain operations via IBC protocols. +- Built enterprise-grade DevOps infrastructure leveraging Kubernetes (Starship), Docker, and Devbox to orchestrate multi-node testnets and local development environments, utilizing GoReleaser for automated, multi-architecture CI/CD pipelines. +- Integrated automated legal compliance tooling directly into the blockchain CLI, generating cryptographic proofs and automated filings for Wyoming DAO LC amendments. diff --git a/src/content/projects/beam-chat.md b/src/content/projects/beam-chat.md new file mode 100644 index 0000000..e125fe3 --- /dev/null +++ b/src/content/projects/beam-chat.md @@ -0,0 +1,14 @@ +--- +title: "Beam — E2EE Decentralized Messaging" +startDate: "2022-08-01" +endDate: "2025-08-01" +description: "Led the architecture and hands-on development of Beam, a cross-platform end-to-end encrypted (E2EE) mobile messaging application built in React Native, directing a 6-engineer cross-functional team across mobile, infrastructure, and product." +link: "https://www.youtube.com/watch?v=plaYwlGl4NM" +tags: ["React Native", "Matrix Protocol", "WebAuthn", "Decentralized Identifiers (DIDs)", "Cryptography"] +experiences: ["sonr"] +--- + +- Led the architecture and hands-on development of Beam, a cross-platform end-to-end encrypted (E2EE) mobile messaging application built in React Native, directing a 6-engineer cross-functional team across mobile, infrastructure, and product. +- Architected and managed a self-hosted Matrix homeserver, customizing the decentralized Matrix protocol to deliver end-to-end encrypted (E2EE), censorship-resistant messaging with self-custodied message history. +- Engineered a passkey-based authentication layer as a drop-in replacement for the Matrix authorization server, swapping centralized email/phone login for WebAuthn passkey wallets and Sonr Decentralized Identifiers (DIDs) to enable passwordless, self-sovereign onboarding and cryptographic key exchange. +- Abstracted decentralized routing and cryptographic key management behind a Web2-grade chat UX, guiding frontend and product teams to deliver a frictionless messaging experience over fully decentralized identity and transport infrastructure. diff --git a/src/content/projects/client-sdk.md b/src/content/projects/client-sdk.md new file mode 100644 index 0000000..a081270 --- /dev/null +++ b/src/content/projects/client-sdk.md @@ -0,0 +1,14 @@ +--- +title: "Client SDK & Motr Cryptographic Enclave" +startDate: "2022-06-01" +endDate: "2023-08-01" +description: "Architected \"Motr,\" a WebAssembly (WASM) cryptographic enclave compiled from Go, designed to run entirely in client-side Web Workers. This sandboxed environment securely handles Multi-Party Computation (MPC), data signing, and UCAN token generation without ever exposing private keys to the main application thread." +link: "https://pub.dev/packages/motor_flutter" +tags: ["API Design", "React.js", "Flutter", "WebAuthn", "Cryptography", "WebAssembly (WASM)", "TypeScript", "Developer Relations"] +experiences: ["sonr"] +--- + +- Architected "Motr," a WebAssembly (WASM) cryptographic enclave compiled from Go, designed to run entirely in client-side Web Workers. This sandboxed environment securely handles Multi-Party Computation (MPC), data signing, and UCAN token generation without ever exposing private keys to the main application thread. +- Built an edge-native orchestration layer using Cloudflare Workers, utilizing Durable Objects, KV namespaces, and Service Bindings to manage persistent cross-platform sessions, smart routing, and state synchronization across the decentralized network. +- Pioneered a "Web2-to-Web3" authentication bridge by integrating WebAuthn (Passkeys/Biometrics) and standard OAuth 2.0 flows directly into the SDK, allowing developers to offer passwordless, seed-phrase-free onboarding to their users. +- Implemented account-isolated client persistence leveraging Dexie.js (IndexedDB) to create a robust offline caching layer for cryptographic states, delegated capabilities, and decentralized web node (DWN) records. diff --git a/src/content/projects/cryptography-library.md b/src/content/projects/cryptography-library.md new file mode 100644 index 0000000..3459557 --- /dev/null +++ b/src/content/projects/cryptography-library.md @@ -0,0 +1,14 @@ +--- +title: "Core Cryptography Library (ZKP & MPC Infrastructure)" +startDate: "2022-01-01" +endDate: "2026-02-01" +description: "Implemented advanced Zero-Knowledge Proof (ZKP) systems, including Bulletproofs (Range Proofs and Inner Product Proofs) and cryptographic accumulators, leveraging the Fiat-Shamir heuristic for efficient, non-interactive verification." +repo: "https://github.com/sonr-io/crypto" +tags: ["Go", "Security Testing", "Quantum Computing", "Cryptography"] +experiences: ["sonr"] +--- + +- Implemented advanced Zero-Knowledge Proof (ZKP) systems, including Bulletproofs (Range Proofs and Inner Product Proofs) and cryptographic accumulators, leveraging the Fiat-Shamir heuristic for efficient, non-interactive verification. +- Designed Multi-Party Computation (MPC) and Distributed Key Generation (DKG) protocols, enabling decentralized infrastructure through Threshold ECDSA/Ed25519, FROST, and Gennaro signature schemes without single points of failure. +- Engineered highly secure, constant-time elliptic curve arithmetic across multiple standards (secp256k1, Ed25519, BLS12-381, NIST P-Curves), strictly enforcing safeguards against side-channel timing attacks, signature malleability, and nonce reuse. +- Hardened system security by integrating AES-GCM authenticated encryption and Argon2id memory-hard key derivation, backed by an exhaustive suite of automated security tests, race-condition checks, and performance benchmarks. diff --git a/src/content/projects/decentralized-airdrop.md b/src/content/projects/decentralized-airdrop.md new file mode 100644 index 0000000..695b4bb --- /dev/null +++ b/src/content/projects/decentralized-airdrop.md @@ -0,0 +1,15 @@ +--- +title: "Decentralized File-Sharing Platform" +startDate: "2020-10-01" +endDate: "2021-12-01" +description: "Engineered deep native OS integrations by writing custom iOS Share Extensions (Swift) and Android Intent filters, allowing users to seamlessly share photos, videos, and files directly from the OS-level share sheet into the decentralized libp2p network." +link: "https://www.youtube.com/watch?v=HeBcg4P3Ipw" +tags: ["Flutter", "Dart", "Swift", "Kotlin", "Java", "FFI (Foreign Function Interface)", "Decentralized Identifiers (DIDs)", "SQLite", "Continuous Integration and Deployment (CI/CD)", "Libp2p"] +experiences: ["sonr"] +--- + +- Engineered deep native OS integrations by writing custom iOS Share Extensions (Swift) and Android Intent filters, allowing users to seamlessly share photos, videos, and files directly from the OS-level share sheet into the decentralized libp2p network. +- Integrated a custom Go-based networking core using Gomobile, creating strict type-conversion bindings to translate low-level libp2p data streams, cryptographic keypairs, and Namebase decentralized routing into Dart. +- Designed a reactive, high-performance UI/UX utilizing the GetX framework for state management, combined with complex Lottie and Rive animations to provide intuitive, real-time feedback during peer discovery and file transfers. +- Built a robust local caching layer using Moor (SQLite) to securely store decentralized identity profiles, transfer history, and media metadata offline. +- Automated the entire CI/CD pipeline by configuring custom Makefiles, Fastlane lanes, and GitHub Actions to fetch core framework submodules, manage code-generation (build_runner), and seamlessly deploy App Bundles and iOS Archives to the Play Store and App Store TestFlight. diff --git a/src/content/projects/ecosystem-devrel.md b/src/content/projects/ecosystem-devrel.md new file mode 100644 index 0000000..7f8d7c9 --- /dev/null +++ b/src/content/projects/ecosystem-devrel.md @@ -0,0 +1,15 @@ +--- +title: "Web3 Developer Ecosystem & DevNet Launch" +startDate: "2022-01-01" +endDate: "2022-06-01" +description: "Designed and hosted hands-on technical workshops aimed at abstracting away the friction of Web3 development, successfully onboarding dozens of first-time blockchain developers to build decentralized applications (dApps) without requiring deep cryptographic expertise." +link: "https://www.youtube.com/watch?v=zy6Xkh9Mvcs" +tags: ["Developer Relations", "Technical Workshops", "Web3 Ecosystem", "Public Speaking", "API Design"] +experiences: ["sonr"] +--- + +Spearheaded the launch of the Sonr DevNet at premier collegiate hackathons, including PennApps XXII and UC Berkeley’s CalHacks, to drive grassroots adoption of decentralized identity protocols. + +- Designed and hosted hands-on technical workshops aimed at abstracting away the friction of Web3 development, successfully onboarding dozens of first-time blockchain developers to build decentralized applications (dApps) without requiring deep cryptographic expertise. +- Provided direct architectural mentorship and SDK support (utilizing the Sonr Motor API and DID modules), enabling developers to seamlessly integrate decentralized identity, secure messaging, and token marketplaces within 48-hour constraints. +- Fostered a thriving early-stage ecosystem that produced highly innovative dApps, including Ptolemy (an on-chain/off-chain validator reputation scoring system), Faiza (a decentralized, token-incentivized health forum), and WalkWithMe (a secure, DID-authenticated peer-to-peer transit network). diff --git a/src/content/projects/floadt.md b/src/content/projects/floadt.md new file mode 100644 index 0000000..ac46088 --- /dev/null +++ b/src/content/projects/floadt.md @@ -0,0 +1,13 @@ +--- +title: "Floadt" +startDate: "2013-05-01" +endDate: "2015-10-01" +description: "Developed a unified social media client designed to aggregate and display Instagram, Facebook, and Twitter feeds within a single, Pinterest-style user interface." +repo: "https://github.com/prdlk/floadt" +tags: ["REST APIs", "User Interface Design", "Social Media Integration", "Network Architecture", "Frontend Development"] +experiences: ["britetwist"] +--- + +- Developed a unified social media client designed to aggregate and display Instagram, Facebook, and Twitter feeds within a single, Pinterest-style user interface. +- Engineered advanced social management features, including cross-platform scheduled publishing, feed filtering to hide specific users, and mutual-follower verification. +- Gained deep, hands-on experience in network architecture and complex REST API integration prior to major platform policy changes that restricted third-party developer access. diff --git a/src/content/projects/likely.md b/src/content/projects/likely.md new file mode 100644 index 0000000..83c8378 --- /dev/null +++ b/src/content/projects/likely.md @@ -0,0 +1,13 @@ +--- +title: "Like.ly" +startDate: "2017-05-01" +endDate: "2017-08-01" +description: "Engineered a predictive analytics tool leveraging TensorFlow and supervised machine learning algorithms to forecast Instagram post engagement and likes." +repo: "https://github.com/prdlk/like.ly" +tags: ["React Native", "Machine Learning", "TensorFlow", "Computer Vision", "Web Scraping"] +--- + +- Engineered a predictive analytics tool leveraging TensorFlow and supervised machine learning algorithms to forecast Instagram post engagement and likes. +- Developed an intuitive user interface by building a cross-platform mobile application and a cohesive messenger bot using React Native. +- Customized a web scraper to systematically aggregate public user data to train the model and optimize prediction accuracy. +- Integrated a Computer Vision API to analyze visual post content and extract key features for the machine learning pipeline. diff --git a/src/content/projects/pool.md b/src/content/projects/pool.md new file mode 100644 index 0000000..af14320 --- /dev/null +++ b/src/content/projects/pool.md @@ -0,0 +1,13 @@ +--- +title: "Pool" +startDate: "2019-08-01" +endDate: "2019-08-01" +description: "Designed and engineered the frontend architecture for an event-sharing mobile application utilizing the Flutter framework and Dart." +repo: "https://github.com/prdlk/pool-tutorial-part-1" +tags: ["Flutter", "Dart", "Cross-Platform Development", "User Interface Design", "Technical Writing"] +--- + +- Designed and engineered the frontend architecture for an event-sharing mobile application utilizing the Flutter framework and Dart. +- Translated complex Dribbble UI/UX designs into fully functional programmatic widget hierarchies, implementing custom TableView cells, dynamic navigation, and animated backgrounds using the simple-animations library. +- Authored and published a comprehensive technical article ("A Deep Dive into Flutter") detailing the application's structural development to educate developers on cross-platform best practices. +- Architected a scalable, modular codebase designed to seamlessly integrate with future Node.js and MongoDB backend implementations. diff --git a/src/content/projects/spacebolt.md b/src/content/projects/spacebolt.md new file mode 100644 index 0000000..f0eae33 --- /dev/null +++ b/src/content/projects/spacebolt.md @@ -0,0 +1,13 @@ +--- +title: "Space Bolt" +startDate: "2016-08-01" +endDate: "2018-07-01" +description: "Designed and developed an endless-runner space game, successfully scaling the player base to 15,000 daily active users and 850,000 total downloads by 2018." +repo: "https://github.com/prdlk/spacebolt" +tags: ["Game Development", "C#", "Social Integration", "API Design", "Microtransactions"] +experiences: ["britetwist"] +--- + +- Designed and developed an endless-runner space game, successfully scaling the player base to 15,000 daily active users and 850,000 total downloads by 2018. +- Engineered a custom API and server-side caching system to generate, retrieve, and distribute shareable gameplay GIFs. +- Integrated Facebook leaderboards to drive user engagement, retention, and global competition among players. diff --git a/src/content/projects/superball.md b/src/content/projects/superball.md new file mode 100644 index 0000000..9646cdc --- /dev/null +++ b/src/content/projects/superball.md @@ -0,0 +1,13 @@ +--- +title: "Superball" +startDate: "2011-12-01" +endDate: "2012-04-01" +description: "Developed and launched a puzzle-based casual game for iOS using the Cocos2D framework and Objective-C at age 12." +link: "https://vimeo.com/172015296" +tags: ["Game Development", "Objective-C", "Cocos2d"] +experiences: ["britetwist"] +--- + +- Developed and launched a puzzle-based casual game for iOS using the Cocos2D framework and Objective-C at age 12. +- Navigated the constraints of early iOS development by successfully managing memory allocation manually, prior to the introduction of built-in automated memory management. +- Designed the complete game loop, mechanics, and user interface tailored specifically for the mobile casual gaming market. diff --git a/src/content/projects/swiper-no-mauling.md b/src/content/projects/swiper-no-mauling.md new file mode 100644 index 0000000..118bed4 --- /dev/null +++ b/src/content/projects/swiper-no-mauling.md @@ -0,0 +1,13 @@ +--- +title: "Swiper No Mauling" +startDate: "2019-02-01" +endDate: "2019-02-01" +description: "Engineered a financially viable, AI-powered computer vision application designed to help farmers autonomously monitor livestock and prevent predator attacks." +repo: "https://github.com/prdlk/CV-Livestock-Protection" +tags: ["Python", "TensorFlow", "YOLOv3", "Computer Vision", "Machine Learning", "Twilio API"] +--- + +- Engineered a financially viable, AI-powered computer vision application designed to help farmers autonomously monitor livestock and prevent predator attacks. +- Implemented the YOLOv3 object detection model using Python and TensorFlow to accurately identify predators and assess hostility levels within live camera frames. +- Integrated the Twilio API to build an automated, real-time alert system that immediately dispatches SMS notifications to farmers the moment a threat is detected. +- Designed an architectural roadmap to transition the computationally heavy detection model to a cloud-based infrastructure, with planned expansions for wildlife researchers to track events of interest. diff --git a/src/content/projects/timeboard.md b/src/content/projects/timeboard.md new file mode 100644 index 0000000..8a6b5fd --- /dev/null +++ b/src/content/projects/timeboard.md @@ -0,0 +1,13 @@ +--- +title: "Timebo[AR]d" +startDate: "2017-10-01" +endDate: "2017-10-01" +description: "Collaborated at HackHarvard 2017 to build a native iOS Augmented Reality application designed to capture, store, and share physical whiteboard sessions." +repo: "https://github.com/anthonyanader/timeboARd" +tags: ["Swift", "iOS Development", "ARKit"] +--- + +- Collaborated at HackHarvard 2017 to build a native iOS Augmented Reality application designed to capture, store, and share physical whiteboard sessions. +- Developed a feature mapping 2D whiteboard drawings into navigable, cloud-stored 3D models using just two screen taps. +- Implemented a "time machine" versioning system, enabling users to step through and view saved virtual frames in real-time. +- Provided a scalable solution for teams and students to archive architectural diagrams and course notes for future collaborative editing. diff --git a/src/content/projects/token-economics.md b/src/content/projects/token-economics.md new file mode 100644 index 0000000..a6a5d29 --- /dev/null +++ b/src/content/projects/token-economics.md @@ -0,0 +1,14 @@ +--- +title: "Network Token Economics & Economic Design" +startDate: "2022-02-01" +endDate: "2022-06-01" +description: "Architected the core financial model and token mechanics, defining critical macroeconomic parameters including dynamic token allocation, circulating supply schedules, token velocity, and staking reward emissions to maximize long-term fundamental value." +tags: ["Tokenization", "Economic Modeling", "Game Theory", "Delegated Proof of Stake (DPoS)", "Incentive Structuring", "System Architecture", "Cross-functional Team Leadership", "On-chain Governance"] +experiences: ["sonr"] +--- + +Directed the comprehensive token economic design for the Sonr Network, collaborating directly with 6 PhD economists from Prysm Group to architect a sustainable, highly secure Delegated Proof of Stake (DPoS) ecosystem. +- Architected the core financial model and token mechanics, defining critical macroeconomic parameters including dynamic token allocation, circulating supply schedules, token velocity, and staking reward emissions to maximize long-term fundamental value. +- Designed the on-chain governance and utility flow, establishing robust mechanisms for protocol voting, validator reputation scoring, and decentralized treasury/grant management. Modeled these frameworks using empirical data and opportunity cost analyses against major networks like Uniswap, Compound, and Algorand. +- Aligned protocol incentives with network security, structuring the token to act as a seamless means of payment across Sonr's Highway modules, registries, and buckets, while ensuring validators are properly incentivized to maintain decentralization via Inter-Blockchain Communication (IBC). +- Bridged academic theory and engineering execution, successfully translating complex economic models—such as price shock absorption, lockup vesting, and subsidy schedules—into actionable smart contract and Layer-1 state machine logic. diff --git a/src/content/projects/voluntree.md b/src/content/projects/voluntree.md new file mode 100644 index 0000000..9c34aac --- /dev/null +++ b/src/content/projects/voluntree.md @@ -0,0 +1,15 @@ +--- +title: "VolunTree" +startDate: "2015-11-01" +endDate: "2017-10-01" +description: "Engineered an iOS application to digitize and legitimize volunteer hour tracking for high school students via a peer-to-peer verification system." +repo: "https://github.com/prdlk/voluntree" +tags: ["Objective-C", "iOS Development", "Database Design"] +experiences: ["britetwist"] +--- + +- Engineered an iOS application to digitize and legitimize volunteer hour tracking for high school students via a peer-to-peer verification system. +- Scaled the platform to 1,250 monthly active users and achieved 15,000 total downloads in 2016. +- Integrated social and community features enabling users to follow peers and discover over 15 daily volunteer opportunities. +- Built an organizational portal allowing local non-profits to post updates and connect directly with the student volunteer community. +- Implemented an export feature allowing students to seamlessly transfer verified hours to their schools for academic credit. diff --git a/src/content/projects/yue.md b/src/content/projects/yue.md new file mode 100644 index 0000000..ab484ba --- /dev/null +++ b/src/content/projects/yue.md @@ -0,0 +1,14 @@ +--- +title: "Yue" +startDate: "2018-03-01" +endDate: "2019-02-01" +description: "Developed and published a fully functional, low-poly 3D space adventure game to both iOS and Android platforms in under 26 hours." +repo: "https://github.com/prdlk/yue" +tags: ["C#", "Procedural Generation", "Cross-Platform Development", "Game Design"] +experiences: ["britetwist"] +--- + +- Developed and published a fully functional, low-poly 3D space adventure game to both iOS and Android platforms in under 26 hours. +- Engineered a custom algorithm for procedural generation, ensuring unique 3D planet environments for every individual game session. +- Designed an in-app economy and digital storefront allowing users to exchange earned currency for game assets. +- Authored two technical articles detailing the game's rapid development process, accumulating over 40,000 reads. diff --git a/src/content/speaking/devntell-sonr.md b/src/content/speaking/devntell-sonr.md new file mode 100644 index 0000000..c6dfa74 --- /dev/null +++ b/src/content/speaking/devntell-sonr.md @@ -0,0 +1,11 @@ +--- +title: "Dev & Tell: Exploring Sonr" +description: "A Developer DAO Dev & Tell session exploring Sonr — digital freedom, true ownership, and security on a decentralized network." +youtubeId: wKGDr3GrNeA +event: "Developer DAO — Dev & Tell" +publishDate: "2022-08-26" +tags: ["web3", "identity"] +experiences: ["sonr"] +--- + +A conversation with the Developer DAO community walking through Sonr's approach to digital freedom, true ownership, and security. diff --git a/src/content/speaking/hackfs-2022-workshop.md b/src/content/speaking/hackfs-2022-workshop.md new file mode 100644 index 0000000..082d6f6 --- /dev/null +++ b/src/content/speaking/hackfs-2022-workshop.md @@ -0,0 +1,11 @@ +--- +title: "Building in the Decentralized Web with Sonr" +description: "A HackFS 2022 workshop, co-led with Michael Amoako, on building decentralized apps with Sonr's identity stack." +youtubeId: zy6Xkh9Mvcs +event: "ETHGlobal HackFS 2022" +publishDate: "2022-07-18" +tags: ["web3", "identity", "developer relations"] +experiences: ["sonr"] +--- + +A hands-on workshop, part of ETHGlobal's HackFS 2022, walking developers through building decentralized applications on Sonr — identity, authentication, and data ownership. diff --git a/src/content/speaking/sxsw-2022-panel.md b/src/content/speaking/sxsw-2022-panel.md new file mode 100644 index 0000000..dea3a1e --- /dev/null +++ b/src/content/speaking/sxsw-2022-panel.md @@ -0,0 +1,11 @@ +--- +title: "Sonr Team Q&A Panel — SXSW 2022" +description: "A SXSW 2022 panel where the Sonr team fielded questions from LaunchHouse founder Brett Goldstein at our .snr/ event." +youtubeId: DJm5aZM9FDw +event: "SXSW 2022" +publishDate: "2022-03-24" +tags: ["web3", "identity", "startups"] +experiences: ["sonr"] +--- + +The Sonr team takes questions about the project from LaunchHouse founder Brett Goldstein during our .snr/ event at South by Southwest 2022. diff --git a/src/content/writing/fc-web3-end-of-cybercrime.md b/src/content/writing/fc-web3-end-of-cybercrime.md new file mode 100644 index 0000000..2510ade --- /dev/null +++ b/src/content/writing/fc-web3-end-of-cybercrime.md @@ -0,0 +1,9 @@ +--- +title: "Web3 will spell the end of cybercrime. Here's why" +description: "Sonr's CEO on why the solution to cybercrime isn't two-factor authentication or your mother's maiden name." +publishDate: "2022-10-16" +externalUrl: "https://www.fastcompany.com/90795682/web3-will-spell-the-end-of-cybercrime-heres-why" +publisher: "Fast Company" +tags: ["web3", "cybersecurity", "identity"] +experiences: ["sonr"] +--- diff --git a/src/content/writing/fc-web3-mac-iphone.md b/src/content/writing/fc-web3-mac-iphone.md new file mode 100644 index 0000000..c2f6211 --- /dev/null +++ b/src/content/writing/fc-web3-mac-iphone.md @@ -0,0 +1,9 @@ +--- +title: "What Web3 can learn from the Mac and the iPhone" +description: "The world's most innovative developers are super-enthusiastic about Web3, but everyone else isn't." +publishDate: "2022-11-26" +externalUrl: "https://www.fastcompany.com/90814899/web3-lessons-mac-iphone" +publisher: "Fast Company" +tags: ["web3", "product design"] +experiences: ["sonr"] +--- diff --git a/src/content/writing/li-pivot-startup.md b/src/content/writing/li-pivot-startup.md new file mode 100644 index 0000000..a8a8e1a --- /dev/null +++ b/src/content/writing/li-pivot-startup.md @@ -0,0 +1,9 @@ +--- +title: "Why (and How) to Pivot Your Startup" +description: "A five-step framework for pivoting a startup — drawn from being rejected by Y Combinator and pivoting toward Web3." +publishDate: "2022-09-08" +externalUrl: "https://www.linkedin.com/pulse/why-how-pivot-your-startup-prad-nukala/" +publisher: "LinkedIn" +tags: ["entrepreneurship", "startups"] +experiences: ["sonr"] +--- diff --git a/src/content/writing/li-self-teacher.md b/src/content/writing/li-self-teacher.md new file mode 100644 index 0000000..1d09bd1 --- /dev/null +++ b/src/content/writing/li-self-teacher.md @@ -0,0 +1,9 @@ +--- +title: "3 Simple Steps to Becoming a Masterful Self-Teacher" +description: "A practical methodology for self-directed learning: start broad, prioritize depth by goal, and study thought leaders through books to reach expertise." +publishDate: "2022-09-22" +externalUrl: "https://www.linkedin.com/pulse/3-simple-steps-becoming-masterful-self-teacher-prad-nukala/" +publisher: "LinkedIn" +tags: ["learning", "career"] +experiences: ["sonr"] +--- diff --git a/src/content/writing/li-taking-the-leap.md b/src/content/writing/li-taking-the-leap.md new file mode 100644 index 0000000..0c781c2 --- /dev/null +++ b/src/content/writing/li-taking-the-leap.md @@ -0,0 +1,9 @@ +--- +title: "Taking the Leap: On Quitting, Debt & Entrepreneurship" +description: "From leaving a secure federal job with $15K in savings to raising $5M for Sonr — how strategic risk-taking, relocating, and embracing rejection shaped my path." +publishDate: "2022-10-20" +externalUrl: "https://www.linkedin.com/pulse/taking-leap-what-quitting-my-job-moving-going-debt-taught-prad-nukala/" +publisher: "LinkedIn" +tags: ["entrepreneurship", "startups", "career"] +experiences: ["sonr"] +--- diff --git a/src/content/writing/medium-flutter-deep-dive-part-2.md b/src/content/writing/medium-flutter-deep-dive-part-2.md new file mode 100644 index 0000000..05e3a4c --- /dev/null +++ b/src/content/writing/medium-flutter-deep-dive-part-2.md @@ -0,0 +1,8 @@ +--- +title: "A Deep Dive into Flutter (Part 2)" +description: "Part 2 of building Pool, an event-sharing app in Flutter — profile views, event detail screens, and multi-functional list views with minimal code." +publishDate: "2019-09-15" +externalUrl: "https://medium.com/flutter-community/a-deep-dive-into-flutter-the-22nd-century-frontend-framework-part-2-8964c02706b9" +publisher: "Medium" +tags: ["flutter", "dart"] +--- diff --git a/src/content/writing/medium-flutter-deep-dive.md b/src/content/writing/medium-flutter-deep-dive.md new file mode 100644 index 0000000..b0ac65f --- /dev/null +++ b/src/content/writing/medium-flutter-deep-dive.md @@ -0,0 +1,8 @@ +--- +title: "A Deep Dive into Flutter" +description: "Exploring Flutter for cross-platform development — building a native-performance mobile UI with Dart and Flutter's widget architecture." +publishDate: "2019-08-08" +externalUrl: "https://medium.com/flutter-community/deep-dive-in-flutter-b0b827440a49" +publisher: "Medium" +tags: ["flutter", "dart", "cross-platform development"] +--- diff --git a/src/content/writing/medium-minimum-amazing-product.md b/src/content/writing/medium-minimum-amazing-product.md new file mode 100644 index 0000000..b8c9e67 --- /dev/null +++ b/src/content/writing/medium-minimum-amazing-product.md @@ -0,0 +1,9 @@ +--- +title: "How I Made My Minimum Amazing Product in a Day" +description: "Building a space-defense game in 26 hours with Unity3D — reusing assets and prioritizing simplicity to ship on an ambitious deadline." +publishDate: "2018-03-30" +externalUrl: "https://medium.com/swlh/map-day-84dd2fe11b1a" +publisher: "Medium" +tags: ["game development", "unity"] +experiences: ["britetwist"] +--- diff --git a/src/content/writing/medium-quote-project-complacency.md b/src/content/writing/medium-quote-project-complacency.md new file mode 100644 index 0000000..74aff23 --- /dev/null +++ b/src/content/writing/medium-quote-project-complacency.md @@ -0,0 +1,9 @@ +--- +title: "A Simple Quote to Save Your Project from Complacency" +description: "How a Derek Jeter quote pushed me to refine my mobile game Yuē — improving procedural planet generation, controls, and difficulty balancing." +publishDate: "2019-02-01" +externalUrl: "https://medium.com/swlh/a-simple-quote-to-save-your-project-complacency-65f1cdd9ca23" +publisher: "Medium" +tags: ["game development", "game design"] +experiences: ["britetwist"] +--- diff --git a/src/data/AGENTS.md b/src/data/AGENTS.md new file mode 100644 index 0000000..b95c626 --- /dev/null +++ b/src/data/AGENTS.md @@ -0,0 +1,27 @@ + + + +# data + +## Purpose +Thin query helpers over Content Collections — draft filtering, sorting, and the experience-taxonomy aggregation. + +## Key Files +| File | Description | +|------|-------------| +| `post.ts` | `getAllPosts()` (returns `CollectionEntry<"writing">`, drafts filtered in prod) and `groupPostsByYear()`. **Naming quirk:** file/functions say "post" but operate on the `writing` collection. | +| `experience.ts` | `getAllExperience()` (sorted by `startDate` desc) and `getContentForExperience(id)` which gathers all `projects`/`writing`/`speaking` entries whose `experiences` include `id`. | +| `speaking.ts` | `getAllSpeaking()` (drafts filtered in prod). | + +## For AI Agents + +### Working In This Directory +- Draft filtering uses `import.meta.env.PROD ? !data.draft : true` — drafts are visible in dev, hidden in production. +- `getContentForExperience` matches refs via `entry.data.experiences.some((ref) => ref.id === id)`. + +## Dependencies + +### Internal +- `astro:content` (`getCollection`). Consumed by pages in `src/pages/` and the homepage. + + diff --git a/src/data/experience.ts b/src/data/experience.ts new file mode 100644 index 0000000..4b0e1ae --- /dev/null +++ b/src/data/experience.ts @@ -0,0 +1,22 @@ +import { type CollectionEntry, getCollection } from "astro:content"; + +/** All experiences, most recent (by startDate) first. */ +export async function getAllExperience(): Promise[]> { + const entries = await getCollection("experience"); + return entries.sort((a, b) => b.data.startDate.valueOf() - a.data.startDate.valueOf()); +} + +const hasExperience = ( + entry: { data: { experiences: { id: string }[] } }, + id: string, +): boolean => entry.data.experiences.some((ref) => ref.id === id); + +/** Every project, writing & talk filed under a given experience id. */ +export async function getContentForExperience(id: string) { + const [projects, writing, speaking] = await Promise.all([ + getCollection("projects", (e) => hasExperience(e, id)), + getCollection("writing", (e) => (import.meta.env.PROD ? !e.data.draft : true) && hasExperience(e, id)), + getCollection("speaking", (e) => (import.meta.env.PROD ? !e.data.draft : true) && hasExperience(e, id)), + ]); + return { projects, writing, speaking }; +} diff --git a/src/data/post.ts b/src/data/post.ts new file mode 100644 index 0000000..57098d6 --- /dev/null +++ b/src/data/post.ts @@ -0,0 +1,15 @@ +import { type CollectionEntry, getCollection } from "astro:content"; + +/** filter out draft posts based on the environment */ +export async function getAllPosts(): Promise[]> { + return await getCollection("writing", ({ data }) => { + return import.meta.env.PROD ? !data.draft : true; + }); +} + +/** groups posts by year, using the year as the key + * Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so. + */ +export function groupPostsByYear(posts: CollectionEntry<"writing">[]) { + return Object.groupBy(posts, (post) => post.data.publishDate.getFullYear().toString()); +} diff --git a/src/data/speaking.ts b/src/data/speaking.ts new file mode 100644 index 0000000..1671ae8 --- /dev/null +++ b/src/data/speaking.ts @@ -0,0 +1,8 @@ +import { type CollectionEntry, getCollection } from "astro:content"; + +/** filter out draft talks in production */ +export async function getAllSpeaking(): Promise[]> { + return await getCollection("speaking", ({ data }) => { + return import.meta.env.PROD ? !data.draft : true; + }); +} diff --git a/src/data/tags.ts b/src/data/tags.ts new file mode 100644 index 0000000..5c634cc --- /dev/null +++ b/src/data/tags.ts @@ -0,0 +1,54 @@ +import { type CollectionEntry, getCollection } from "astro:content"; + +/** URL-safe slug for a skill/tag (tags are already lowercased by the schema). */ +export const slugifyTag = (tag: string) => + tag + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); + +const draftFilter = ({ data }: { data: { draft?: boolean } }) => + import.meta.env.PROD ? !data.draft : true; + +/** All tag-bearing content across the four collections (drafts filtered in prod). */ +export async function getTaggedCollections() { + const [projects, experience, writing, speaking] = await Promise.all([ + getCollection("projects"), + getCollection("experience"), + getCollection("writing", draftFilter), + getCollection("speaking", draftFilter), + ]); + return { projects, experience, writing, speaking }; +} + +export type TaggedCollections = Awaited>; + +type AnyTagged = CollectionEntry<"projects" | "experience" | "writing" | "speaking">; +const tagsOf = (entry: AnyTagged) => entry.data.tags ?? []; + +/** Map of slug → { label, count } across every collection, for the tag index. */ +export function getTagIndex(cols: TaggedCollections) { + const index = new Map(); + for (const col of Object.values(cols)) { + for (const entry of col) { + for (const tag of tagsOf(entry)) { + const slug = slugifyTag(tag); + const cur = index.get(slug) ?? { label: tag, count: 0 }; + cur.count += 1; + index.set(slug, cur); + } + } + } + return index; +} + +/** Every entry, grouped by collection, that carries the given tag label. */ +export function filterByTag(cols: TaggedCollections, label: string) { + const has = (e: AnyTagged) => tagsOf(e).includes(label); + return { + projects: cols.projects.filter(has), + experience: cols.experience.filter(has), + writing: cols.writing.filter(has), + speaking: cols.speaking.filter(has), + }; +} diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..e1ef1df --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,5 @@ +declare module "@pagefind/default-ui" { + declare class PagefindUI { + constructor(arg: unknown); + } +} diff --git a/src/icons/x.svg b/src/icons/x.svg new file mode 100644 index 0000000..4fad2be --- /dev/null +++ b/src/icons/x.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/avatar.jpg b/src/images/avatar.jpg deleted file mode 100644 index b0a6963..0000000 Binary files a/src/images/avatar.jpg and /dev/null differ diff --git a/src/images/logos/airbnb.svg b/src/images/logos/airbnb.svg deleted file mode 100644 index aab81e3..0000000 --- a/src/images/logos/airbnb.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/src/images/logos/animaginary.svg b/src/images/logos/animaginary.svg deleted file mode 100644 index ddea3ce..0000000 --- a/src/images/logos/animaginary.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/images/logos/cosmos.svg b/src/images/logos/cosmos.svg deleted file mode 100644 index 1a4a63f..0000000 --- a/src/images/logos/cosmos.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/images/logos/facebook.svg b/src/images/logos/facebook.svg deleted file mode 100644 index d5eaf5d..0000000 --- a/src/images/logos/facebook.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/images/logos/helio-stream.svg b/src/images/logos/helio-stream.svg deleted file mode 100644 index 35cc192..0000000 --- a/src/images/logos/helio-stream.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/images/logos/open-shuttle.svg b/src/images/logos/open-shuttle.svg deleted file mode 100644 index 3869ef6..0000000 --- a/src/images/logos/open-shuttle.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/images/logos/planetaria.svg b/src/images/logos/planetaria.svg deleted file mode 100644 index 77183b7..0000000 --- a/src/images/logos/planetaria.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/images/logos/sonr.svg b/src/images/logos/sonr.svg deleted file mode 100644 index 562ef2d..0000000 --- a/src/images/logos/sonr.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/images/logos/spacebolt.svg b/src/images/logos/spacebolt.svg deleted file mode 100644 index 2bcb48c..0000000 --- a/src/images/logos/spacebolt.svg +++ /dev/null @@ -1,1887 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/images/logos/starbucks.svg b/src/images/logos/starbucks.svg deleted file mode 100644 index cd80b21..0000000 --- a/src/images/logos/starbucks.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/images/logos/superball.svg b/src/images/logos/superball.svg deleted file mode 100644 index 71a8401..0000000 --- a/src/images/logos/superball.svg +++ /dev/null @@ -1,5014 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/images/logos/voluntree.svg b/src/images/logos/voluntree.svg deleted file mode 100644 index 9aec887..0000000 --- a/src/images/logos/voluntree.svg +++ /dev/null @@ -1,4294 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/images/logos/yue.svg b/src/images/logos/yue.svg deleted file mode 100644 index 7b70de2..0000000 --- a/src/images/logos/yue.svg +++ /dev/null @@ -1,2367 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/images/photos/image-1.jpg b/src/images/photos/image-1.jpg deleted file mode 100644 index 6700186..0000000 Binary files a/src/images/photos/image-1.jpg and /dev/null differ diff --git a/src/images/photos/image-2.jpg b/src/images/photos/image-2.jpg deleted file mode 100644 index 3b18c9b..0000000 Binary files a/src/images/photos/image-2.jpg and /dev/null differ diff --git a/src/images/photos/image-3.jpg b/src/images/photos/image-3.jpg deleted file mode 100644 index 5ae7740..0000000 Binary files a/src/images/photos/image-3.jpg and /dev/null differ diff --git a/src/images/photos/image-4.jpg b/src/images/photos/image-4.jpg deleted file mode 100644 index b4aa318..0000000 Binary files a/src/images/photos/image-4.jpg and /dev/null differ diff --git a/src/images/photos/image-5.jpg b/src/images/photos/image-5.jpg deleted file mode 100644 index bc2acdd..0000000 Binary files a/src/images/photos/image-5.jpg and /dev/null differ diff --git a/src/images/portrait.jpg b/src/images/portrait.jpg deleted file mode 100644 index d7f7d72..0000000 Binary files a/src/images/portrait.jpg and /dev/null differ diff --git a/src/layouts/AGENTS.md b/src/layouts/AGENTS.md new file mode 100644 index 0000000..4278992 --- /dev/null +++ b/src/layouts/AGENTS.md @@ -0,0 +1,26 @@ + + + +# layouts + +## Purpose +Page shells that wrap route content with ``, header, footer, and theming. + +## Key Files +| File | Description | +|------|-------------| +| `Base.astro` | Root shell for all pages. Renders `BaseHead`, `ThemeProvider`, `Header`, `
    `, `Footer`, and prefetch speculation rules. Takes a `meta: SiteMeta` prop; `description` defaults to `siteConfig.description`. Sets `max-w-3xl`, `font-mono`, padding. | +| `BlogPost.astro` | Layout for individual writing entries. Renders `Masthead`, optional `TOC`, the prose body, webmentions, and a back-to-top button. Builds the OG image URL as `/og-image/${post.id}.png`. | + +## For AI Agents + +### Working In This Directory +- Every page should render inside `Base.astro` (directly or via `BlogPost.astro`) and pass a `meta` object (`title` required; `description`, `ogImage`, `articleDate` optional). +- `
    ` carries `mb-20` to separate content from the footer's top border — adjust here for global content/footer spacing. + +## Dependencies + +### Internal +- `@/components/BaseHead.astro`, `@/components/layout/*`, `@/components/blog/*`, `@/site.config`, `@/types`. + + diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro new file mode 100644 index 0000000..426b5bc --- /dev/null +++ b/src/layouts/Base.astro @@ -0,0 +1,63 @@ +--- +import Analytics from "@vercel/analytics/astro"; +import BaseHead from "@/components/BaseHead.astro"; +import Footer from "@/components/layout/Footer.astro"; +import Header from "@/components/layout/Header.astro"; +import SkipLink from "@/components/SkipLink.astro"; +import ThemeProvider from "@/components/ThemeProvider.astro"; +import { siteConfig } from "@/site.config"; +import type { SiteMeta } from "@/types"; + +interface Props { + meta: SiteMeta; +} + +const { + meta: { articleDate, description = siteConfig.description, keywords, ogImage, schema, title }, +} = Astro.props; +--- + + + + + + + + +
    +
    + +
    +