Compare commits
14 Commits
0d820466d0
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9909ee6d94 | |||
| 91931039bc | |||
| f983fe5b83 | |||
| acb5d225c4 | |||
| 774c7766d6 | |||
| 30fb5d57ce | |||
| 9ef949550e | |||
| 46f416c6b5 | |||
| c3d539ffbd | |||
| a701b6a855 | |||
| 51c8effaf2 | |||
| a5999f1606 | |||
| 7aed841226 | |||
| 3b47596a57 |
+142
@@ -0,0 +1,142 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# Cloudflare
|
||||||
|
.wrangler/
|
||||||
|
|
||||||
|
# Vercel
|
||||||
|
.vercel/
|
||||||
|
|
||||||
|
# Sentry Vite Plugin
|
||||||
|
.env.sentry-build-plugin
|
||||||
|
|
||||||
|
# aws-cdk
|
||||||
|
.cdk.staging
|
||||||
|
cdk.out
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
nodejs 22.15.0
|
||||||
|
pnpm 9.7.1
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
Generated with [vike.dev/new](https://vike.dev/new) ([version 471](https://www.npmjs.com/package/create-vike/v/0.0.471)) using this command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm create vike@latest --react --compiled-css --cloudflare --eslint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
* [React](#react)
|
||||||
|
|
||||||
|
* [`/pages/+config.ts`](#pagesconfigts)
|
||||||
|
* [Routing](#routing)
|
||||||
|
* [`/pages/_error/+Page.jsx`](#pages_errorpagejsx)
|
||||||
|
* [`/pages/+onPageTransitionStart.ts` and `/pages/+onPageTransitionEnd.ts`](#pagesonpagetransitionstartts-and-pagesonpagetransitionendts)
|
||||||
|
* [SSR](#ssr)
|
||||||
|
* [HTML Streaming](#html-streaming)
|
||||||
|
|
||||||
|
## React
|
||||||
|
|
||||||
|
This app is ready to start. It's powered by [Vike](https://vike.dev) and [React](https://react.dev/learn).
|
||||||
|
|
||||||
|
### `/pages/+config.ts`
|
||||||
|
|
||||||
|
Such `+` files are [the interface](https://vike.dev/config) between Vike and your code. It defines:
|
||||||
|
|
||||||
|
* A default [`<Layout>` component](https://vike.dev/Layout) (that wraps your [`<Page>` components](https://vike.dev/Page)).
|
||||||
|
* A default [`title`](https://vike.dev/title).
|
||||||
|
* Global [`<head>` tags](https://vike.dev/head-tags).
|
||||||
|
|
||||||
|
### Routing
|
||||||
|
|
||||||
|
[Vike's built-in router](https://vike.dev/routing) lets you choose between:
|
||||||
|
|
||||||
|
* [Filesystem Routing](https://vike.dev/filesystem-routing) (the URL of a page is determined based on where its `+Page.jsx` file is located on the filesystem)
|
||||||
|
* [Route Strings](https://vike.dev/route-string)
|
||||||
|
* [Route Functions](https://vike.dev/route-function)
|
||||||
|
|
||||||
|
### `/pages/_error/+Page.jsx`
|
||||||
|
|
||||||
|
The [error page](https://vike.dev/error-page) which is rendered when errors occur.
|
||||||
|
|
||||||
|
### `/pages/+onPageTransitionStart.ts` and `/pages/+onPageTransitionEnd.ts`
|
||||||
|
|
||||||
|
The [`onPageTransitionStart()` hook](https://vike.dev/onPageTransitionStart), together with [`onPageTransitionEnd()`](https://vike.dev/onPageTransitionEnd), enables you to implement page transition animations.
|
||||||
|
|
||||||
|
### SSR
|
||||||
|
|
||||||
|
SSR is enabled by default. You can [disable it](https://vike.dev/ssr) for all your pages or only for some pages.
|
||||||
|
|
||||||
|
### HTML Streaming
|
||||||
|
|
||||||
|
You can enable/disable [HTML streaming](https://vike.dev/stream) for all your pages, or only for some pages while still using it for others.
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*.pdf
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
-44
@@ -1,44 +0,0 @@
|
|||||||
@import url("https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&display=swap");
|
|
||||||
|
|
||||||
/* @font-face {
|
|
||||||
font-family: Lato;
|
|
||||||
src: url("Lato2OFL/Lato-Regular.ttf");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: Lato;
|
|
||||||
src: url("Lato2OFL/Lato-Bold.ttf");
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: Lato;
|
|
||||||
src: url("Lato2OFL/Lato-Italic.ttf");
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: Lato;
|
|
||||||
src: url("Lato2OFL/Lato-BoldItalic.ttf");
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: bold;
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* @page {
|
|
||||||
@bottom-center {
|
|
||||||
content: "https://sakal.us";
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #aaa;
|
|
||||||
font-family: Lato;
|
|
||||||
}
|
|
||||||
@top-center {
|
|
||||||
content: "CSS Paged Media Tutorial by Andreas Jung";
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #aaa;
|
|
||||||
font-family: Lato;
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* body {
|
|
||||||
font-family: Lato;
|
|
||||||
} */
|
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
export const contactInfo = {
|
||||||
|
name: "Brian Sakal",
|
||||||
|
email: "brian@sakal.us",
|
||||||
|
phone: "305-930-0248",
|
||||||
|
website: "https://git.sakal.us/avraham",
|
||||||
|
location: "Passaic, NJ",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mainSkills = [
|
||||||
|
"Javascript/Typescript",
|
||||||
|
"React",
|
||||||
|
"Next.js",
|
||||||
|
"Node.js",
|
||||||
|
"Express.js",
|
||||||
|
"Docker",
|
||||||
|
"MySQL",
|
||||||
|
"Clickhouse",
|
||||||
|
"DevOps",
|
||||||
|
"Kubernetes",
|
||||||
|
"Linux",
|
||||||
|
"Cloudflare",
|
||||||
|
"Vercel",
|
||||||
|
"S3",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const exposedSkills = [
|
||||||
|
"PostgreSQL",
|
||||||
|
"Elasticsearch",
|
||||||
|
"Redis",
|
||||||
|
"RethinkDB",
|
||||||
|
"Helm",
|
||||||
|
"nginx",
|
||||||
|
"React Native",
|
||||||
|
"Swagger",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const employment = [
|
||||||
|
{
|
||||||
|
title: "Lead Developer (Full-Stack)",
|
||||||
|
company: "TorahAnytime.com",
|
||||||
|
location: "Flushing, NY",
|
||||||
|
start: "June 2022",
|
||||||
|
end: "Present",
|
||||||
|
highlights: [
|
||||||
|
// From old resume:
|
||||||
|
// "Fixed/troubleshooted bugs and added features across multiple legacy codebases",
|
||||||
|
// "Consolidated cloud resources by using Kubernetes.",
|
||||||
|
// "Transitioned a legacy Cloudflare/nginx TLD site to Vercel, while maintaining custom nginx routes on the TLD, plus the entire legacy site under a subdomain.",
|
||||||
|
// "Reduced time-to-deployment from 5 minutes to 15 seconds with locally-runnable deploy scripts; besides setting up Drone/Gitlab CI/CD pipelines.",
|
||||||
|
// "Architected custom analytics backend w/ Clickhouse. Reduced query times from minutes to milliseconds.",
|
||||||
|
// "Implemented a custom IVR phone system to dynamically browse content.",
|
||||||
|
// "Deployed and configured various supporting/ancillary services in Kubernetes, including Gitlab, private Docker & NPM registries.",
|
||||||
|
// "Implemented OpenTelemetry tracing visualized in Grafana, decreasing transcoder troubleshooting by 50%.",
|
||||||
|
// "Implemented various asynchronous workflows, including a new video transcoder, in Temporal; thus completely severing our dependence on Vimeo, reducing costs by $40k/year.",
|
||||||
|
// From Otta resume:
|
||||||
|
"General full-stack work (frontend implementation in Next.js, adding endpoints to api, running database migrations in MySQL, updating Clickhouse with those schema changes, etc.)",
|
||||||
|
"Architected and implemented an in-house transcoding/content-delivery system based on Temporal, Cloudflare, and Backblaze B2 to replace Vimeo; saving $70k/year.",
|
||||||
|
"Setup generic events analytics system in Clickhouse to replace MySQL, Redis, and (eventually) Google Analytics, for faster and more granular analytics over arbitrary events and event payloads; significantly improving query times from minutes to milliseconds",
|
||||||
|
"Legacy code maintenance/incremental refactoring",
|
||||||
|
"Consolidated various async jobs (e.g. notifications, daily/weekly digest, delayed release, etc.) into type-safe Temporal workflows, enhancing developer experience and setting conventions for easy implementation of future workflows",
|
||||||
|
"Designed and deployed a multi-instance tRPC server for type-safe endpoints for the frontend",
|
||||||
|
`Adapted Agile/Scrum to accomodate stakeholder expectations of simultaneous planned and unplanned (i.e. during a Sprint) throughput and turnaround, while keeping longer-term goals predictable.`,
|
||||||
|
"Crafted multi-stage Dockerfiles to leverage layer caching in various projects",
|
||||||
|
"Reduced deployment time from 5 minutes to 15 seconds by implementing locally-runnable deploy scripts, besides setting up Github Actions CI/CD pipelines.",
|
||||||
|
"Developed a custom IVR phone system for dynamically browsing our content.",
|
||||||
|
"Deployed and configured various supporting services in Kubernetes such as Grafana, private Docker & NPM registries.",
|
||||||
|
"Implemented OpenTelemetry tracing visualized in Grafana, resulting in 50% decrease in legacy transcoder troubleshooting time.",
|
||||||
|
"Designed various parameterized Grafana dashboards for at-a-glance analysis of user stats.",
|
||||||
|
"Implemented two integrations: MySQL-Clickhouse sync (using Clickhouse primitives), and MySQL-Salesforce sync (on Temporal).",
|
||||||
|
"Transitioned legacy Cloudflare/nginx TLD site to Vercel, while maintaining custom nginx routes on the TLD and hosting the entire legacy site under a subdomain.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "Assistant Manager",
|
||||||
|
// company: "Sureknit Inc. Property Management",
|
||||||
|
// location: "East Flatbush, NY",
|
||||||
|
// start: "February 2020",
|
||||||
|
// end: "June 2022",
|
||||||
|
// highlights: [
|
||||||
|
// "Kept accurate records of tenant rent payments.",
|
||||||
|
// "Balanced and monitored bank accounts for all subsidiary companies.",
|
||||||
|
// "Furnished necessary documentation for various real estate operations, such as new leases, property sales, etc.",
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Architect Intern",
|
||||||
|
// company: "Sandy Hacohen",
|
||||||
|
// location: "Flushing, NY",
|
||||||
|
// start: "July 2018",
|
||||||
|
// end: "November 2018",
|
||||||
|
// highlights: [
|
||||||
|
// "Produced architectural drawings of various points-of-view based on given floorplans.",
|
||||||
|
// "Adjusted existing floorplans to spec.",
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Operations Manager",
|
||||||
|
// company: "Tax Opportunities America",
|
||||||
|
// location: "Flushing, NY",
|
||||||
|
// start: "April 2014",
|
||||||
|
// end: "May 2015",
|
||||||
|
// highlights: [
|
||||||
|
// "Interfaced with clients, prepared necessary documentation and submitted applications to government programs on behalf of clients.",
|
||||||
|
// "Monitored and accommodated deadlines, missing client data, and when client promised to have the data available",
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Representative",
|
||||||
|
// company: "Computer-Aided Engineering Network (CAEN) Hotline",
|
||||||
|
// location: "U. of Michigan",
|
||||||
|
// start: "2007",
|
||||||
|
// end: "2009, 2010",
|
||||||
|
// highlights: [
|
||||||
|
// "Provided first-response assistance to students and collecting pertinent information to arrange for involvement of specialized staff.",
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const projectsHobbies = [
|
||||||
|
{
|
||||||
|
title: "Token-Efficient AI Chat",
|
||||||
|
highlights: ["Vike", "Hono", "React", "Cloudflare Pages"],
|
||||||
|
code: "https://git.sakal.us/avraham/context-efficient-ai",
|
||||||
|
deployment: "https://ai.sakal.us",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Calendar Optimizer project",
|
||||||
|
highlights: [
|
||||||
|
"Stock options research platform",
|
||||||
|
"Preact",
|
||||||
|
"trpc",
|
||||||
|
"Clickhouse",
|
||||||
|
],
|
||||||
|
code: "https://git.sakal.us/avraham/calendar-optimizer",
|
||||||
|
deployment: "https://calendar-optimizer-frontend.sakal.us",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "This Résumé",
|
||||||
|
highlights: ["Vike", "React", "Cloudflare Pages"],
|
||||||
|
code: "https://git.sakal.us/avraham/resume",
|
||||||
|
deployment: "https://resume.sakal.us",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Personal coding blog",
|
||||||
|
highlights: ["Astro", "Cloudflare Pages"],
|
||||||
|
code: "https://git.sakal.us/avraham/blog-astro",
|
||||||
|
deployment: "https://blog.sakal.us",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Self-host email for the sakal.us domain",
|
||||||
|
highlights: ["Vultr-Hosted", "Postfix for SMTP", "Dovecot for IMAP"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Self-host a Kubernetes cluster",
|
||||||
|
highlights: [
|
||||||
|
"k3s",
|
||||||
|
"Private Docker image registry",
|
||||||
|
"cert-manager",
|
||||||
|
"Private coding projects",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Self-host a Gitea code repository",
|
||||||
|
highlights: ["Deployed within the above Kubernetes cluster."],
|
||||||
|
deployment: "https://git.sakal.us",
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "Custom From-Scratch Full-Stack eCommerce website",
|
||||||
|
// highlights: [
|
||||||
|
// "Previously at piazzaoptical.com",
|
||||||
|
// "mithril.js",
|
||||||
|
// "nginx as reverse proxy and TLS termination",
|
||||||
|
// "lua-http on LuaJIT",
|
||||||
|
// "LMDB",
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const education = [
|
||||||
|
{
|
||||||
|
title: "B.S. in Aerospace Engineering",
|
||||||
|
location: "University of Michigan, Ann Arbor, MI",
|
||||||
|
start: "Sept. 2006",
|
||||||
|
end: "April 2009, Sept. 2010, April 2011",
|
||||||
|
gpa: "3.185",
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "Ph.D. in Rabbinics",
|
||||||
|
// location: "Rabbinical Seminary of America, Flushing, NY",
|
||||||
|
// start: "April 2009",
|
||||||
|
// end: "April 2010, April 2011, January 2020",
|
||||||
|
// },
|
||||||
|
];
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import eslint from "@eslint/js";
|
||||||
|
import react from "eslint-plugin-react";
|
||||||
|
import globals from "globals";
|
||||||
|
import tseslint, { type ConfigArray } from "typescript-eslint";
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"dist/*",
|
||||||
|
// Temporary compiled files
|
||||||
|
"**/*.ts.build-*.mjs",
|
||||||
|
|
||||||
|
// JS files at the root of the project
|
||||||
|
"*.js",
|
||||||
|
"*.cjs",
|
||||||
|
"*.mjs",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
warnOnUnsupportedTypeScriptVersion: false,
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-namespace": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
|
||||||
|
...react.configs.flat.recommended,
|
||||||
|
languageOptions: {
|
||||||
|
...react.configs.flat.recommended.languageOptions,
|
||||||
|
globals: {
|
||||||
|
...globals.serviceworker,
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ConfigArray[number],
|
||||||
|
|
||||||
|
react.configs.flat["jsx-runtime"] as ConfigArray[number],
|
||||||
|
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
"react/no-unknown-property": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
ignore: ["css"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
-343
@@ -1,343 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<section class="header">
|
|
||||||
<h2 class="lg bold">Brian Sakal</h2>
|
|
||||||
</section>
|
|
||||||
<section class="contact-info">
|
|
||||||
<ul class="inline">
|
|
||||||
<li>Passaic, NJ</li>
|
|
||||||
<li>305-930-0248</li>
|
|
||||||
<li><a href="https://git.sakal.us/">Git</a></li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section class="sections">
|
|
||||||
<section class="relevant-skills">
|
|
||||||
<label>
|
|
||||||
<div class="fit-content">
|
|
||||||
<div class="bold">Skills/Technologies</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div>
|
|
||||||
<ul class="inline">
|
|
||||||
<li>Docker</li>
|
|
||||||
<li>DevOps (Logging, Monitoring, Grafana, CI/CD)</li>
|
|
||||||
<li>Kubernetes</li>
|
|
||||||
<li>Node.js</li>
|
|
||||||
<li>Javascript/Typescript</li>
|
|
||||||
<li>React</li>
|
|
||||||
<li>MySQL/PostgreSQL</li>
|
|
||||||
<li>Elasticsearch</li>
|
|
||||||
<li>Clickhouse</li>
|
|
||||||
<li>Linux</li>
|
|
||||||
<li>Cloudflare</li>
|
|
||||||
<li>Redis</li>
|
|
||||||
<li>RethinkDB</li>
|
|
||||||
<li>Helm</li>
|
|
||||||
<li>nginx</li>
|
|
||||||
<li>Vercel</li>
|
|
||||||
<li>S3</li>
|
|
||||||
<li>Express.js</li>
|
|
||||||
<li>React</li>
|
|
||||||
<li>React Native</li>
|
|
||||||
<li>Next.js</li>
|
|
||||||
<li>Swagger</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="employment">
|
|
||||||
<label>
|
|
||||||
<div class="fit-content">
|
|
||||||
<span class="bold">Employment</span>
|
|
||||||
<span class="sm italic right">(Most recent)</span>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div>
|
|
||||||
<ul class="no-bullet">
|
|
||||||
<li>
|
|
||||||
<div class="space-between">
|
|
||||||
<div>
|
|
||||||
<span class="bold">Senior Full-Stack Developer,</span>
|
|
||||||
<span>TorahAnytime.com, Flushing, NY</span>
|
|
||||||
</div>
|
|
||||||
<div class="italic right">June 2022–Present</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
<!-- <li>
|
|
||||||
Fixed/troubleshooted bugs and added features across multiple
|
|
||||||
legacy codebases
|
|
||||||
</li>
|
|
||||||
<li>Consolidated cloud resources by using Kubernetes.</li> -->
|
|
||||||
<li>
|
|
||||||
Transitioned a legacy Cloudflare/nginx TLD site to Vercel,
|
|
||||||
while maintaining custom nginx routes on the TLD, plus the
|
|
||||||
entire legacy site under a subdomain.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Reduced time-to-deployment from 5 minutes to 15 seconds with
|
|
||||||
locally-runnable deploy scripts; besides setting up
|
|
||||||
Drone/Gitlab CI/CD pipelines.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Architected custom analytics backend w/ Clickhouse. Reduced
|
|
||||||
query times from minutes to milliseconds.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Implemented a custom IVR phone system to dynamically browse
|
|
||||||
content.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Deployed and configured various supporting/ancillary
|
|
||||||
services in Kubernetes, including Gitlab, private Docker &
|
|
||||||
NPM registries.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Implemented OpenTelemetry tracing visualized in Grafana,
|
|
||||||
decreasing transcoder troubleshooting by 50%.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Implemented various asynchronous workflows, including a new
|
|
||||||
video transcoder, in Temporal; thus completely severing our
|
|
||||||
dependence on Vimeo, reducing costs by $40k/year.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="space-between">
|
|
||||||
<div>
|
|
||||||
<span class="bold">Assistant Manager,</span>
|
|
||||||
<span
|
|
||||||
>Sureknit Inc. Property Management, East Flatbush, NY</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="italic right">February 2020–June 2022</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
<li>Kept accurate records of tenant rent payments.</li>
|
|
||||||
<li>
|
|
||||||
Balanced and monitored bank accounts for all subsidiary
|
|
||||||
companies.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Furnished necessary documentation for various real estate
|
|
||||||
operations, such as new leases, property sales, etc.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<!-- <li>
|
|
||||||
<div>
|
|
||||||
<span class="bold">Architect Intern,</span>
|
|
||||||
<span>Sandy Hacohen, Flushing, NY</span>
|
|
||||||
</div>
|
|
||||||
<div class="italic right">July 2018–November 2018</div>
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Produced architectural drawings of various points-of-view
|
|
||||||
based on given floorplans.
|
|
||||||
</li>
|
|
||||||
<li>Adjusted existing floorplans to spec.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li> -->
|
|
||||||
<!-- <li>
|
|
||||||
<div class="space-between">
|
|
||||||
<div>
|
|
||||||
<span class="bold">Operations Manager,</span>
|
|
||||||
<span>Tax Opportunities America, Flushing, NY</span>
|
|
||||||
</div>
|
|
||||||
<div class="italic right">April 2014–May 2015</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Interfaced with clients, prepared necessary documentation
|
|
||||||
and submitted applications to government programs on behalf
|
|
||||||
of clients.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Monitored and accommodated deadlines, missing client data,
|
|
||||||
and when client promised to have the data available
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li> -->
|
|
||||||
<!-- <li>
|
|
||||||
<div class="space-between">
|
|
||||||
<div>
|
|
||||||
<span class="bold">Representative,</span>
|
|
||||||
<span
|
|
||||||
>Computer-Aided Engineering Network (CAEN) Hotline, U. of
|
|
||||||
Michigan</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="italic right">2007–2009, 2010–2011</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Provided first-response assistance to students and
|
|
||||||
collecting pertinent information to arrange for involvement
|
|
||||||
of specialized staff.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li> -->
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="projects-hobbies">
|
|
||||||
<label>
|
|
||||||
<div class="fit-content">
|
|
||||||
<div class="bold">Projects/Hobbies</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div>
|
|
||||||
<ul class="no-bullet">
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
<span class="bold"
|
|
||||||
>Self-host email for the sakal.us domain</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
><ul class="inline">
|
|
||||||
<li>Vultr-Hosted</li>
|
|
||||||
<li>Postfix for SMTP</li>
|
|
||||||
<li>Dovecot for IMAP</li>
|
|
||||||
</ul></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
<span class="bold">Self-host a Kubernetes cluster</span>
|
|
||||||
<span
|
|
||||||
><ul class="inline">
|
|
||||||
<li><code>k3s</code></li>
|
|
||||||
<li>Private Docker image registry</li>
|
|
||||||
<li>
|
|
||||||
<code>cert-manager</code>
|
|
||||||
</li>
|
|
||||||
<li>Private coding projects</li>
|
|
||||||
</ul></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
<span class="bold">Self-host a Gitea code repository</span>
|
|
||||||
<span
|
|
||||||
>Deployed within the above Kubernetes cluster. Available at
|
|
||||||
<a href="https://git.sakal.us/">https://git.sakal.us</a></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
<span class="bold"
|
|
||||||
>Personal coding blog (<a
|
|
||||||
href="https://git.sakal.us/avraham/blog-astro"
|
|
||||||
>Code</a
|
|
||||||
>)</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
><ul class="inline">
|
|
||||||
<li>Astro</li>
|
|
||||||
<li>Cloudflare Pages</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://blog.sakal.us/">https://blog.sakal.us</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
<span class="bold"
|
|
||||||
>Calendar Optimizer project (<a
|
|
||||||
href="https://git.sakal.us/avraham/calendar-optimizer"
|
|
||||||
>Code</a
|
|
||||||
>)</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
><ul class="inline">
|
|
||||||
<li>Stock options research platform</li>
|
|
||||||
<li>Preact</li>
|
|
||||||
<li>trpc</li>
|
|
||||||
<li>Clickhouse</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://calendar-optimizer-frontend.sakal.us"
|
|
||||||
>https://calendar-optimizer-frontend.sakal.us</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
<span class="bold"
|
|
||||||
>Custom From-Scratch Full-Stack eCommerce website (<a
|
|
||||||
href="https://git.sakal.us/avraham/piazzaoptical.com"
|
|
||||||
>Code</a
|
|
||||||
>)</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
><ul class="inline">
|
|
||||||
<li>Previously at piazzaoptical.com</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://mithril.js.org/">mithril.js</a>
|
|
||||||
</li>
|
|
||||||
<li>nginx as reverse proxy and TLS termination</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://daurnimator.github.io/lua-http/0.4/"
|
|
||||||
>lua-http</a
|
|
||||||
>
|
|
||||||
on <a href="https://luajit.org/">LuaJIT</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="http://www.lmdb.tech/doc/">LMDB</a></li>
|
|
||||||
</ul></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="education">
|
|
||||||
<label>
|
|
||||||
<span class="bold">Education</span>
|
|
||||||
</label>
|
|
||||||
<div>
|
|
||||||
<ul class="no-bullet">
|
|
||||||
<li>
|
|
||||||
<div class="space-between">
|
|
||||||
<div>
|
|
||||||
<span class="bold">B.S. in Aerospace Engineering,</span>
|
|
||||||
<span>University of Michigan, Ann Arbor, MI</span>
|
|
||||||
</div>
|
|
||||||
<div class="italic right">
|
|
||||||
Sept. 2006–April 2009, Sept. 2010–April 2011, GPA 3.185
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<!-- <li>
|
|
||||||
<div>
|
|
||||||
<span class="bold">Ph.D. in Rabbinics,</span>
|
|
||||||
<span>Rabbinical Seminary of America, Flushing, NY</span>
|
|
||||||
</div>
|
|
||||||
<div class="italic right">
|
|
||||||
April 2009–April 2010, April 2011–January 2020
|
|
||||||
</div>
|
|
||||||
</li> -->
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import "./style.css";
|
||||||
|
|
||||||
|
export default function LayoutDefault({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
css={{
|
||||||
|
display: "flex",
|
||||||
|
maxWidth: 1024,
|
||||||
|
margin: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/* Reset */
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
@import url("./common.css");
|
|
||||||
@page {
|
|
||||||
size: letter;
|
|
||||||
margin: 0.5in;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vike dev",
|
||||||
|
"build": "vike build",
|
||||||
|
"preview": "run-s build preview:wrangler",
|
||||||
|
"generate-pdf": "concurrently -k -s first 'pnpm vike preview' 'wait-on http://localhost:3000 && chromium --headless --no-pdf-header-footer --print-to-pdf=./assets/resume-brian-sakal.pdf --timeout=1500 http://localhost:3000' && cp ./assets/resume-brian-sakal.pdf ./dist/client/assets/static/resume-brian-sakal.pdf",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview:wrangler": "wrangler pages dev",
|
||||||
|
"deploy:wrangler": "wrangler pages deploy",
|
||||||
|
"deploy": "run-s build generate-pdf deploy:wrangler"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@compiled/react": "^0.18.6",
|
||||||
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
"react": "^19.1.1",
|
||||||
|
"react-dom": "^19.1.1",
|
||||||
|
"vike": "^0.4.237",
|
||||||
|
"vike-cloudflare": "^0.1.7",
|
||||||
|
"vike-react": "^0.6.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@cloudflare/workers-types": "^4.20250927.0",
|
||||||
|
"@eslint/js": "^9.33.0",
|
||||||
|
"@types/react": "^19.1.10",
|
||||||
|
"@types/react-dom": "^19.1.7",
|
||||||
|
"concurrently": "^9.2.1",
|
||||||
|
"eslint": "^9.33.0",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
"globals": "^16.3.0",
|
||||||
|
"npm-run-all2": "^8.0.4",
|
||||||
|
"typescript": "^5.9.2",
|
||||||
|
"typescript-eslint": "^8.39.1",
|
||||||
|
"vite": "^7.1.2",
|
||||||
|
"vite-plugin-compiled-react": "^1.3.1",
|
||||||
|
"wait-on": "^9.0.1",
|
||||||
|
"wrangler": "^4.40.2"
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import type { Config } from "vike/types";
|
||||||
|
import vikeReact from "vike-react/config";
|
||||||
|
import Layout from "../layouts/LayoutDefault.js";
|
||||||
|
|
||||||
|
// Default config (can be overridden by pages)
|
||||||
|
// https://vike.dev/config
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// https://vike.dev/Layout
|
||||||
|
Layout,
|
||||||
|
|
||||||
|
// https://vike.dev/head-tags
|
||||||
|
title: "Brian Sakal | Resume",
|
||||||
|
description: "Resume Auto-Generated from Metadata",
|
||||||
|
|
||||||
|
extends: vikeReact,
|
||||||
|
} satisfies Config;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { usePageContext } from "vike-react/usePageContext";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const { is404 } = usePageContext();
|
||||||
|
if (is404) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>404 Page Not Found</h1>
|
||||||
|
<p>This page could not be found.</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>500 Internal Server Error</h1>
|
||||||
|
<p>Something went wrong.</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
import { useData } from "vike-react/useData";
|
||||||
|
import type { Data } from "./+data";
|
||||||
|
import "../../styles.css";
|
||||||
|
import "./print-button.css";
|
||||||
|
import resumeImage from "../../assets/resume-image.jpg";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
css={{
|
||||||
|
fontFamily: '"EB Garamond", serif',
|
||||||
|
fontOpticalSizing: "auto",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontSize: "0.85em",
|
||||||
|
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "30%",
|
||||||
|
backgroundColor: "lavender",
|
||||||
|
padding: "1em",
|
||||||
|
gap: "1em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Header />
|
||||||
|
<MainSkills />
|
||||||
|
<ExposedSkills />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
css={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "70%",
|
||||||
|
paddingLeft: "1em",
|
||||||
|
paddingRight: "1em",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "center",
|
||||||
|
width: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="floating-print-btn"
|
||||||
|
href={/*pdfUrl*/ "assets/static/resume-brian-sakal.pdf"}
|
||||||
|
download
|
||||||
|
>
|
||||||
|
{/* Print */}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 640 640"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
>
|
||||||
|
{/*Font Awesome Free v7.0.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.*/}
|
||||||
|
<path d="M128 128C128 92.7 156.7 64 192 64L405.5 64C422.5 64 438.8 70.7 450.8 82.7L493.3 125.2C505.3 137.2 512 153.5 512 170.5L512 208L128 208L128 128zM64 320C64 284.7 92.7 256 128 256L512 256C547.3 256 576 284.7 576 320L576 416C576 433.7 561.7 448 544 448L512 448L512 512C512 547.3 483.3 576 448 576L192 576C156.7 576 128 547.3 128 512L128 448L96 448C78.3 448 64 433.7 64 416L64 320zM192 480L192 512L448 512L448 416L192 416L192 480zM520 336C520 322.7 509.3 312 496 312C482.7 312 472 322.7 472 336C472 349.3 482.7 360 496 360C509.3 360 520 349.3 520 336z" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Employment />
|
||||||
|
<ProjectsHobbies />
|
||||||
|
<Education />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Header() {
|
||||||
|
const { contactInfo } = useData<Data>();
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
css={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<section>
|
||||||
|
<div css={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<img
|
||||||
|
src={resumeImage}
|
||||||
|
alt="headshot"
|
||||||
|
css={{ width: "70%", borderRadius: "50%", objectFit: "contain" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2 css={{ marginBottom: "0.3em" }} className="lg bold">
|
||||||
|
{contactInfo.name}
|
||||||
|
</h2>
|
||||||
|
</section>
|
||||||
|
<section css={{ textAlign: "center" }}>
|
||||||
|
<ul className="inline">
|
||||||
|
<li>{contactInfo.phone}</li>
|
||||||
|
<li>{contactInfo.email}</li>
|
||||||
|
</ul>
|
||||||
|
<ul className="inline">
|
||||||
|
<li>{contactInfo.location}</li>
|
||||||
|
<li>
|
||||||
|
<a href={contactInfo.website}>{contactInfo.website}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MainSkills() {
|
||||||
|
const { mainSkills } = useData<Data>();
|
||||||
|
return (
|
||||||
|
<section className="relevant-skills">
|
||||||
|
<h3>Main Skills/Technologies</h3>
|
||||||
|
<div>
|
||||||
|
<ul className="inline" css={{ textAlign: "justify" }}>
|
||||||
|
{mainSkills.map((skill) => (
|
||||||
|
<li>{skill}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExposedSkills() {
|
||||||
|
const { exposedSkills } = useData<Data>();
|
||||||
|
return (
|
||||||
|
<section className="relevant-skills">
|
||||||
|
<h3>Have Used</h3>
|
||||||
|
<div>
|
||||||
|
<ul className="inline" css={{ textAlign: "justify" }}>
|
||||||
|
{exposedSkills.map((skill) => (
|
||||||
|
<li>{skill}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Employment() {
|
||||||
|
const { employment } = useData<Data>();
|
||||||
|
return (
|
||||||
|
<section className="employment">
|
||||||
|
<h2>
|
||||||
|
<div className="fit-content">
|
||||||
|
<span className="bold">Employment </span>
|
||||||
|
<span css={{ fontWeight: 300 }} className="sm italic right">
|
||||||
|
(Most recent)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</h2>
|
||||||
|
<div>
|
||||||
|
<ul className="no-bullet">
|
||||||
|
{employment.map((employment) => (
|
||||||
|
<li>
|
||||||
|
<div className="space-between" key={employment.title}>
|
||||||
|
<div>
|
||||||
|
<span className="bold">{employment.title},</span>{" "}
|
||||||
|
<span>
|
||||||
|
{employment.company}, {employment.location}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="italic right">
|
||||||
|
{employment.start}-{employment.end}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
{employment.highlights.map((highlight) => (
|
||||||
|
<li key={highlight} css={{ textAlign: "justify" }}>
|
||||||
|
{highlight}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProjectsHobbies() {
|
||||||
|
const { projectsHobbies } = useData<Data>();
|
||||||
|
return (
|
||||||
|
<section className="projects-hobbies">
|
||||||
|
<h2>
|
||||||
|
<div className="fit-content">
|
||||||
|
<div className="bold">Projects/Hobbies</div>
|
||||||
|
</div>
|
||||||
|
</h2>
|
||||||
|
<div>
|
||||||
|
<ul className="no-bullet">
|
||||||
|
{projectsHobbies.map((projectHobby) => (
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<span className="bold">{projectHobby.title}</span>
|
||||||
|
<span>
|
||||||
|
<ul className="inline">
|
||||||
|
{projectHobby.highlights.map((highlight) => (
|
||||||
|
<li>{highlight}</li>
|
||||||
|
))}
|
||||||
|
{projectHobby.deployment && (
|
||||||
|
<li>
|
||||||
|
<a href={projectHobby.deployment}>
|
||||||
|
{projectHobby.deployment}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{projectHobby.code && (
|
||||||
|
<li>
|
||||||
|
<a href={projectHobby.code}>[Code]</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Education() {
|
||||||
|
const { education } = useData<Data>();
|
||||||
|
return (
|
||||||
|
<section className="education">
|
||||||
|
<h2>
|
||||||
|
<span className="bold">Education</span>
|
||||||
|
</h2>
|
||||||
|
<div>
|
||||||
|
<ul className="no-bullet">
|
||||||
|
{education.map((education) => (
|
||||||
|
<li>
|
||||||
|
<div className="space-between">
|
||||||
|
<div>
|
||||||
|
<span className="bold">{education.title},</span>
|
||||||
|
<span>{education.location}</span>
|
||||||
|
</div>
|
||||||
|
<div className="italic right">
|
||||||
|
{education.start}–{education.end}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import * as resumeData from "../../database/resume.js";
|
||||||
|
|
||||||
|
export default function data() {
|
||||||
|
return resumeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Data = ReturnType<typeof data>;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
.floating-print-btn {
|
||||||
|
|
||||||
|
/* position: fixed;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px; */
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f8f9fa;
|
||||||
|
/* border: none; */
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
animation: float 2.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-print-btn:hover {
|
||||||
|
background: #f0f2f5;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2), 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-print-btn:active {
|
||||||
|
background: #f8f9fa;
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-print-btn {
|
||||||
|
color: #444444;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
.floating-print-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+5565
File diff suppressed because it is too large
Load Diff
+9
-2
@@ -1,5 +1,3 @@
|
|||||||
@import url("./letter.css");
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "EB Garamond", serif;
|
font-family: "EB Garamond", serif;
|
||||||
font-optical-sizing: auto;
|
font-optical-sizing: auto;
|
||||||
@@ -102,3 +100,12 @@ a {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.employment > :first-child {
|
||||||
|
margin-block-start: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-block-start: 0.3em;
|
||||||
|
margin-block-end: 0.3em;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"noEmit": true,
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": [
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable",
|
||||||
|
"ESNext"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"vite/client",
|
||||||
|
"vike-react",
|
||||||
|
"vike-cloudflare/types"
|
||||||
|
],
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "react"
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { pages } from "vike-cloudflare";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { compiled } from "vite-plugin-compiled-react";
|
||||||
|
import vike from "vike/plugin";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vike(),
|
||||||
|
compiled({
|
||||||
|
extract: true,
|
||||||
|
}),
|
||||||
|
react(),
|
||||||
|
pages(),
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
target: "es2022",
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
name = "resume"
|
||||||
|
compatibility_date = "2024-09-29"
|
||||||
|
pages_build_output_dir = "./dist/cloudflare"
|
||||||
|
compatibility_flags = [ "nodejs_compat" ]
|
||||||
Reference in New Issue
Block a user