diff --git a/.cursor/rules/code-style.mdc b/.cursor/rules/code-style.mdc new file mode 100644 index 0000000..1091d23 --- /dev/null +++ b/.cursor/rules/code-style.mdc @@ -0,0 +1,30 @@ +--- +description: Code and comment conventions for marc.tt +alwaysApply: true +--- + +# Code Style + +Keep code and comments as concise as they can be. + +## Comments + +- Comment intent, trade-offs, or non-obvious constraints — never narrate what the code already says. + +```scss +// BAD — narrates the obvious +.card { padding: 1rem; } // add padding to the card + +// GOOD — explains a non-obvious choice +// Clip layer: lets grid-template-rows: 0fr collapse the padded panel fully. +.menu-dropdown-clip { min-height: 0; overflow: hidden; } +``` + +- Prefer a one-line `/* Section */` header over multi-line ASCII banners. + +## General + +- Prefer editing existing files over adding new ones; reuse design tokens (`--fg`, `--bg`, `--font-*`) instead of hardcoding values. +- SCSS lives in `_sass/` and is wired through `css/main.scss`. Homepage styles go in `_home.scss` under the `.home` scope. +- Markup inside a block-level HTML element in a `.md` file is not processed as Markdown — write plain HTML there. +- No dependencies for things CSS can do. diff --git a/.cursor/rules/design-philosophy.mdc b/.cursor/rules/design-philosophy.mdc new file mode 100644 index 0000000..b21c01c --- /dev/null +++ b/.cursor/rules/design-philosophy.mdc @@ -0,0 +1,22 @@ +--- +description: Design philosophy for marc.tt — reference for all UI/UX decisions +alwaysApply: true +--- + +# Design Philosophy + +Inspired by Apple. Three principles: + +1. **Purposeful** — every animation communicates something (entrance, hierarchy, interactivity). If it doesn't serve a purpose, remove it. +2. **Fluid** — use natural ease curves (`cubic-bezier(0.25, 0.1, 0.25, 1)`), never linear or mechanical. Motion should feel like it has weight. +3. **Restrained** — if you notice the animation, it's too much. Subtlety over spectacle. Prefer 6-8px translate and 400-600ms durations. + +## Implementation rules + +- CSS-first: SCSS with `@keyframes` and the `.animate-in` utility (stagger via the `--delay` custom property). No animation libraries. +- Always respect `prefers-reduced-motion` with a media query. +- Hover micro-interactions: subtle lifts (`translateY(-2px)`), scale (`scale(1.1)`), color transitions. Keep durations 200-300ms. +- No scroll-triggered animations — the column is narrow; entrance-on-mount is enough. +- Typography: Inter for body, JetBrains Mono for mono. Generous tracking on headings, tight on labels. +- Color: monochrome. Layer opacity over solid colors for depth using the RGB tokens, e.g. `rgb(var(--fg) / 0.05)` and `rgb(var(--bg) / 0.6)`. Root font-size stays 16px. +- Layout: single 640px column, generous whitespace, content-first. diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000..4e9903b --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,37 @@ +#!/bin/sh +# Enforces Conventional Commits on the subject line. +# Install: npm install (sets core.hooksPath via prepare) +# or: git config core.hooksPath .githooks + +commit_msg_file="$1" +subject=$(head -n1 "$commit_msg_file") + +# Git-generated messages during merge/rebase/cherry-pick. +case "$subject" in + Merge\ *) exit 0 ;; + Revert\ *) exit 0 ;; + fixup!* | squash!*) exit 0 ;; +esac + +pattern='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9._/-]+\))?(!)?: .+' + +if printf '%s\n' "$subject" | grep -Eq "$pattern"; then + exit 0 +fi + +cat >&2 <<'EOF' +Commit message rejected: subject must follow Conventional Commits. + + type(scope): description + type!: description (breaking change) + + Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert + + Examples: + feat(projects): add prev/next navigation + fix: correct film embed aspect ratio + chore: remove unused project logos + +EOF + +exit 1 diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml new file mode 100644 index 0000000..783ca7f --- /dev/null +++ b/.github/workflows/jekyll.yml @@ -0,0 +1,57 @@ +name: Build and Deploy Jekyll Site + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v4 + + - name: Fetch latest YouTube film + run: ruby scripts/fetch-latest-film.rb + + - name: Build with Jekyll + run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" + env: + JEKYLL_ENV: production + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + deploy: + if: github.ref == 'refs/heads/main' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..083b96b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +_site/ +.jekyll-cache/ +.DS_Store diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..9c25013 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.6 diff --git a/404.html b/404.html new file mode 100644 index 0000000..df1ab59 --- /dev/null +++ b/404.html @@ -0,0 +1,18 @@ +--- +layout: default +--- + +

404. Computer says no.

+Page not found +

+
+The page you're looking for doesn't exist. +Maybe it did in the past, but it doesn't now. +Maybe it's a typo. +Maybe it's a mistake. +But we all make mistakes... Don't we? +We're all a bit lost sometimes. +
+
+It ain't too late to go back to the home page. +

diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..bef3d8b --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +marc.tt \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..bab59e6 --- /dev/null +++ b/Gemfile @@ -0,0 +1,9 @@ +source "https://rubygems.org" + +gem "jekyll", "~> 4.4.1" + +group :jekyll_plugins do + gem "webrick" # Required for Ruby 3.0+ + gem "jekyll-sitemap" + gem "jekyll-feed" +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..d721d5a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,168 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + base64 (0.2.0) + bigdecimal (3.1.9) + colorator (1.1.0) + concurrent-ruby (1.3.5) + csv (3.3.2) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + ffi (1.17.1) + ffi (1.17.1-aarch64-linux-gnu) + ffi (1.17.1-aarch64-linux-musl) + ffi (1.17.1-arm-linux-gnu) + ffi (1.17.1-arm-linux-musl) + ffi (1.17.1-arm64-darwin) + ffi (1.17.1-x86-linux-gnu) + ffi (1.17.1-x86-linux-musl) + ffi (1.17.1-x86_64-darwin) + ffi (1.17.1-x86_64-linux-gnu) + ffi (1.17.1-x86_64-linux-musl) + forwardable-extended (2.6.0) + google-protobuf (4.29.3) + bigdecimal + rake (>= 13) + google-protobuf (4.29.3-aarch64-linux) + bigdecimal + rake (>= 13) + google-protobuf (4.29.3-arm64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.29.3-x86-linux) + bigdecimal + rake (>= 13) + google-protobuf (4.29.3-x86_64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.29.3-x86_64-linux) + bigdecimal + rake (>= 13) + http_parser.rb (0.8.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + jekyll (4.4.1) + addressable (~> 2.4) + base64 (~> 0.2) + colorator (~> 1.0) + csv (~> 3.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + json (~> 2.6) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (~> 0.3, >= 0.3.6) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-feed (0.17.0) + jekyll (>= 3.7, < 5.0) + jekyll-sass-converter (3.0.0) + sass-embedded (~> 1.54) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + json (2.9.1) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.4.0) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (6.0.1) + rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.4.0) + rouge (4.5.1) + safe_yaml (1.0.5) + sass-embedded (1.83.4) + google-protobuf (~> 4.29) + rake (>= 13) + sass-embedded (1.83.4-aarch64-linux-android) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-aarch64-linux-gnu) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-aarch64-linux-musl) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-aarch64-mingw-ucrt) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-arm-linux-androideabi) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-arm-linux-gnueabihf) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-arm-linux-musleabihf) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-arm64-darwin) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-riscv64-linux-android) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-riscv64-linux-gnu) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-riscv64-linux-musl) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-x86_64-cygwin) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-x86_64-darwin) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-x86_64-linux-android) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-x86_64-linux-gnu) + google-protobuf (~> 4.29) + sass-embedded (1.83.4-x86_64-linux-musl) + google-protobuf (~> 4.29) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + unicode-display_width (2.6.0) + webrick (1.9.1) + +PLATFORMS + aarch64-linux + aarch64-linux-android + aarch64-linux-gnu + aarch64-linux-musl + aarch64-mingw-ucrt + arm-linux-androideabi + arm-linux-gnu + arm-linux-gnueabihf + arm-linux-musl + arm-linux-musleabihf + arm64-darwin + riscv64-linux-android + riscv64-linux-gnu + riscv64-linux-musl + ruby + x86-linux + x86-linux-gnu + x86-linux-musl + x86_64-cygwin + x86_64-darwin + x86_64-linux + x86_64-linux-android + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + jekyll (~> 4.4.1) + jekyll-feed + jekyll-sitemap + webrick + +BUNDLED WITH + 2.6.3 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5f8f555 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +MIT License + +Copyright (c) 2022 Dr. Sefer Bora Lisesivdin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- +Biscuit is derived from Solo Theme with the MIT License (MIT) +Solo - Copyright (c) 2015 Shu Uesugi +More: https://github.com/chibicode/solo/blob/gh-pages/LICENSE diff --git a/README.md b/README.md index fd32393..1e67df3 100644 --- a/README.md +++ b/README.md @@ -1 +1,30 @@ -# marc +# My Personal Website + +This is my GitHub Pages repo for my [personal website](https://marc.tt), based on the Jekyll theme Biscuit. + +## Installation + +### Prerequisites + +- Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) +- Install Bundler and Jekyll: + ```sh + gem install bundler jekyll + ``` + +### Clone and Run Locally + +1. Clone the repository: + ```sh + git clone https://github.com/YOUR_USERNAME/YOUR_REPO.git + cd YOUR_REPO + ``` +2. Install dependencies: + ```sh + bundle install + ``` +3. Serve the site locally: + ```sh + bundle exec jekyll serve + ``` +4. Open `http://127.0.0.1:4000/` in your browser. diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..a1f91cb --- /dev/null +++ b/_config.yml @@ -0,0 +1,45 @@ +# Site settings +title: Marc Beepath +description: Marc Beepath is a Software Engineer based in the UK from Trinidad & Tobago. +url: "https://marc.tt" +lang: "en" +author: "Marc Beepath" +youtube_channel_id: "UCKoxl9AEeGte0uLwQxunldQ" + +# Build settings +markdown: kramdown +exclude: ["README.md", "node_modules", "package.json", "package-lock.json"] +sass: + style: compressed + +# Plugins +plugins: + - jekyll-sitemap + - jekyll-feed + +# Collections +collections: + projects: + output: true + permalink: /:path/ + films: + output: true + permalink: /:path/ + +# Default front matter +defaults: + - scope: + path: "" + type: "posts" + values: + layout: "writings" + - scope: + path: "" + type: "projects" + values: + layout: "projects" + - scope: + path: "" + type: "films" + values: + layout: "films" diff --git a/_data/latest_film.yml b/_data/latest_film.yml new file mode 100644 index 0000000..ebfaaec --- /dev/null +++ b/_data/latest_film.yml @@ -0,0 +1,5 @@ +--- +id: 3EuLyWeoLWg +title: My Next 10 Years +published_at: '2025-06-25T20:59:58+00:00' +url: https://www.youtube.com/watch?v=3EuLyWeoLWg diff --git a/_films/goodbyeforever.md b/_films/goodbyeforever.md new file mode 100644 index 0000000..df61232 --- /dev/null +++ b/_films/goodbyeforever.md @@ -0,0 +1,91 @@ +--- +layout: films +title: "Goodbye Forever" +description: "Tommy tries to escape his ex and his friends throw him a party." +date: 2021-09-18 +image: /assets/films/goodbyeforever/1.webp +visit: https://youtu.be/HQOF-Jt8bSk +emoji: "🎉" +categories: films +permalink: goodbyeforever +--- + +At the time of writing this film, I had a big milestone happening in my life: I was moving from Trinidad to go study in the UK. +I remember being a bit nervous about it, then channelling that into an over the top comedy. +I knew I always wanted to do a mockumentary like The Office. +That style of filming and improv comedy felt so natural and attractive to me. +I was incredibly fortunate to work with the most amazing cast on this. + +## Behind the Story + +Fun fact: the party at the end of the film was an actual party we threw when we wrapped filming. + +
+ Matthew singing into a bottle of White Oak +
Matthew singing into a bottle of White Oak
+
+
+
+ Albert on the pool table +
Albert on the pool table
+
+
+
+ Mikayla downing some rum (we used water I think) +
Mikayla downing some rum (we used water I think)
+
+
+
+ Jabari getting drenched +
Jabari getting drenched
+
+
+
+ Albert again +
Albert again
+
+
+
+ Me spraying White Oak +
Me spraying White Oak
+
+
+
+ We were doing the YMCA I think +
We were doing the YMCA I think
+
+
+
+ I hope this was fruit juice +
I hope this was fruit juice
+
+
+
+ The gang saying our actual goodbyes +
The gang saying our actual goodbyes
+
+
+
+ One of the most memorable shoots I've done +
One of the most memorable shoots I've done
+
+ +## Cast & Crew + +- **Written & Directed** by Marc Beepath +- **Produced** by Raeis Jawad +- **Production Assistant** Teyen Bailey +- **Casting** by Caitlan Nandlal +- **Associate Producer** Daniel Baptiste +- **Story Editors** Ashley Jokhan, Michael Beepath +- **Sound Mixing** by Timothy Bally +- **Starring** + - Mikayla Almandoz as Kayla + - Jabari 'Black Maestro' Roach as Chong + - Matthew Samm as Jack + - Sareev Gangapersad as Jim + - Albert Smith as Alejandro + - Vinaya Mohan as Julia + - Feeyaz Mohammed as Gary + - Adeishalyn as the Cashier + - Marc Beepath as Tommy \ No newline at end of file diff --git a/_films/wrecked.md b/_films/wrecked.md new file mode 100644 index 0000000..cb2b69a --- /dev/null +++ b/_films/wrecked.md @@ -0,0 +1,56 @@ +--- +layout: films +title: "Wrecked" +description: "A young man tries to buy a car after he crashed his own." +date: 2021-01-31 +image: /assets/films/wrecked/1.webp +visit: https://youtu.be/q3DFanw9s40 +emoji: "🚗" +categories: films +permalink: wrecked +--- + +When the coronavirus hit, I was left at home with nothing to do. +I knew that I wanted to work with Daniel Batiste on a project, so we began brainstorming different ideas. +Eventually, that summer we put together a small team. +We had to navigate social distancing regulations at the time which was tiresome but we ended up creating this which I am quite proud of. + +Wrecked was an official selection for the 2021 TT Film Festival. + +
+ Polaroids taken for the film +
Polaroids taken for the film
+
+ +## Behind the Story + +Fun fact: We took 3 separate days to film the scene with Matthew (the car dealer) and Daniel (the protagonist). +The actual scene was less than 30 seconds long in the final cut. + +All stills are courtesy of Francois Elbourne (Droneswa Media). + +
+ Working through fight planning +
Working through fight planning
+
+
+
+ Filming in the rain +
Filming in the rain
+
+
+
+ The crew at my great aunt's kitchen at night +
The crew at my great aunt's kitchen at night
+
+ +## Cast & Crew + +- **Written & Directed** by Marc Beepath +- **Produced** by Daniel Baptiste & Marc Beepath +- **Starring** Daniel Baptiste, Bridget Kaley, Matthew Samm, Albert Smith & Joash Webb +- **Director of Photography** Raeis Jawad +- **Sound** by Travis Ragbir +- **Gimbal & Additional Photography** by Kai Murphy +- **Drone Operator & BTS** by Francois Elbourne +- **Score** by Timothy Bally diff --git a/_includes/post-nav.html b/_includes/post-nav.html new file mode 100644 index 0000000..d7acbdb --- /dev/null +++ b/_includes/post-nav.html @@ -0,0 +1,25 @@ +{% if page.previous or page.next %} + +{% endif %} diff --git a/_includes/scripts.html b/_includes/scripts.html new file mode 100644 index 0000000..6452cdd --- /dev/null +++ b/_includes/scripts.html @@ -0,0 +1,27 @@ + diff --git a/_includes/structured-data.html b/_includes/structured-data.html new file mode 100644 index 0000000..fe0cf82 --- /dev/null +++ b/_includes/structured-data.html @@ -0,0 +1,51 @@ + \ No newline at end of file diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..7a917ba --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + {{ page.title | default: site.title }} | {{ site.title }} + + + + + + + + + + + + + + + + + + + + {% if page.url == '/' %} + {% include structured-data.html %} + {% endif %} + + + + + + + + + + +
+ + {% unless page.url == "/" or page.url == "/index.html" %} + + {% endunless %} + + + {{ content }} +
+ + {% include scripts.html %} + + + diff --git a/_layouts/films.html b/_layouts/films.html new file mode 100644 index 0000000..52eadb1 --- /dev/null +++ b/_layouts/films.html @@ -0,0 +1,45 @@ +--- +layout: default +--- + +
+
+ {% if page.date %} + + {% endif %} + +

{{ page.title }}

+ + {% if page.description %} +

{{ page.description }}

+ {% endif %} + + {% if page.visit %} + + {% endif %} +
+ + {% if page.image %} +
+ {{ page.title }} +
+ {% endif %} + + {% assign yt_id = page.visit | split: '/' | last | split: '?' | first %} + {% if yt_id %} +
+ +
+ {% endif %} + +
+ {{ content }} +
+ + {% include post-nav.html %} +
diff --git a/_layouts/projects.html b/_layouts/projects.html new file mode 100644 index 0000000..645d37a --- /dev/null +++ b/_layouts/projects.html @@ -0,0 +1,55 @@ +--- +layout: default +--- + +
+ {% if page.image %} +
+ {{ page.title }} +
+ {% endif %} + +
+ {% if page.date %} + + {% endif %} + +

{{ page.title }}

+ + {% if page.description %} +

{{ page.description }}

+ {% endif %} + + {% if page.tech_stack %} +
+ {% for tech in page.tech_stack %}{{ tech }}{% endfor %} +
+ {% endif %} + + {% if page.visit or page.external_links %} + + {% endif %} +
+ +
+ {{ content }} +
+ + {% include post-nav.html %} +
\ No newline at end of file diff --git a/_layouts/writings.html b/_layouts/writings.html new file mode 100644 index 0000000..3822734 --- /dev/null +++ b/_layouts/writings.html @@ -0,0 +1,29 @@ +--- +layout: default +--- + +
+
+ {% if page.date %} + + {% endif %} + +

{{ page.title }}

+ + {% if page.description %} +

{{ page.description }}

+ {% endif %} +
+ + {% if page.image %} +
+ {{ page.title }} +
+ {% endif %} + +
+ {{ content }} +
+ + {% include post-nav.html %} +
diff --git a/_projects/creditcard.md b/_projects/creditcard.md new file mode 100644 index 0000000..223f2dd --- /dev/null +++ b/_projects/creditcard.md @@ -0,0 +1,25 @@ +--- +layout: projects +permalink: creditcard +categories: projects +title: "Random Forests & Credit Card Fraud" +description: "Research paper investigating the effectiveness of random forests in detecting credit card fraud." +external_links: + - label: Paper + url: "assets/projects/creditcard/paper.pdf" +date: 2025-05-16 +emoji: "🌳" +tech_stack: ["Python", "scikit-learn", "XGBoost"] +--- + +This group project explored how effectively Random Forest classifiers can detect credit card fraud, a domain where fraudulent transactions represent less than 1% of total data which makes detection extremely difficult due to class imbalance. + +We investigated: +- The impact of data balancing methods such as undersampling, oversampling and SMOTE on model performance. +- The role of model refinement techniques like boosting (XGBoost) and bagging and... +- How feature selection and hyperparameter tuning affect accuracy and recall. + +We found that, using the IEEE-CIS Fraud Detection dataset (590k+ transactions): +- The baseline Random Forest achieved high accuracy (98.7%) but poor recall (0.48), missing many fraud cases. +- Limited undersampling and feature selection improved recall to 0.65, achieving the best F1-score of 0.77. +- Boosting methods like XGBoost provided additional gains in recall and robustness. \ No newline at end of file diff --git a/_projects/cvfish.md b/_projects/cvfish.md new file mode 100644 index 0000000..9d6b426 --- /dev/null +++ b/_projects/cvfish.md @@ -0,0 +1,53 @@ +--- +layout: projects +permalink: cvfish +categories: projects +title: "CVFish" +description: "Helps students get their first jobs." +date: 2024-07-19 +emoji: "🎣" +tech_stack: ["React", "Node.js", "MongoDB", "NLP"] +--- + +
+ +
+ +## The Problem + +Many students struggle with: +- Creating effective CVs and cover letters +- Finding relevant entry-level positions +- Understanding job requirements +- Standing out in the application process + +## The Solution + +CVFish provided: +- CV builder and analyser +- Job matching algorithm +- Application tracking system +- Career guidance resources + +## Technical Stack + +- React.js frontend +- Node.js backend +- Natural Language Processing for CV analysis +- MongoDB database + +## The story behind CVFish + +Having worked at the Career Studio at the University of Liverpool part time for 2 whole years, I felt uniquely positioned to lend a helping hand to my fellow student colleagues. +I really started CVFish as the first project to document on Marc's Fieldnotes (a YouTube channel). +The idea was to take a startup from 0 to 1 and film the entire thing. +I soon found out, it is a monumental task to code alone - let alone film and make a compelling story. + +CVFish received some small initial traction however it struggled to retain recurring users. +Amidst a misconfigured server, it ended up racking up some unexpected costs (silly me) and thus I made the decision to cut my losses and take it down. +It is not currently deployed. \ No newline at end of file diff --git a/_projects/monty.md b/_projects/monty.md new file mode 100644 index 0000000..f8db611 --- /dev/null +++ b/_projects/monty.md @@ -0,0 +1,61 @@ +--- +layout: projects +permalink: monty +categories: projects +title: "Monty" +description: "A portfolio backtester tool for retail investors." +external_links: + - label: GitHub + url: "https://github.com/marcbeep/monty" +date: 2025-09-01 +emoji: "🐍" +tech_stack: ["React", "Python", "Flask", "Yahoo Finance API"] +--- + +
+ Screenshot of Monty +
Monty's Dashboard
+
+ +
+ +This was my final project for my Master's in Advanced Computer Science. + +Monty allows you to create a portfolio of assets and analyse it by using a historical backtest or monte carlo simulation. + +Features include: + +1. Dashboard showing key metrics, historic chart and portfolio holdings +2. Portfolio builder that has a fuzzy find search via YahooFinance API, and allows you to allocate percentages to assets +3. Backtester that allows: + - Stress Test using historic dates + - Monte Carlo simulation with projection bands chart and return distribution curve +4. Portfolio comparison tool + +## More Screenshots + +
+ Screenshot of Monty +
Monty's Portfolio Builder Tool
+
+ +
+ +
+ Screenshot of Monty +
Monty's Stress Test Results
+
+ +
+ +
+ Screenshot of Monty +
Monty's Monte Carlo results
+
+ +
+ +
+ Screenshot of Monty +
Monty's Portfolio Comparison Tool
+
diff --git a/_projects/spenny.md b/_projects/spenny.md new file mode 100644 index 0000000..42f4464 --- /dev/null +++ b/_projects/spenny.md @@ -0,0 +1,51 @@ +--- +layout: projects +permalink: spenny +categories: projects +title: "Spenny" +description: "A zero based budgeting tool." +visit: null +external_links: + - label: GitHub + url: "https://github.com/marcbeep/spenny" +date: 2024-06-01 +emoji: "💷" +tech_stack: ["React", "Node.js", "MongoDB"] +--- + +
+ +
+ +## The Problem + +Traditional budgeting tools often: +- Are complex and overwhelming +- Don't follow zero-based budgeting principles (ever dollar gets a job) +- Lack flexibility for different income patterns +- Don't provide actionable insights + +## The Solution + +Spenny offers: +- Simple zero-based budget creation +- Real-time expense tracking +- Custom category management +- Budget performance analytics +- Financial goal setting + +## Technical Stack + +- React.js frontend +- Node.js backend +- MongoDB database + +## The story behind Spenny + +I have always been a massive fan of the app [You Need A Budget](https://ynab.com). +Having been paranoid about not being in control of my finances, the concept of assigning every dollar to a job gave me comfort. +However, YNAB cost me some money per month (around £15). +I felt like I could reduplicate it myself. + +Given that I needed to work on something substantial for my Undergraduate Final Year Project, I thought: Why not give it a go? +I ended up scoring really well on my final dissertation and Spenny remains something I spent a lot of time on and I'm proud of. \ No newline at end of file diff --git a/_projects/wildroutes.md b/_projects/wildroutes.md new file mode 100644 index 0000000..5d2886a --- /dev/null +++ b/_projects/wildroutes.md @@ -0,0 +1,75 @@ +--- +layout: projects +permalink: wildroutes +categories: projects +title: "Wildroutes" +description: "Helps you discover nearby, off the beaten path adventures." +visit: null +date: 2023-05-10 +emoji: "🗺️" +tech_stack: ["Next.js", "shadcn/ui", "Google Maps API", "Node.js", "MongoDB"] +external_links: + - label: "Press Coverage" + url: "https://news.liverpool.ac.uk/2023/05/10/enterprising-students-win-design-your-future-awards/" + - label: "More Press" + url: "https://www.liverpool.ac.uk/careers/students/blog/enterprise/livunienterprisemarcsstory/" +--- + +
+ +
+ +## The Problem + +Traditional travel platforms: +- Focus on popular tourist destinations +- Overlook local hidden gems +- Contribute to overtourism +- Don't support local communities effectively + +## The Solution + +Wildroutes provides: +- Curated local adventure discoveries +- Community-driven recommendations +- Interactive route planning +- Local business partnerships +- Sustainable tourism promotion + +## Technical Stack + +- Next.js frontend with ShadcnUI components +- Google Maps API integration +- Node.js backend +- MongoDB for data storage + +## The story behind Wildroutes + +I first read a book called "Slow Adventures" and was enamoured by how many things there are to do for free outdoors. +In my second year at University, I was placed into a group project module. +So, I convinced everyone that Wildroutes would be an ideal project to pursue. +We ended up working really hard on that project throughout the Easter vacation of 2023. + +At the same time, the University of Liverpool was hosting a startup pitching competition with funding from Santander and a grand prize of £4000. +I figured it was worth a shot and delivered the inital pitch (shown above) and made it to the next round which was a live pitching event in front of around 100 people (Shark Tank Style with some judges). + +Wildroutes ended up winning first place in the Design Your Future Awards 2023! We've got photos to prove it. + +
+ Me & the other winners +
Me & the other winners
+
+ +
+ +
+ Me & friends with a big cheque +
Me & friends with a big cheque
+
+ +
+ +
+ My certificate +
My certificate
+
\ No newline at end of file diff --git a/_projects/worldtag.md b/_projects/worldtag.md new file mode 100644 index 0000000..3554a8e --- /dev/null +++ b/_projects/worldtag.md @@ -0,0 +1,46 @@ +--- +layout: projects +permalink: worldtag +categories: projects +title: "WorldTag" +description: "A digital product passport solution for clothing brands." +visit: "https://worldtag.co.uk" +date: 2024-10-29 +emoji: "🏷️" +tech_stack: ["Next.js", "Tailwind CSS"] +--- + +
+ +
+ +## The Problem + +The fashion industry lacks transparency. Consumers want to know more about their clothes: +- Where were they made? +- What materials were used? +- How sustainable is the production process? +- What's the environmental impact? + +## The Solution + +WorldTag provides a simple way for brands to: +- Create digital passports for their products +- Track the product journey +- Share sustainability credentials +- Engage with customers through product stories + +## The story behind WorldTag + +My friend [Virej](https://virejdasani.github.io) and I decided to come together to hack together this idea. +We spent time putting the above pitch together, building a landing page and putting together some demos for clothing companies. +We reached out to them to provide some feedback and were met with a lukewarm response. +Most brands simply were not aware of the impending regulation nor sensed the urgency to adopt a solution like this. + +Virej and I did submit this pitch to a Startup Accelerator and received the following feedback after being rejected: + +> We couldn't really see from the application why you would be the people to dominate this market and get ahead of the regulation coming down, particularly as it's likely some of the bigger retail players will be thinking of this already if they know this regulation is happening. + +Looking back on WorldTag, it was exciting to work on this with Virej. +Although I suppose, when creating anything new, there's always a chance of not reaching product market fit. +Hey ho, we'll take those lessons and roll 'em onto the next one ;) \ No newline at end of file diff --git a/_sass/_biscuit.scss b/_sass/_biscuit.scss new file mode 100644 index 0000000..7e6b3fa --- /dev/null +++ b/_sass/_biscuit.scss @@ -0,0 +1,826 @@ +:root { + /* Font Families */ + --font-primary: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Helvetica, sans-serif; + --font-mono: "JetBrains Mono", "SF Mono", Menlo, monospace; + + font-family: var(--font-primary); + + /* Foreground / background as RGB triplets for opacity layering */ + --fg: 31 31 31; /* oklch(0.13 0 0) approx */ + --bg: 253 253 253; /* oklch(0.995 0 0) approx */ + + /* Primary Color Palette */ + --color-primary: rgb(var(--fg)); + --color-secondary: rgb(var(--fg) / 0.45); + --color-accent: rgb(var(--fg)); + + /* Background Colors */ + --color-light: rgb(var(--bg)); + --color-dark: rgb(var(--fg)); + --color-quote-bg: rgb(var(--fg) / 0.03); + + /* Highlight Colors */ + --color-highlight-light: rgb(var(--fg) / 0.04); + --color-highlight-dark: rgb(var(--fg) / 0.1); + + /* Shadow Colors */ + --shadow-accent-light: rgb(var(--fg) / 0.08); + --shadow-accent-medium: rgb(var(--fg) / 0.12); + --shadow-subtle: rgb(var(--fg) / 0.05); + + /* Accent Color Variations */ + --accent-lighter: rgb(var(--fg) / 0.7); + --accent-darker: rgb(var(--fg)); + --accent-muted: rgb(var(--fg) / 0.55); +} + +html { + font: 16px/1.5 var(--font-primary); + -webkit-font-smoothing: antialiased; + background-color: var(--color-light); + color: var(--color-dark); + overflow-x: hidden; + width: 100%; + position: relative; +} + +/* ------------------------- */ +/* ENTRANCE ANIMATIONS */ +/* ------------------------- */ + +@keyframes fade-in-up { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-in { + animation: fade-in-up 600ms cubic-bezier(0.25, 0.1, 0.25, 1) both; + animation-delay: var(--delay, 0ms); +} + +@media (prefers-reduced-motion: reduce) { + .animate-in { + animation: none; + opacity: 1; + } +} + +body { + margin: 0 auto; + font-weight: 400; + background-color: var(--color-light); + color: var(--color-dark); + max-width: 100%; + padding: 0; + line-height: 1.6; + overflow-x: hidden; + width: 100%; + position: relative; + box-sizing: border-box; +} + +a { + color: var(--color-dark); + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 3px; + text-decoration-color: var(--color-accent); + font-weight: 500; + transition: text-decoration-color 0.2s, color 0.2s; +} + +a:hover, +a:focus { + color: var(--color-accent); + text-decoration-color: var(--color-accent); + background: none; +} + +a:active { + background-color: var(--color-highlight-dark); + color: var(--color-light); +} + +#footer { + display: flex; + flex-direction: column; + gap: 1.5rem; + padding: 2.5rem 1.5rem; + width: 100%; + max-width: 640px; + margin: 0 auto; + box-sizing: border-box; + font-size: 0.85rem; + + .footer-divider { + width: 100%; + height: 1px; + background: rgb(var(--fg) / 0.06); + border-radius: 1px; + margin-bottom: 0.5rem; + } + + .footer-social { + display: flex; + gap: 0.75rem; + + .social-link { + color: var(--color-secondary); + text-decoration: none; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5rem; + border-radius: 6px; + transition: all 0.2s ease; + + svg { + display: block; + } + + i { + font-size: 16px; + line-height: 1; + } + + &:hover { + color: var(--color-accent); + background: var(--color-highlight-light); + transform: translateY(-2px); + } + } + } + +} + +@media (max-width: 600px) { + #footer { + padding: 2rem 1.5rem; + gap: 1rem; + + .footer-social { + gap: 0.5rem; + + .social-link { + padding: 0.45rem; + + i { + font-size: 15px; + } + } + } + } +} + +#footer a { + color: var(--color-primary); + text-decoration: none; +} + +#footer a:hover, +#footer a:focus { + color: var(--color-secondary); +} + +#footer { + .footer-top { + display: flex; + flex-direction: column; + gap: 1.5rem; + align-items: flex-start; + + @media (min-width: 640px) { + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + } + } + + .footer-newsletter { + display: flex; + flex-direction: column; + gap: 0.75rem; + } + + .footer-pitch { + font-size: 0.8125rem; + color: rgb(var(--fg) / 0.45); + max-width: 18rem; + margin: 0; + } + + .footer-join { + display: inline-flex; + align-items: center; + gap: 0.375rem; + align-self: flex-start; + height: 2rem; + padding: 0 0.75rem; + border-radius: 0.5rem; + background: var(--color-dark); + color: var(--color-light); + font-size: 0.8125rem; + font-weight: 500; + text-decoration: none; + transition: background-color 0.2s ease; + + svg { + line-height: 0; + } + + &:hover { + background: rgb(var(--fg) / 0.85); + color: var(--color-light); + } + } + + .footer-social .social-link:hover { + transform: scale(1.1); + background: rgb(var(--fg) / 0.04); + } + + .footer-copyright { + font-size: 0.75rem; + color: rgb(var(--fg) / 0.25); + margin: 1.5rem 0 0 0; + } +} + +small { + color: var(--color-secondary); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-primary); + font-weight: 600; + -webkit-font-smoothing: antialiased; + margin: 1.5rem 0 1rem; + line-height: 1.3; + color: var(--color-dark); + letter-spacing: -0.02em; +} + +h1 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 2rem; +} + +@media (min-width: 48rem) { + h1 { + font-size: 2.75rem; + margin-bottom: 2.5rem; + } +} + +h1 a { + color: inherit; +} + +h2 { + font-size: 1.75rem; + margin-top: 2.5rem; + margin-bottom: 1rem; +} + +@media (min-width: 48rem) { + h2 { + margin-top: 2.5rem; + font-size: 1.6rem; + margin-bottom: 1rem; + } +} + +h3 { + font-size: 1.25rem; + margin-top: 2rem; + font-weight: 600; +} + +h4, +h5, +h6 { + margin-top: 1.5rem; + font-size: 1rem; + font-style: italic; + font-weight: bold; +} + +p, +ul, +ol, +dl, +table, +pre, +blockquote { + margin-top: 0; + margin-bottom: 1rem; +} + +ul, +ol { + padding-left: 1.5rem; +} + +dd { + margin-left: 1.5rem; +} + +blockquote { + margin: 1.5rem 0; + padding: 1rem 1.5rem; + background: var(--color-quote-bg); + border-radius: 0.25rem; + color: var(--color-secondary); + border-left: 3px solid var(--color-accent); + font-weight: 400; +} + +blockquote p:last-child { + margin-bottom: 0; +} + +hr { + border: none; + margin: 1.5rem 0; + border-bottom: 2px solid var(--color-accent); + position: relative; + top: -1px; +} + +.container img, +.container iframe { + max-width: 100%; +} + +.container img { + margin: 0 auto; + display: block; + border-radius: 8px; +} + +figcaption { + text-align: left; + font-size: 0.7rem; + color: var(--color-secondary); + margin-top: 0.5rem; + font-family: var(--font-mono); + font-weight: 600; + letter-spacing: -0.01em; +} + +.container { + max-width: 640px; + margin: 0 auto; + padding: 6rem 1.5rem 1rem 1.5rem; + width: 100%; + box-sizing: border-box; + overflow-x: hidden; +} + +@media (max-width: 768px) { + body { + font-size: 16px; + } +} + +.breadcrumb { + margin-top: 10px; + margin-bottom: 20px; + font-size: 0.8rem; + font-family: var(--font-mono); + font-weight: 600; + color: var(--color-secondary); + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; + + a { + color: var(--color-secondary); + text-decoration: none; + transition: color 0.2s ease; + position: relative; + padding: 0.2rem 0; + + &:hover { + color: var(--color-accent); + } + } + + /* Modern separator using a forward slash */ + > a:not(:last-child)::after { + content: "/"; + margin-left: 0.75rem; + color: var(--color-highlight-dark); + font-weight: 400; + opacity: 0.6; + font-size: 1.1rem; + position: relative; + top: -1px; + } + + /* Add subtle hover effect for the current page */ + > a:last-child { + color: var(--color-dark); + font-weight: 600; + } +} + +/* ------------------------- */ +/* NAVIGATION STYLES */ +/* ------------------------- */ + +.navbar { + margin: 0; + padding: 0; + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 1000; + background-color: rgb(var(--bg) / 0.6); + -webkit-backdrop-filter: saturate(150%) blur(20px); + backdrop-filter: saturate(150%) blur(20px); + color: var(--color-dark); + box-sizing: border-box; +} + +.navbar-container { + width: 100%; + max-width: 640px; + margin: 0 auto; + height: 3.5rem; + padding: 0 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + font-family: var(--font-primary); + box-sizing: border-box; +} + +/* Left brand signature */ +.navbar-brand { + display: inline-flex; + align-items: center; + + img { + height: 1.25rem; + width: auto; + display: block; + transition: opacity 0.2s ease; + } + + &:hover img { + opacity: 0.6; + } +} + +/* Desktop inline links */ +.navbar-links { + display: none; + align-items: center; + gap: 1.25rem; + + @media (min-width: 769px) { + display: flex; + } + + .nav-link { + font-size: 0.8125rem; + text-decoration: none; + color: rgb(var(--fg) / 0.45); + transition: color 0.2s ease; + + &:hover { + color: rgb(var(--fg) / 0.8); + } + + &.is-active { + color: var(--color-dark); + font-weight: 500; + } + } +} + +/* Mobile menu button (hamburger) */ +.menu-btn { + background: none; + border: none; + margin-right: -0.25rem; + padding: 0.375rem; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 8px; + color: rgb(var(--fg) / 0.5); + transition: color 0.2s ease, background-color 0.2s ease; + + &:hover { + color: var(--color-dark); + background-color: rgb(var(--fg) / 0.05); + } + + @media (min-width: 769px) { + display: none; + } + + .menu-icon-open, + .menu-icon-close { + display: inline-flex; + line-height: 0; + } + + .menu-icon-close { + display: none; + } + + &[aria-expanded="true"] { + .menu-icon-open { + display: none; + } + .menu-icon-close { + display: inline-flex; + } + } +} + +/* Mobile dropdown panel */ +.menu-dropdown { + display: grid; + grid-template-rows: 0fr; + overflow: hidden; + transition: grid-template-rows 0.3s cubic-bezier(0.25, 0.1, 0.25, 1); + + &.show-dropdown { + grid-template-rows: 1fr; + } + + @media (min-width: 769px) { + display: none; + } +} + +.menu-dropdown-clip { + min-height: 0; + overflow: hidden; +} + +.menu-dropdown-content { + background-color: rgb(var(--fg) / 0.03); + border-top: 1px solid rgb(var(--fg) / 0.1); + border-bottom: 1px solid rgb(var(--fg) / 0.1); + box-shadow: 0 12px 40px -4px rgb(var(--fg) / 0.15); + padding: 0.75rem 1.5rem; + display: flex; + flex-direction: column; + gap: 0.25rem; + + .nav-link { + display: block; + padding: 0.625rem 0.75rem; + margin: 0 -0.75rem; + border-radius: 8px; + color: rgb(var(--fg) / 0.7); + text-decoration: none; + font-size: 0.9375rem; + transition: color 0.2s ease, background-color 0.2s ease; + + &:hover { + color: var(--color-dark); + background-color: rgb(var(--fg) / 0.04); + } + + &.is-active { + color: var(--color-dark); + font-weight: 500; + background-color: rgb(var(--fg) / 0.06); + } + } +} + +::selection { + background-color: var(--color-highlight-light); + color: var(--color-dark); +} + +::-moz-selection { + background-color: var(--color-highlight-light); + color: var(--color-dark); +} + +/* Video Embed Styles */ +.video-container { + position: relative; + width: 100%; + padding-bottom: 56.25%; /* 16:9 Aspect Ratio */ + margin: 2rem 0; + border-radius: 8px; + overflow: hidden; +} + +.video-container iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: none; +} + +/* Add a subtle loading state */ +.video-container::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--color-highlight-light); + z-index: -1; +} + + +/* Update writing styles */ + +.writing-header { + margin-bottom: 3rem; +} + +/* Unified date styles for writings, films, and projects */ +.writing-date, +.film-date, +.project-date { + display: block; + font-size: 0.85rem; + color: var(--color-secondary); + font-family: var(--font-mono); + margin-bottom: 1rem; + font-weight: 400; + letter-spacing: 0.02em; + text-transform: uppercase; +} + +.writing-title { + margin: 0; + font-size: 2.25rem; + letter-spacing: -0.035em; + + @media (min-width: 48rem) { + font-size: 2.75rem; + } +} + +.writing-banner { + margin-bottom: 3rem; + + img { + width: 100%; + height: auto; + display: block; + border-radius: 1rem; + } +} + +@media (max-width: 768px) { + .writing-header { + margin-bottom: 2rem; + } + + .writing-date { + font-size: 0.8rem; + } +} + +/* Shared prev/next nav cards (writings, films, projects) */ +.post-nav { + margin-top: 5rem; + + .post-nav-divider { + width: 100%; + height: 1px; + background: rgb(var(--fg) / 0.06); + margin-bottom: 2rem; + } + + .post-nav-grid { + display: grid; + grid-template-columns: 1fr; + gap: 0.75rem; + + @media (min-width: 640px) { + grid-template-columns: 1fr 1fr; + } + } + + .post-nav-card { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 1.25rem; + border-radius: 1rem; + background: rgb(var(--fg) / 0.02); + border: 1px solid rgb(var(--fg) / 0.06); + text-decoration: none; + transition: background-color 0.3s ease, transform 0.3s ease; + + &:hover { + background: rgb(var(--fg) / 0.04); + transform: translateY(-2px); + + .post-nav-title { + color: rgb(var(--fg) / 0.8); + } + } + + &--next { + text-align: right; + + /* When next is the only card, keep it in the right column. */ + &:only-child { + @media (min-width: 640px) { + grid-column-start: 2; + } + } + + .post-nav-label { + justify-content: flex-end; + } + } + } + + .post-nav-label { + display: flex; + align-items: center; + gap: 0.375rem; + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; + color: rgb(var(--fg) / 0.25); + + svg { + line-height: 0; + } + } + + .post-nav-title { + font-size: 0.875rem; + font-weight: 500; + line-height: 1.4; + color: rgb(var(--fg) / 0.55); + transition: color 0.2s ease; + } +} + +/* Film Styles */ +.film { + margin-bottom: 3rem; +} + +.film-header { + margin-bottom: 3rem; +} + +.film-title { + margin: 0; + font-size: 2.25rem; + letter-spacing: -0.035em; + + @media (min-width: 48rem) { + font-size: 2.75rem; + } +} + +.film-banner { + margin-bottom: 3rem; + + img { + width: 100%; + height: auto; + display: block; + border-radius: 1rem; + } +} + +.film-content { + margin-top: 2rem; +} + +@media (max-width: 768px) { + .film-date { + font-size: 0.8rem; + } +} diff --git a/_sass/_contact.scss b/_sass/_contact.scss new file mode 100644 index 0000000..cb57958 --- /dev/null +++ b/_sass/_contact.scss @@ -0,0 +1,152 @@ +/* Contact page */ + +.contact { + a { + text-decoration: none; + font-weight: inherit; + } + + h1 { + font-size: 2.5rem; + font-weight: 700; + letter-spacing: -0.04em; + line-height: 1.1; + margin: 0 0 1.5rem 0; + color: var(--color-dark); + + @media (min-width: 48rem) { + font-size: 3.25rem; + } + } + + .contact-intro { + font-size: 1.0625rem; + color: rgb(var(--fg) / 0.45); + line-height: 1.6; + margin: 0 0 2rem 0; + } + + /* Email pill */ + .contact-email { + display: inline-flex; + align-items: center; + gap: 0.75rem; + padding: 0.875rem 1.25rem; + margin-bottom: 3.5rem; + border-radius: 1rem; + background: rgb(var(--fg) / 0.025); + border: 1px solid rgb(var(--fg) / 0.06); + transition: background-color 0.3s ease; + + i { + font-size: 1rem; + color: rgb(var(--fg) / 0.3); + } + + .contact-email-address { + font-size: 1.0625rem; + font-family: var(--font-mono); + font-weight: 500; + color: rgb(var(--fg) / 0.7); + transition: color 0.2s ease; + } + + .contact-arrow { + color: rgb(var(--fg) / 0.2); + line-height: 0; + transition: color 0.2s ease; + } + + &:hover { + background: rgb(var(--fg) / 0.04); + + .contact-email-address { + color: var(--color-dark); + } + + .contact-arrow { + color: rgb(var(--fg) / 0.4); + } + } + } + + /* Elsewhere list */ + .contact-label { + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: rgb(var(--fg) / 0.25); + margin: 0 0 1.25rem 0; + } + + .contact-links { + display: flex; + flex-direction: column; + gap: 0.25rem; + } + + .contact-link { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.875rem 1rem; + margin: 0 -1rem; + border-radius: 0.75rem; + transition: background-color 0.3s ease; + + .contact-link-left { + display: flex; + align-items: center; + gap: 0.75rem; + } + + i { + font-size: 1rem; + width: 1rem; + text-align: center; + color: rgb(var(--fg) / 0.25); + transition: color 0.2s ease; + } + + .contact-link-label { + font-size: 0.9375rem; + color: rgb(var(--fg) / 0.65); + transition: color 0.2s ease; + } + + .contact-link-right { + display: flex; + align-items: center; + gap: 0.5rem; + } + + .contact-handle { + font-size: 0.8125rem; + font-family: var(--font-mono); + color: rgb(var(--fg) / 0.2); + } + + .contact-arrow { + color: rgb(var(--fg) / 0.15); + line-height: 0; + transition: color 0.2s ease; + } + + &:hover { + background: rgb(var(--fg) / 0.025); + + i { + color: rgb(var(--fg) / 0.45); + } + + .contact-link-label { + color: var(--color-dark); + } + + .contact-arrow { + color: rgb(var(--fg) / 0.35); + } + } + } +} diff --git a/_sass/_films.scss b/_sass/_films.scss new file mode 100644 index 0000000..2b9a3fe --- /dev/null +++ b/_sass/_films.scss @@ -0,0 +1,152 @@ +/* Films */ + +.films { + a { + text-decoration: none; + font-weight: inherit; + } + + h1 { + font-size: 2.5rem; + font-weight: 700; + letter-spacing: -0.04em; + line-height: 1.1; + margin: 0 0 0.75rem 0; + color: var(--color-dark); + + @media (min-width: 48rem) { + font-size: 3.25rem; + } + } + + .films-intro { + font-size: 1.0625rem; + color: rgb(var(--fg) / 0.4); + line-height: 1.6; + margin: 0 0 3.5rem 0; + + a { + color: rgb(var(--fg) / 0.55); + transition: color 0.2s ease; + + &:hover { + color: var(--color-dark); + } + } + } + + .films-label { + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: rgb(var(--fg) / 0.25); + margin: 0 0 1.5rem 0; + } + + /* Latest film highlight */ + .film-latest { + margin-bottom: 3.5rem; + + .film-latest-head { + display: flex; + align-items: baseline; + gap: 0.625rem; + margin-bottom: 1.5rem; + min-width: 0; + } + + .films-label { + margin: 0; + flex-shrink: 0; + } + + .film-latest-sep { + color: rgb(var(--fg) / 0.15); + user-select: none; + } + + .film-latest-title { + font-size: 0.8125rem; + color: rgb(var(--fg) / 0.55); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + transition: color 0.2s ease; + + &:hover { + color: var(--color-dark); + } + } + + .film-latest-embed { + position: relative; + aspect-ratio: 16 / 9; + border-radius: 0.75rem; + overflow: hidden; + background: rgb(var(--fg) / 0.03); + box-shadow: 0 0 0 1px rgb(var(--fg) / 0.08); + + iframe { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border: none; + } + } + } +} + +/* Film detail */ +.film-excerpt { + font-size: 1.0625rem; + color: rgb(var(--fg) / 0.4); + line-height: 1.6; + margin: 0.75rem 0 0 0; +} + +.film-actions { + margin-top: 1.5rem; +} + +.film-watch { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: 0.375rem 0.875rem; + border-radius: 999px; + font-size: 0.8125rem; + font-weight: 500; + text-decoration: none; + background: var(--color-dark); + color: var(--color-light); + transition: background-color 0.2s ease; + + i { + font-size: 0.875rem; + } + + &:hover { + background: rgb(var(--fg) / 0.85); + color: var(--color-light); + } +} + +.film-embed { + position: relative; + width: 100%; + padding-bottom: 56.25%; + margin-bottom: 3rem; + border-radius: 1rem; + overflow: hidden; + background: rgb(var(--fg) / 0.03); + + iframe { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border: none; + } +} diff --git a/_sass/_home.scss b/_sass/_home.scss new file mode 100644 index 0000000..94db2c0 --- /dev/null +++ b/_sass/_home.scss @@ -0,0 +1,384 @@ +/* Homepage */ + +.home { + /* Drop the global underlined link style; sections re-enable it where wanted. */ + a { + text-decoration: none; + font-weight: inherit; + } + + /* Hero */ + .home-hero { + margin-bottom: 3.5rem; + } + + h1 { + font-size: 2.5rem; + font-weight: 700; + letter-spacing: -0.04em; + line-height: 1.1; + margin: 0; + color: var(--color-dark); + + @media (min-width: 48rem) { + font-size: 3.25rem; + } + } + + .home-intro { + font-size: 1.125rem; + color: rgb(var(--fg) / 0.45); + margin: 1.25rem 0 0 0; + line-height: 1.65; + max-width: 520px; + + @media (min-width: 48rem) { + font-size: 1.25rem; + } + + a { + color: rgb(var(--fg) / 0.65); + transition: color 0.2s ease; + + &:hover { + color: var(--color-dark); + } + } + } + + /* Hero photo */ + .home-photo { + position: relative; + margin: 0 auto 3.5rem auto; + max-width: 85%; + overflow: hidden; + border-radius: 1rem; + + img { + display: block; + width: 100%; + height: auto; + border-radius: 1rem; + filter: grayscale(1); + margin: 0; + } + + .photo-caption { + position: absolute; + bottom: 0.75rem; + right: 0.875rem; + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.02em; + color: rgb(255 255 255 / 0); + background: rgb(0 0 0 / 0); + -webkit-backdrop-filter: blur(0); + backdrop-filter: blur(0); + padding: 0.25rem 0.625rem; + border-radius: 0.375rem; + pointer-events: none; + transition: all 0.5s cubic-bezier(0.25, 0.1, 0.25, 1); + } + + &:hover .photo-caption { + color: rgb(255 255 255 / 0.7); + background: rgb(0 0 0 / 0.25); + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); + } + } + + /* Latest cards */ + .latest-grid { + display: grid; + grid-template-columns: 1fr; + gap: 0.75rem; + margin: 3.5rem 0; + + @media (min-width: 640px) { + grid-template-columns: repeat(3, 1fr); + } + } + + .latest-card { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 7.5rem; + padding: 1.25rem; + border-radius: 1rem; + background: rgb(var(--fg) / 0.025); + border: 1px solid rgb(var(--fg) / 0.06); + text-decoration: none; + color: var(--color-dark); + transition: background-color 0.3s ease, transform 0.3s ease; + + /* Film card wraps its content in an extra div populated via JS; + make it fill the card so top/bottom split matches the other cards. */ + #latest-film-content { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + } + + .latest-card-top { + display: flex; + align-items: center; + justify-content: space-between; + } + + .latest-label { + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; + color: rgb(var(--fg) / 0.3); + } + + .latest-arrow { + color: rgb(var(--fg) / 0.2); + line-height: 0; + transition: color 0.3s ease; + } + + .latest-title { + font-size: 0.9375rem; + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.3; + color: rgb(var(--fg) / 0.8); + margin: 0; + transition: color 0.3s ease; + } + + .latest-date { + font-size: 0.75rem; + color: rgb(var(--fg) / 0.3); + font-family: var(--font-mono); + margin: 0.25rem 0 0 0; + } + + &:hover { + background: rgb(var(--fg) / 0.05); + transform: translateY(-2px); + + .latest-arrow { + color: rgb(var(--fg) / 0.4); + } + + .latest-title { + color: var(--color-dark); + } + } + } + + /* Section label */ + .section-label { + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: rgb(var(--fg) / 0.3); + margin: 5rem 0 2rem 0; + } + + /* Timeline */ + .timeline { + display: flex; + flex-direction: column; + } + + .timeline-row { + display: grid; + grid-template-columns: 3.5rem 1fr; + gap: 1rem; + padding: 0.75rem 0; + border-bottom: 1px solid rgb(var(--fg) / 0.04); + + &:last-child { + border-bottom: 0; + } + + .timeline-date { + font-size: 0.8125rem; + font-family: var(--font-mono); + color: rgb(var(--fg) / 0.25); + padding-top: 1px; + } + + .timeline-content { + font-size: 0.9375rem; + color: rgb(var(--fg) / 0.55); + line-height: 1.6; + margin: 0; + + a { + color: rgb(var(--fg) / 0.75); + text-decoration: underline; + text-decoration-color: rgb(var(--fg) / 0.15); + text-decoration-thickness: 1px; + text-underline-offset: 3px; + transition: text-decoration-color 0.2s ease; + + &:hover { + text-decoration-color: rgb(var(--fg) / 0.4); + } + } + } + } + + /* Recommendations carousel */ + .recs { + position: relative; + } + + .recs-track { + display: flex; + overflow-x: auto; + scroll-snap-type: x mandatory; + scroll-behavior: smooth; + -ms-overflow-style: none; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + } + + .recs-slide { + flex: 0 0 100%; + width: 100%; + scroll-snap-align: start; + box-sizing: border-box; + } + + .rec-card { + border-radius: 1rem; + background: rgb(var(--fg) / 0.025); + border: 1px solid rgb(var(--fg) / 0.06); + padding: 1.5rem; + + .rec-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 1rem; + } + + .rec-person { + display: flex; + align-items: center; + gap: 0.75rem; + } + + .rec-avatar { + height: 2.5rem; + width: 2.5rem; + border-radius: 999px; + background: rgb(var(--fg) / 0.04); + margin: 0; + } + + .rec-name { + font-size: 0.9375rem; + font-weight: 600; + color: rgb(var(--fg) / 0.8); + margin: 0; + line-height: 1.3; + } + + .rec-role { + font-size: 0.75rem; + color: rgb(var(--fg) / 0.3); + font-family: var(--font-mono); + margin: 0.125rem 0 0 0; + } + + .rec-linkedin { + color: rgb(var(--fg) / 0.2); + line-height: 0; + margin-top: 0.25rem; + transition: color 0.2s ease; + + &:hover { + color: rgb(var(--fg) / 0.5); + } + } + + .rec-quote { + font-size: 0.875rem; + color: rgb(var(--fg) / 0.55); + line-height: 1.6; + margin: 0; + + .rec-quote-mark { + color: rgb(var(--fg) / 0.15); + font-size: 1.125rem; + font-family: Georgia, "Times New Roman", serif; + line-height: 1; + } + } + + .rec-date { + font-size: 0.6875rem; + color: rgb(var(--fg) / 0.2); + font-family: var(--font-mono); + margin: 1rem 0 0 0; + } + } + + .recs-controls { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + margin-top: 1rem; + } + + .recs-btn { + display: inline-flex; + align-items: center; + justify-content: center; + height: 1.75rem; + width: 1.75rem; + border-radius: 999px; + background: rgb(var(--fg) / 0.025); + border: 1px solid rgb(var(--fg) / 0.06); + color: rgb(var(--fg) / 0.3); + cursor: pointer; + line-height: 0; + transition: background-color 0.2s ease, color 0.2s ease; + + &:hover { + background: rgb(var(--fg) / 0.05); + color: rgb(var(--fg) / 0.6); + } + } + + .recs-dots { + display: flex; + align-items: center; + gap: 0.375rem; + } + + .recs-dot { + height: 0.375rem; + width: 0.375rem; + border-radius: 999px; + background: rgb(var(--fg) / 0.1); + border: none; + padding: 0; + cursor: pointer; + transition: width 0.3s ease, background-color 0.3s ease; + + &:hover { + background: rgb(var(--fg) / 0.2); + } + + &.is-active { + width: 1rem; + background: rgb(var(--fg) / 0.3); + } + } +} diff --git a/_sass/_normalize.scss b/_sass/_normalize.scss new file mode 100644 index 0000000..adcf187 --- /dev/null +++ b/_sass/_normalize.scss @@ -0,0 +1,406 @@ +/*! normalize.css v2.1.3 | MIT License | git.io/normalize */ + +/* ========================================================================== + HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined in IE 8/9. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} + +/** + * Correct `inline-block` display not defined in IE 8/9. + */ + +audio, +canvas, +video { + display: inline-block; +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9. + * Hide the `template` element in IE, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* ========================================================================== + Base + ========================================================================== */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* ========================================================================== + Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background: transparent; +} + +/** + * Address `outline` inconsistency between Chrome and other browsers. + */ + +a:focus { + outline: thin dotted; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* ========================================================================== + Typography + ========================================================================== */ + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari 5, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9, Safari 5, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari 5 and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Correct font family set oddly in Safari 5 and Chrome. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + font-size: 0.8em; +} + +/** + * Improve readability of pre-formatted text in all browsers. + */ + +pre { + white-space: pre-wrap; +} + +/** + * Set consistent quote types. + */ + +q { + quotes: "\201C" "\201D" "\2018" "\2019"; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* ========================================================================== + Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9. + */ + +img { + border: 0; +} + +/** + * Correct overflow displayed oddly in IE 9. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* ========================================================================== + Figures + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari 5. + */ + +figure { + margin: 0; +} + +/* ========================================================================== + Forms + ========================================================================== */ + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * 1. Correct font family not being inherited in all browsers. + * 2. Correct font size not being inherited in all browsers. + * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. + */ + +button, +input, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +button, +input { + line-height: normal; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. + * Correct `select` style inheritance in Firefox 4+ and Opera. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari 5 and Chrome + * on OS X. + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * 1. Remove default vertical scrollbar in IE 8/9. + * 2. Improve readability and alignment in all browsers. + */ + +textarea { + overflow: auto; /* 1 */ + vertical-align: top; /* 2 */ +} + +/* ========================================================================== + Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/_sass/_projects.scss b/_sass/_projects.scss new file mode 100644 index 0000000..472ee49 --- /dev/null +++ b/_sass/_projects.scss @@ -0,0 +1,203 @@ +/* Projects */ + +.projects { + a { + text-decoration: none; + font-weight: inherit; + } + + h1 { + font-size: 2.5rem; + font-weight: 700; + letter-spacing: -0.04em; + line-height: 1.1; + margin: 0 0 0.75rem 0; + color: var(--color-dark); + + @media (min-width: 48rem) { + font-size: 3.25rem; + } + } + + .projects-intro { + font-size: 1.0625rem; + color: rgb(var(--fg) / 0.4); + margin: 0 0 3rem 0; + } + + .projects-list { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + /* List row */ + .project-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + padding: 1rem; + margin: 0 -1rem; + border-radius: 0.75rem; + transition: background-color 0.3s ease; + + &:hover { + background: rgb(var(--fg) / 0.025); + + .project-name { + color: var(--color-dark); + } + + .project-arrow { + color: rgb(var(--fg) / 0.4); + } + } + } + + .project-row-head { + display: flex; + align-items: center; + gap: 0.625rem; + margin-bottom: 0.25rem; + } + + .project-emoji { + font-size: 1.125rem; + line-height: 1; + } + + .project-name { + font-size: 0.9375rem; + font-weight: 500; + letter-spacing: -0.01em; + color: rgb(var(--fg) / 0.8); + transition: color 0.2s ease; + } + + /* Excerpt and tags align under the title, past the emoji. */ + .project-excerpt { + font-size: 0.8125rem; + color: rgb(var(--fg) / 0.35); + line-height: 1.5; + margin: 0.125rem 0 0 0; + padding-left: calc(1.125rem + 0.625rem); + } + + .project-tags { + padding-left: calc(1.125rem + 0.625rem); + } + + .project-row-meta { + display: flex; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; + padding-top: 0.125rem; + } + + .project-year { + font-size: 0.75rem; + font-family: var(--font-mono); + color: rgb(var(--fg) / 0.2); + } + + .project-arrow { + color: rgb(var(--fg) / 0.15); + line-height: 0; + transition: color 0.2s ease; + } +} + +/* Shared tech-stack badges (list + detail) */ +.project-tags { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + margin-top: 0.5rem; +} + +.project-tag { + padding: 0.0625rem 0.5rem; + border-radius: 999px; + background: rgb(var(--fg) / 0.04); + color: rgb(var(--fg) / 0.3); + font-size: 0.6875rem; +} + +/* Detail header */ +.project-header { + margin-bottom: 2.5rem; + + .project-title { + font-size: 2.25rem; + font-weight: 700; + letter-spacing: -0.035em; + line-height: 1.1; + margin: 0; + + @media (min-width: 48rem) { + font-size: 2.75rem; + } + } + + .project-excerpt { + font-size: 1.0625rem; + color: rgb(var(--fg) / 0.4); + line-height: 1.6; + margin: 0.75rem 0 0 0; + } + + .project-tags { + margin-top: 1.25rem; + gap: 0.375rem; + } + + .project-tag { + padding: 0.125rem 0.625rem; + font-size: 0.75rem; + font-weight: 500; + background: rgb(var(--fg) / 0.05); + color: rgb(var(--fg) / 0.45); + } + + .project-links { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 1.5rem; + } + + .project-link { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: 0.375rem 0.875rem; + border-radius: 999px; + font-size: 0.8125rem; + font-weight: 500; + text-decoration: none; + background: rgb(var(--fg) / 0.05); + color: rgb(var(--fg) / 0.6); + transition: background-color 0.2s ease, color 0.2s ease; + + i { + font-size: 0.875rem; + } + + &:hover { + background: rgb(var(--fg) / 0.08); + color: rgb(var(--fg) / 0.8); + } + + &--primary { + background: var(--color-dark); + color: var(--color-light); + + &:hover { + background: rgb(var(--fg) / 0.85); + color: var(--color-light); + } + } + } +} diff --git a/_sass/_writings.scss b/_sass/_writings.scss new file mode 100644 index 0000000..c4cc49c --- /dev/null +++ b/_sass/_writings.scss @@ -0,0 +1,91 @@ +/* Writings */ + +.writings { + a { + text-decoration: none; + font-weight: inherit; + } + + h1 { + font-size: 2.5rem; + font-weight: 700; + letter-spacing: -0.04em; + line-height: 1.1; + margin: 0 0 0.75rem 0; + color: var(--color-dark); + + @media (min-width: 48rem) { + font-size: 3.25rem; + } + } + + .writings-intro { + font-size: 1.0625rem; + color: rgb(var(--fg) / 0.4); + line-height: 1.6; + margin: 0 0 3.5rem 0; + + p { + margin: 0 0 0.75rem 0; + + &:last-child { + margin-bottom: 0; + } + } + } + + .writings-year-group { + margin-bottom: 3rem; + } + + .writings-year { + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: rgb(var(--fg) / 0.25); + margin: 0 0 1rem 0; + } + + /* List row */ + .writing-row { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + padding: 0.625rem 0.75rem; + margin: 0 -0.75rem; + border-radius: 0.5rem; + transition: background-color 0.3s ease; + + &:hover { + background: rgb(var(--fg) / 0.025); + + .writing-row-title { + color: var(--color-dark); + } + } + } + + .writing-row-title { + font-size: 0.9375rem; + letter-spacing: -0.01em; + color: rgb(var(--fg) / 0.65); + transition: color 0.2s ease; + } + + .writing-row-date { + font-size: 0.75rem; + font-family: var(--font-mono); + color: rgb(var(--fg) / 0.2); + flex-shrink: 0; + } +} + +/* Detail excerpt */ +.writing-excerpt { + font-size: 1.0625rem; + color: rgb(var(--fg) / 0.4); + line-height: 1.6; + margin: 1rem 0 0 0; +} diff --git a/android-chrome-192x192.png b/android-chrome-192x192.png new file mode 100644 index 0000000..b5d88f5 Binary files /dev/null and b/android-chrome-192x192.png differ diff --git a/android-chrome-512x512.png b/android-chrome-512x512.png new file mode 100644 index 0000000..34e9e8b Binary files /dev/null and b/android-chrome-512x512.png differ diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000..fdbc6bf Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/assets/films/goodbyeforever/1.webp b/assets/films/goodbyeforever/1.webp new file mode 100644 index 0000000..cef3319 Binary files /dev/null and b/assets/films/goodbyeforever/1.webp differ diff --git a/assets/films/goodbyeforever/10.webp b/assets/films/goodbyeforever/10.webp new file mode 100644 index 0000000..3e752c0 Binary files /dev/null and b/assets/films/goodbyeforever/10.webp differ diff --git a/assets/films/goodbyeforever/11.webp b/assets/films/goodbyeforever/11.webp new file mode 100644 index 0000000..1e3444a Binary files /dev/null and b/assets/films/goodbyeforever/11.webp differ diff --git a/assets/films/goodbyeforever/2.webp b/assets/films/goodbyeforever/2.webp new file mode 100644 index 0000000..73443c5 Binary files /dev/null and b/assets/films/goodbyeforever/2.webp differ diff --git a/assets/films/goodbyeforever/3.webp b/assets/films/goodbyeforever/3.webp new file mode 100644 index 0000000..3e64ffe Binary files /dev/null and b/assets/films/goodbyeforever/3.webp differ diff --git a/assets/films/goodbyeforever/4.webp b/assets/films/goodbyeforever/4.webp new file mode 100644 index 0000000..80a9cb5 Binary files /dev/null and b/assets/films/goodbyeforever/4.webp differ diff --git a/assets/films/goodbyeforever/5.webp b/assets/films/goodbyeforever/5.webp new file mode 100644 index 0000000..d1b191c Binary files /dev/null and b/assets/films/goodbyeforever/5.webp differ diff --git a/assets/films/goodbyeforever/6.webp b/assets/films/goodbyeforever/6.webp new file mode 100644 index 0000000..d6b5612 Binary files /dev/null and b/assets/films/goodbyeforever/6.webp differ diff --git a/assets/films/goodbyeforever/7.webp b/assets/films/goodbyeforever/7.webp new file mode 100644 index 0000000..1f26564 Binary files /dev/null and b/assets/films/goodbyeforever/7.webp differ diff --git a/assets/films/goodbyeforever/8.webp b/assets/films/goodbyeforever/8.webp new file mode 100644 index 0000000..a710725 Binary files /dev/null and b/assets/films/goodbyeforever/8.webp differ diff --git a/assets/films/goodbyeforever/9.webp b/assets/films/goodbyeforever/9.webp new file mode 100644 index 0000000..1e47bf8 Binary files /dev/null and b/assets/films/goodbyeforever/9.webp differ diff --git a/assets/films/wrecked/1.webp b/assets/films/wrecked/1.webp new file mode 100644 index 0000000..af6a0c4 Binary files /dev/null and b/assets/films/wrecked/1.webp differ diff --git a/assets/films/wrecked/2.webp b/assets/films/wrecked/2.webp new file mode 100644 index 0000000..2964d09 Binary files /dev/null and b/assets/films/wrecked/2.webp differ diff --git a/assets/films/wrecked/3.webp b/assets/films/wrecked/3.webp new file mode 100644 index 0000000..b259541 Binary files /dev/null and b/assets/films/wrecked/3.webp differ diff --git a/assets/films/wrecked/4.webp b/assets/films/wrecked/4.webp new file mode 100644 index 0000000..2ba1425 Binary files /dev/null and b/assets/films/wrecked/4.webp differ diff --git a/assets/films/wrecked/5.webp b/assets/films/wrecked/5.webp new file mode 100644 index 0000000..f6b8650 Binary files /dev/null and b/assets/films/wrecked/5.webp differ diff --git a/assets/index/404.webp b/assets/index/404.webp new file mode 100644 index 0000000..9c7b581 Binary files /dev/null and b/assets/index/404.webp differ diff --git a/assets/index/home.png b/assets/index/home.png new file mode 100644 index 0000000..742e180 Binary files /dev/null and b/assets/index/home.png differ diff --git a/assets/index/navbar-logo-transparent.png b/assets/index/navbar-logo-transparent.png new file mode 100644 index 0000000..c5605bd Binary files /dev/null and b/assets/index/navbar-logo-transparent.png differ diff --git a/assets/index/og-default.png b/assets/index/og-default.png new file mode 100644 index 0000000..299ae4c Binary files /dev/null and b/assets/index/og-default.png differ diff --git a/assets/js/home.js b/assets/js/home.js new file mode 100644 index 0000000..6525068 --- /dev/null +++ b/assets/js/home.js @@ -0,0 +1,53 @@ +// Lightweight recommendations carousel: scroll-snap track + dots + prev/next. +(function () { + const track = document.getElementById("recs-track"); + const dotsContainer = document.getElementById("recs-dots"); + const prevBtn = document.getElementById("recs-prev"); + const nextBtn = document.getElementById("recs-next"); + + if (!track) return; + + const slides = Array.from(track.querySelectorAll(".recs-slide")); + if (slides.length === 0) return; + + let current = 0; + + const dots = slides.map((_, i) => { + const dot = document.createElement("button"); + dot.className = "recs-dot" + (i === 0 ? " is-active" : ""); + dot.setAttribute("aria-label", "Go to slide " + (i + 1)); + dot.addEventListener("click", () => scrollToIndex(i)); + dotsContainer && dotsContainer.appendChild(dot); + return dot; + }); + + function setActive(index) { + current = index; + dots.forEach((dot, i) => dot.classList.toggle("is-active", i === index)); + } + + function scrollToIndex(index) { + const clamped = Math.max(0, Math.min(index, slides.length - 1)); + track.scrollTo({ left: slides[clamped].offsetLeft, behavior: "smooth" }); + setActive(clamped); + } + + prevBtn && + prevBtn.addEventListener("click", () => + scrollToIndex(current === 0 ? slides.length - 1 : current - 1) + ); + nextBtn && + nextBtn.addEventListener("click", () => + scrollToIndex(current === slides.length - 1 ? 0 : current + 1) + ); + + // Keep dots in sync when the user swipes/scrolls manually. + let scrollTimer = null; + track.addEventListener("scroll", () => { + if (scrollTimer) window.clearTimeout(scrollTimer); + scrollTimer = window.setTimeout(() => { + const index = Math.round(track.scrollLeft / track.clientWidth); + setActive(Math.max(0, Math.min(index, slides.length - 1))); + }, 80); + }); +})(); diff --git a/assets/js/menu.js b/assets/js/menu.js new file mode 100644 index 0000000..de12d7c --- /dev/null +++ b/assets/js/menu.js @@ -0,0 +1,38 @@ +function toggleMenu() { + const menuBtn = document.querySelector('.menu-btn'); + const menuDropdown = document.getElementById('menuDropdown'); + const isExpanded = menuBtn.getAttribute('aria-expanded') === 'true'; + + menuBtn.setAttribute('aria-expanded', !isExpanded); + menuDropdown.classList.toggle('show-dropdown'); + + // Close menu when clicking outside + if (!isExpanded) { + document.addEventListener('click', closeMenuOnClickOutside); + } else { + document.removeEventListener('click', closeMenuOnClickOutside); + } +} + +function closeMenuOnClickOutside(event) { + const menuBtn = document.querySelector('.menu-btn'); + const menuDropdown = document.getElementById('menuDropdown'); + + if (!menuBtn.contains(event.target) && !menuDropdown.contains(event.target)) { + menuBtn.setAttribute('aria-expanded', 'false'); + menuDropdown.classList.remove('show-dropdown'); + document.removeEventListener('click', closeMenuOnClickOutside); + } +} + +// Close menu on ESC key +document.addEventListener('keydown', (event) => { + if (event.key === 'Escape') { + const menuBtn = document.querySelector('.menu-btn'); + const menuDropdown = document.getElementById('menuDropdown'); + + menuBtn.setAttribute('aria-expanded', 'false'); + menuDropdown.classList.remove('show-dropdown'); + document.removeEventListener('click', closeMenuOnClickOutside); + } +}); \ No newline at end of file diff --git a/assets/projects/creditcard/paper.pdf b/assets/projects/creditcard/paper.pdf new file mode 100644 index 0000000..0b7c293 Binary files /dev/null and b/assets/projects/creditcard/paper.pdf differ diff --git a/assets/projects/monty/1.png b/assets/projects/monty/1.png new file mode 100644 index 0000000..b6901e7 Binary files /dev/null and b/assets/projects/monty/1.png differ diff --git a/assets/projects/monty/2.png b/assets/projects/monty/2.png new file mode 100644 index 0000000..f1bcb5c Binary files /dev/null and b/assets/projects/monty/2.png differ diff --git a/assets/projects/monty/3.png b/assets/projects/monty/3.png new file mode 100644 index 0000000..522676c Binary files /dev/null and b/assets/projects/monty/3.png differ diff --git a/assets/projects/monty/4.png b/assets/projects/monty/4.png new file mode 100644 index 0000000..3b6e81f Binary files /dev/null and b/assets/projects/monty/4.png differ diff --git a/assets/projects/monty/5.png b/assets/projects/monty/5.png new file mode 100644 index 0000000..06f6f3a Binary files /dev/null and b/assets/projects/monty/5.png differ diff --git a/assets/projects/wildroutes/1.webp b/assets/projects/wildroutes/1.webp new file mode 100644 index 0000000..b4a0764 Binary files /dev/null and b/assets/projects/wildroutes/1.webp differ diff --git a/assets/projects/wildroutes/2.webp b/assets/projects/wildroutes/2.webp new file mode 100644 index 0000000..0c8e69f Binary files /dev/null and b/assets/projects/wildroutes/2.webp differ diff --git a/assets/projects/wildroutes/3.webp b/assets/projects/wildroutes/3.webp new file mode 100644 index 0000000..668ff31 Binary files /dev/null and b/assets/projects/wildroutes/3.webp differ diff --git a/assets/writings/2022/mike-1.webp b/assets/writings/2022/mike-1.webp new file mode 100644 index 0000000..e66b48b Binary files /dev/null and b/assets/writings/2022/mike-1.webp differ diff --git a/assets/writings/2022/mike-2.webp b/assets/writings/2022/mike-2.webp new file mode 100644 index 0000000..d6a9c1c Binary files /dev/null and b/assets/writings/2022/mike-2.webp differ diff --git a/assets/writings/2022/mike-3.webp b/assets/writings/2022/mike-3.webp new file mode 100644 index 0000000..d73f627 Binary files /dev/null and b/assets/writings/2022/mike-3.webp differ diff --git a/assets/writings/2022/mike-4.webp b/assets/writings/2022/mike-4.webp new file mode 100644 index 0000000..e72e4ec Binary files /dev/null and b/assets/writings/2022/mike-4.webp differ diff --git a/assets/writings/2022/mike-5.webp b/assets/writings/2022/mike-5.webp new file mode 100644 index 0000000..cafc3ca Binary files /dev/null and b/assets/writings/2022/mike-5.webp differ diff --git a/assets/writings/2023/speedrunning.webp b/assets/writings/2023/speedrunning.webp new file mode 100644 index 0000000..b1d7570 Binary files /dev/null and b/assets/writings/2023/speedrunning.webp differ diff --git a/assets/writings/2024/slow.webp b/assets/writings/2024/slow.webp new file mode 100644 index 0000000..f80d20e Binary files /dev/null and b/assets/writings/2024/slow.webp differ diff --git a/assets/writings/2026/immigrant.png b/assets/writings/2026/immigrant.png new file mode 100644 index 0000000..120be1e Binary files /dev/null and b/assets/writings/2026/immigrant.png differ diff --git a/contact.md b/contact.md new file mode 100644 index 0000000..72509cc --- /dev/null +++ b/contact.md @@ -0,0 +1,41 @@ +--- +layout: default +permalink: contact +title: Contact +description: Get in touch with Marc. +--- + +
+ +

Get in touch

+ +

My current timezone is London and I do my best to reply when I can.

+ + + + hi@marc.tt + + + +

Elsewhere

+ + + +
diff --git a/css/main.scss b/css/main.scss new file mode 100644 index 0000000..fd36f66 --- /dev/null +++ b/css/main.scss @@ -0,0 +1,13 @@ +--- +# Main scss file - imports all stylesheets +--- + +@charset "utf-8"; + +@use "normalize"; +@use "biscuit"; +@use "home"; +@use "contact"; +@use "projects"; +@use "writings"; +@use "films"; diff --git a/favicon-16x16.png b/favicon-16x16.png new file mode 100644 index 0000000..b5238ca Binary files /dev/null and b/favicon-16x16.png differ diff --git a/favicon-32x32.png b/favicon-32x32.png new file mode 100644 index 0000000..0a0076c Binary files /dev/null and b/favicon-32x32.png differ diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..15890ea Binary files /dev/null and b/favicon.ico differ diff --git a/films.md b/films.md new file mode 100644 index 0000000..3aefcd4 --- /dev/null +++ b/films.md @@ -0,0 +1,49 @@ +--- +layout: default +permalink: films +title: Films +description: A collection of Marc Beepath's films. +--- + +
+ +

Films

+

Making films is a massive hobby of mine. More on YouTube.

+ +{% assign yt = site.data.latest_film %} +{% if yt.id %} +
+
+

Latest Film

+ + {{ yt.title }} +
+
+ +
+
+{% endif %} + +

Notable Films

+
+ +
+ +
diff --git a/index.md b/index.md new file mode 100644 index 0000000..7484516 --- /dev/null +++ b/index.md @@ -0,0 +1,186 @@ +--- +layout: default +title: Home +--- + +
+ +
+

Hey, I'm Marc

+

Software Engineer from Trinidad and Tobago, based in London. I build products, write, and make short films.

+
+ +
+ Lisbon, 2024 + Lisbon, 2024 +
+ +
+ {% assign latest_project = site.projects | sort: "date" | last %} + {% if latest_project %} + +
+ Latest Project + +
+
+

{{ latest_project.title | truncate: 40 }}

+ {% if latest_project.date %}

{{ latest_project.date | date: "%b %Y" }}

{% endif %} +
+
+ {% else %} +
+
+ Latest Project + +
+
+

Coming soon

+
+
+ {% endif %} + + {% assign latest_post = site.categories.writings | first %} + {% if latest_post %} + +
+ Latest Writing + +
+
+

{{ latest_post.title | truncate: 40 }}

+

{{ latest_post.date | date: "%b %Y" }}

+
+
+ {% else %} +
+
+ Latest Writing + +
+
+

Coming soon

+
+
+ {% endif %} + + {% assign yt = site.data.latest_film %} + {% if yt.id %} + +
+ Latest Film + +
+
+

{{ yt.title | truncate: 40 }}

+ {% if yt.published_at %}

{{ yt.published_at | date: "%b %Y" }}

{% endif %} +
+
+ {% else %} + +
+ Latest Film + +
+
+

YouTube

+
+
+ {% endif %} +
+ +

Stuff I've done

+
+
+ 2025 +

Platform Engineer at LiveFlow

+
+
+ 2024 +

Software Engineer at Ultamation · MSc Advanced Computer Science

+
+
+ 2024 +

Graduated BSc Computer Science, University of Liverpool

+
+
+ 2023 +

Software Engineering Intern at Octopus Energy

+
+
+ 2023 +

Founded Wildroutes · Won Design Your Future startup competition

+
+
+ 2021 +

Directed Goodbye Forever and Wrecked (T&T Film Festival selection)

+
+
+ 2020 +

National scholarship for Computer Science · Duke of Edinburgh Gold

+
+
+ +

Folks I've worked with

+
+
+
+
+
+
+ +
+

Oliver Hall

+

Managing Director · Ultamation Ltd.

+
+
+ +
+

Marc demonstrated a keen ability to understand complex problems, develop technical strategies to address those challenges, and also align those strategies with the business needs. On top of that, he's also an incredibly personable guy which, on the one hand makes him a pleasure to work with, but on the other means he's a great communicator, able to share his technical knowledge and approach with the broader team.

+

Jul 2025

+
+
+
+
+
+
+ +
+

Jane Colesby

+

Career Studio Lead · University of Liverpool

+
+
+ +
+

His communication skills are excellent — he worked with students from so many different academic and cultural backgrounds and treated them all with empathy and respect. Marc is an excellent team player, willing to support his colleagues with sharing information and advice on his approaches. The wider team always expressed how impressed they were with Marc's contribution to their events, in public speaking and networking skills.

+

Jul 2024

+
+
+
+
+
+
+ +
+

Alistair Duncan

+

Lead Software Engineer · KrakenFlex

+
+
+ +
+

Marc requires little direction to deliver front-end tasks. He breaks down requirements systematically, asks thoughtful questions and meets deadlines ahead of schedule. I was consistently impressed with Marc's solutions during code reviews and pairing sessions. In addition to his strong technical skills, Marc is simply a pleasure to work with. He has an outgoing, friendly personality and a great sense of humour that made him well-liked across the team.

+

Aug 2023

+
+
+
+ +
+ +
+ +
+
+ +
+ + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fcda8e7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "marc", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "prettier": "^3.0.1" + } + }, + "node_modules/prettier": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.1.tgz", + "integrity": "sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b409e20 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "prepare": "git config core.hooksPath .githooks" + }, + "dependencies": { + "prettier": "^3.0.1" + } +} diff --git a/projects.md b/projects.md new file mode 100644 index 0000000..d68a9f2 --- /dev/null +++ b/projects.md @@ -0,0 +1,33 @@ +--- +title: Projects +layout: default +permalink: projects +description: A collection of Marc Beepath's projects. +--- + +
+ +

Projects

+

Things I've built and explored.

+ +
+{% assign sorted_projects = site.projects | sort: "date" | reverse %} +{% for project in sorted_projects %} + +
+
+ {% if project.emoji %}{{ project.emoji }}{% endif %} + {{ project.title }} +
+ {% if project.description %}

{{ project.description }}

{% endif %} + {% if project.tech_stack %}
{% for tech in project.tech_stack %}{{ tech }}{% endfor %}
{% endif %} +
+
+ {% if project.date %}{{ project.date | date: "%Y" }}{% endif %} + +
+
+{% endfor %} +
+ +
diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..a665178 --- /dev/null +++ b/robots.txt @@ -0,0 +1,9 @@ +# robots.txt for marc.tt +User-agent: * +Allow: / +Allow: /projects/ +Allow: /writings/ +Allow: /films/ + +# Sitemap location +Sitemap: https://marc.tt/sitemap.xml \ No newline at end of file diff --git a/scripts/fetch-latest-film.rb b/scripts/fetch-latest-film.rb new file mode 100755 index 0000000..9baaec6 --- /dev/null +++ b/scripts/fetch-latest-film.rb @@ -0,0 +1,41 @@ +#!/usr/bin/env ruby +# Fetches latest YouTube upload via public RSS feed (no API key). +require "net/http" +require "uri" +require "yaml" +require "fileutils" + +CHANNEL_ID = ENV.fetch("YOUTUBE_CHANNEL_ID", "UCKoxl9AEeGte0uLwQxunldQ") +ROOT = File.expand_path("..", __dir__) +OUT = File.join(ROOT, "_data", "latest_film.yml") + +def decode_xml_entities(s) + s.gsub("&", "&") + .gsub("<", "<") + .gsub(">", ">") + .gsub(""", '"') + .gsub("'", "'") + .gsub("'", "'") +end + +uri = URI("https://www.youtube.com/feeds/videos.xml?channel_id=#{CHANNEL_ID}") +xml = Net::HTTP.get(uri) +entry = xml.match(/[\s\S]*?<\/entry>/) +abort "No entries in YouTube RSS feed" unless entry + +body = entry[0] +id = body[/\([^<]+)\<\/yt:videoId\>/, 1] +title = body[/\([^<]+)\<\/title\>/, 1] +published = body[/\([^<]+)\<\/published\>/, 1] +abort "Could not parse YouTube RSS entry" unless id && title + +data = { + "id" => id.strip, + "title" => decode_xml_entities(title.strip), + "published_at" => published&.strip, + "url" => "https://www.youtube.com/watch?v=#{id.strip}" +} + +FileUtils.mkdir_p(File.dirname(OUT)) +File.write(OUT, data.to_yaml) +puts "Wrote #{OUT}: #{data['title']}" diff --git a/site.webmanifest b/site.webmanifest new file mode 100644 index 0000000..c0da41d --- /dev/null +++ b/site.webmanifest @@ -0,0 +1 @@ +{"name":"Marc Beepath","short_name":"Marc","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#000000","background_color":"#fafafa","display":"standalone"} \ No newline at end of file diff --git a/writings.md b/writings.md new file mode 100644 index 0000000..15faeef --- /dev/null +++ b/writings.md @@ -0,0 +1,29 @@ +--- +layout: default +permalink: writings +title: Writings +description: A collection of Marc Beepath's writings, thoughts, and insights on software engineering, technology, and personal experiences. +--- + +
+ +

Writings

+
+

These notes are the most honest version of myself I can provide. They are a snapshot into whatever I’m thinking most about at the minute. But beware, my opinions change frequently.

+

You should interpret these as me speaking to myself.

+
+ +{% assign posts_by_year = site.categories.writings | group_by_exp: "post", "post.date | date: '%Y'" | sort: "name" | reverse %} +{% for year in posts_by_year %} +
+

{{ year.name }}

+ {% for post in year.items %} + + {{ post.title }} + {{ post.date | date: "%b %-d" }} + + {% endfor %} +
+{% endfor %} + +
diff --git a/writings/_posts/2022/2022-03-05-life.md b/writings/_posts/2022/2022-03-05-life.md new file mode 100644 index 0000000..e81e1e3 --- /dev/null +++ b/writings/_posts/2022/2022-03-05-life.md @@ -0,0 +1,15 @@ +--- +layout: writings +title: Life +permalink: life +categories: writings +description: This essay is about life, and the accumulation of stories. +--- + +I have a great-aunt whom I absolutely love. She'll be 82 in July of 2022 and has accumulated a lot of stories throughout her life time. Some of them are funny, some questionable, but all are interesting. And that's a great thing to think about: We usually just spend our lives accumulating fascinating stories to tell other people. That's usually the means by which other people remember us in any event. They don't usually remember our individual character traits, but rather the specific stories that highlight our character. + +All of this to say, we all have incredible stories to tell. No matter how boring things might really seem, there's always some sort of valuable lesson that can be extracted from our regular, everyday experiences. + +Think about the funniest people you know. Who comes to mind? Is it Kevin Hart or George Carlin? Actually, the funniest people I know happen to be my friends and the people around me. Their account of experiences happening to them are some of the best stories I've ever heard. My favorite time at school would be when everyone was gathered in a circle and someone was telling some sort of story that happened to them - but usually exaggerated to get everyone to laugh. + +I suppose in a sense, we are all continuously writing our autobiographies without even realizing. We are constantly in pursuit of cool stories to tell. And that's a nice little way to look at things; because if you constantly chase after exciting things, then after a while you're going to have some exciting things to talk about. Why wouldn't you try to fill your life with exciting events? After all we're going to die one day. diff --git a/writings/_posts/2022/2022-03-05-mike.md b/writings/_posts/2022/2022-03-05-mike.md new file mode 100644 index 0000000..a578e06 --- /dev/null +++ b/writings/_posts/2022/2022-03-05-mike.md @@ -0,0 +1,23 @@ +--- +layout: writings +title: Mike's Philosophy +permalink: mike +categories: writings +description: These are my brother Michael's drawings on life. Drawn during the lockdown period of COVID-19. +--- + +At the beginning of the coronavirus pandemic, we had a lot of free time at home. My brother Michael started to draw. He'd use my iPad every night to make whatever sketch he was interested in, but eventually just decided that wasn't fun enough after a while. One morning I thought I'd delete some unused apps, so I went through the sketch app to try and determine if there was anything worth keeping. To my surprise, there was a rather touching comic-book like pane of art with dialogue taken from the award winning, Emmy® Nominated TV series: The Good Place. It struck a chord with me so I made sure I saved them. I think they have a deserved place here within this text. + +> Picture a wave. In the ocean. You can see it, measure it; it's height, the way the sunlight refracts when it passes through. And it's there. And you can see it, you know what it is. It's a wave. And then it crashes into the shore and it's gone. But the water is still there. The wave was just a different way for the water to be, for a little while. You know it's one concept of death for Buddhists. The wave returns to the ocean, where it came from and where it's supposed to be. + +We're all just waves in a bigger, grander ocean. And the idea is not to necessarily be the biggest wave or some sort of tsunami, but rather, it's just to simply enjoy being a wave. We exist at a specific moment in time and we are all confined to our present. This gets deeper and deeper but the general idea remains the same: appreciate moments as life gives them to you. Cherish them and share them with others. Be grateful that you have the opportunity to wake up everyday and make stories for yourself. + +![mike-1](/assets/writings/2022/mike-1.webp) + +![mike-2](/assets/writings/2022/mike-2.webp) + +![mike-3](/assets/writings/2022/mike-3.webp) + +![mike-4](/assets/writings/2022/mike-4.webp) + +![mike-5](/assets/writings/2022/mike-5.webp) diff --git a/writings/_posts/2022/2022-03-05-reading.md b/writings/_posts/2022/2022-03-05-reading.md new file mode 100644 index 0000000..927313e --- /dev/null +++ b/writings/_posts/2022/2022-03-05-reading.md @@ -0,0 +1,17 @@ +--- +layout: writings +title: Bring back Reading +permalink: reading +categories: writings +description: This essay is about reading. +--- + +Twitter democratized the sharing of stories for whoever was interested in reading them. + +The quality of information didn't matter. Once it could fit within 120 characters and had the ability to go viral, you had a banger tweet on your hands! We live in an age where the expedience of information is valued over the quality of information. + +Once we began reading in 120 character long increments, our attention spans began to become limited to that. The value for longform reading seemingly disappeared. Who has time to read long ass articles when we basically know what they mean from the headline anyway (right?). + +Cal Newport gave me this idea through his book "Digital Minimalism"! Let's bring reading back. Let's bring sharing our ideas with others back. Texts can't replace actual conversations with people and tweets surely can't be the medium to convey nuanced ideas. + +In a world where information is haphazardly thrown at you 24 hours a day, we need some peace and quiet. With clickbaity titles and thumbnails designed to capture your eyeballs, our brains are operating on information overload. When was the last time we sat down and read a piece of longform writing (and I'm not just talking about skimming)? diff --git a/writings/_posts/2022/2022-05-22-art.md b/writings/_posts/2022/2022-05-22-art.md new file mode 100644 index 0000000..caa7d78 --- /dev/null +++ b/writings/_posts/2022/2022-05-22-art.md @@ -0,0 +1,15 @@ +--- +layout: writings +title: The World doesn't care about your Art +permalink: art +categories: writings +description: This essay is about getting out of your own head. +--- + +I have no idea who you are. You might be someone with the next big business idea that will change the world, a lonesome author, a misunderstood singer or perhaps you’re a guy that likes to make dumb films in his free time (ahem). Whatever it is, I've got some news for you buddy: you're not a genius. + +There we go, like it or hate it, it’s the truth. In a world filled with information flying at us by the gigabyte, you are not the next Mozart. None of us are inherently “special” when it comes to what we create. We’d like to think we are, and this makes sense since we are all the centre of our own universes. With that being said, you’re also not not a genius. What does this mean? It means that you’re neither as terrible nor as good as you think you are. In fact, you’re somewhere in the middle. + +The world doesn't care about your art. + +Not everyone at least. There’s a billion people in the world. Maybe 5 of them (including your mom) are willing to give your demo a listen or read that draft of the novel you're writing. And that’s alright. Start small with people that support you. These are your true fans. Over time, you’ll gain many more true fans, but we all have to start somewhere. Your “garden” will grow organically and exponentially if you tend to the seedlings (your few true fans) carefully. diff --git a/writings/_posts/2022/2022-05-22-exclude.md b/writings/_posts/2022/2022-05-22-exclude.md new file mode 100644 index 0000000..58d4620 --- /dev/null +++ b/writings/_posts/2022/2022-05-22-exclude.md @@ -0,0 +1,15 @@ +--- +layout: writings +title: Proudly Exclude People +permalink: exclude +categories: writings +description: This essay is about understanding that what you build isn't for everyone. +--- + +You can afford to leave people out. If you make something for everyone, you end up making something for no one. Publish your work with confidence and irreverence. Will it turn some people off? Sure. But it's better to gain a few quality fans than a lot of useless eyeballs. Convert someone for life, not for the day. In turn, they will always support you in anything that you do. + +Find your voice. Shout it from rooftops. Keep doing it until the people that are looking for you find you (Dan Harmon said that, not me). + +Some of your own friends and family may not like what you do - and that's okay! A few of them will! Be sure and take good care of the people that like what you do. Show them that you know that they are there and that you appreciate them - genuinely! + +You can't find your voice if you don't use it. diff --git a/writings/_posts/2022/2022-05-22-garage.md b/writings/_posts/2022/2022-05-22-garage.md new file mode 100644 index 0000000..4828f7b --- /dev/null +++ b/writings/_posts/2022/2022-05-22-garage.md @@ -0,0 +1,11 @@ +--- +layout: writings +title: Work with the Garage Door Up +permalink: garage +categories: writings +description: This essay is about showing the process. +--- + +Let us behind the scenes of whatever you're making. Show us the steps behind the brushstrokes of your next painting. Show us the unfinished piece. Why? Because it's important to show the process. It's a form of "anti-marketing". + +Progress isn't linear. You don't keep producing hit after hit after hit. Even some of the greats produce duds (see: Once Upon a Time in Hollywood). Instead of showing the rosiest picture of your work, you would have shown the challenges you encountered. It builds a deeper, more authentic relationship with your audience/customers. This is a form of "watering the seedlings" in your garden. diff --git a/writings/_posts/2022/2022-05-22-perfectionism.md b/writings/_posts/2022/2022-05-22-perfectionism.md new file mode 100644 index 0000000..0489994 --- /dev/null +++ b/writings/_posts/2022/2022-05-22-perfectionism.md @@ -0,0 +1,9 @@ +--- +layout: writings +title: Perfectionism +permalink: perfectionism +categories: writings +description: This essay is about just doing things. +--- + +A lot of the time, we hide behind the guise of perfectionism because, in reality, we are just afraid that everyone will harshly judge what we produce. It’s difficult to start a business when your biggest fear is: “What if I open the door to customers and no one shows up”. So instead, you hide behind “getting the product exactly right” before you release it and it ends up taking months until - inevitably - you realise you don't want to do it anymore. Instead, you should give yourself permission to fail: fail cheaply and fail lots of times. diff --git a/writings/_posts/2022/2022-06-26-stuff.md b/writings/_posts/2022/2022-06-26-stuff.md new file mode 100644 index 0000000..37250c5 --- /dev/null +++ b/writings/_posts/2022/2022-06-26-stuff.md @@ -0,0 +1,23 @@ +--- +layout: writings +title: Love your Stuff +permalink: stuff +categories: writings +description: This essay is about owning less and doing more. +--- + +We accumulate so much stuff throughout our lives. +But we should love everything we own. + +This inherently means that over time we have accumulated things we don't love. +The solution? Aggressively evaluate the joy your belongings bring to you. If they don't spark joy, then we should thank these objects for their use and either: + +Donate +Recycle +Trash +I believe this should be done on a regular basis. + +Thanks to modern consumerism, we have grown to believe that things are constrained to one particular purpose and are limited to a certain time frame (think: a new iPhone is released every year, making the old ones seem obsolete and inferior). +But there is great satisfaction in fixing and reusing things we already own and love. We also tend to gain a deeper connection with our stuff as we spend more time with them. + +Don't buy a new one, fix the old one. diff --git a/writings/_posts/2022/2022-10-03-21-lessons.md b/writings/_posts/2022/2022-10-03-21-lessons.md new file mode 100644 index 0000000..1105e0c --- /dev/null +++ b/writings/_posts/2022/2022-10-03-21-lessons.md @@ -0,0 +1,40 @@ +--- +layout: writings +title: 21 Lessons in 21 years +permalink: 21lessons +categories: writings +description: This essay is about lessons I've learned, written on my 21st birthday. +--- + +## On relationships and communicating with others + +1. Learn people’s first names. To them, its the sweetest and most important sound in any language. Making a concerted effort to remember names is difficult, but rewarding (From Dale Carnegie) +2. As humans, our greatest desire is to be important. So appreciate people - but don’t flatter them. Be genuine and sincere. (From speaking with other people) +3. Be a good friend before seeking out good friends. When you find good friends, keep them close (From my own friends) +4. Seek to help people first and don’t ask for favors. When you really need something, make it very clear that you are asking for help and don’t brush it off as a simple request. (From Gary Vaynerchuck) +5. Actually listen to people. You don’t have to agree, but you do have to pay attention. (From Dale Carnegie) +6. Meaningful conversations cannot be replaced by superficial likes and a text message. With all my communication, if given a choice between sending a text or calling someone, I’d always call. (From Cal Newport) +7. Despite cultures and borders, at the end of the day, people are people, no different to you. (My mother when I expressed my fears of moving to a new country to study) + +## On Business and Finance + +8. The first rule of compounding is to never interrupt it unnecessarily. (Charlie Munger on the importance of slow and steady growth) +9. Always support the people that support you. (My father when I asked him “Dad, why would you pay your own client more money to do the same job when we can get a cheaper rate with this company?”) +10. There’s always a bigger fish. A bigger house. A better car. A more expensive watch. Take some time to be grateful. (From people that can never have enough) +11. Eat little and live long. (My father on the importance of sustainability in the ambition of making money) +12. Success isn’t for a chosen few, but is chosen by a few. (This is one of the oldest sayings I can remember. It’s plastered in big bold letters across my dad’s office) + +## On Productivity + +13. Perfectionism is just a lazy excuse for fear. (From Austin Kleon) +14. If more information was the answer, we’d all have abs. We’d all be rich. But it isn’t. So the difference is in action. (The Joe Rogan Podcast on the abundance of information we have access to) +15. Be an amateur and work with the garage door up. Cultivate a novice mindset and always be willing to learn from others and show your progress. (From Austin Kleon) +16. Treat failure as a regular part of your life. Think of it as a science experiment. The idea of iterative failure means that you make little tweaks each time and constantly improve. (From Josh Kaufman) + +## On Life in General + +17. A healthy person has 1000 wishes. A sick person has only one. So take care of yourself. (From a global pandemic) +18. Don’t try. Approach things with a lightness and curiosity. (From Charles Bukowski and Andy Puddicombe) +19. Picture a wave in the ocean. You can see it and measure it. But then it crashes on the shore and it’s gone. But the water is still there. The wave is just a different way for the water to be for a little while. (From an illustration my brother created reflecting on the transient nature of life) +20. You can find inner peace through simplicity and minimalism. (From Zen Buddhism. This principle is known as Kanso (簡素)) +21. Drive Slow. (From Kanye West in his song from Late Registration, always reminding me to take life easy) diff --git a/writings/_posts/2023/2023-01-01-books.md b/writings/_posts/2023/2023-01-01-books.md new file mode 100644 index 0000000..f3f5200 --- /dev/null +++ b/writings/_posts/2023/2023-01-01-books.md @@ -0,0 +1,21 @@ +--- +layout: writings +title: Marc's Book List +permalink: books +categories: writings +description: These are some books I recommend. +--- + +Last updated 7th July 2025 + +- Atomic habits (Clear, James) +- How to win friends and influence people (Carnegie, Dale) +- When breath becomes air (Kalanithi, Paul) +- Steal like an artist (Kleon, Austin) +- Show your work! (Kleon, Austin) +- The subtle art of not giving a f\*ck (Manson, Mark) +- Zen: the art of simple living (Masuno, Shunmyō) +- Digital minimalism (Newport, Cal) +- So good they can't ignore you (Newport, Cal) +- Siddhartha (Hesse, Hermann) +- Anything you want (Sivers, Derek) diff --git a/writings/_posts/2023/2023-01-01-goals.md b/writings/_posts/2023/2023-01-01-goals.md new file mode 100644 index 0000000..f761322 --- /dev/null +++ b/writings/_posts/2023/2023-01-01-goals.md @@ -0,0 +1,24 @@ +--- +layout: writings +title: How to set Goals +permalink: goals +categories: writings +description: This essay is about actionable goals. +--- + +There are 2 types of ways we can set goals: Lag measures vs Lead measures. + +Lag measures are those that are measure past performances. +For example, a goal using a lag measure might be "I want to gain 10kg of muscle by December 31st 2023." +You won't know if you've achieved your goal until December 31st 2023. +They're great if you have some big arbitrary target in your mind and if you're motivated by that. +But lag measures are not necessarily within your control. +They're also difficult to change. + +Lead measures are those which you have actionable control over. +The alternative to the above example using a lead measure might be "I will work out for 1 hour +everyday and consume 2700 calories per day." +It gives you a set of actions that you can follow and easily change if needed. +You're not guaranteed to gain 10kg of muscle mass by working out for an hour and consuming 7500 calories each day, but it puts the odds in your favour and gives you a sort of psychological comfort. + +This is the approach I take to my own goal setting. diff --git a/writings/_posts/2023/2023-01-02-amateur.md b/writings/_posts/2023/2023-01-02-amateur.md new file mode 100644 index 0000000..fdc96a0 --- /dev/null +++ b/writings/_posts/2023/2023-01-02-amateur.md @@ -0,0 +1,11 @@ +--- +layout: writings +title: Be an Amateur +permalink: amateur +categories: writings +description: This essay is about learning and looking stupid. +--- + +We are all amateurs of varying levels. It’s important to cultivate the mindset of being a novice. Amateurs aren’t afraid to make mistakes or look silly. They aren’t overly protective of their work and they don’t believe that whatever they produce should hang at The Louvre. Instead, they are vicious consumers of feedback. They produce work quickly for their small audience, and use the resulting feedback to iteratively improve their work. They are continuously learning. + +Everytime I make a film, I am humbled. It turns out way worse that I thought it would and I realise that I’m not as good as I thought I was. But I put it out anyway. Why? Because I enjoy the process. I enjoy it for the sake of doing it. That's not to say that the film is inherently "bad". It just means that it didn't match the picture of what I thought it was going to be in my head. And that's alright! diff --git a/writings/_posts/2023/2023-03-06-expectations.md b/writings/_posts/2023/2023-03-06-expectations.md new file mode 100644 index 0000000..bffee31 --- /dev/null +++ b/writings/_posts/2023/2023-03-06-expectations.md @@ -0,0 +1,26 @@ +--- +layout: writings +title: Letting go of Expectations +permalink: expectations +categories: writings +description: This essay is about gracefully accepting life. +--- + +Life does this funny thing where it never works out the way I've imagined. +It's become the rule now that if I hold some sort of expectation, the opposite usually happens. + +I've since accepted the fact that it is pretty much useless to have a detailed life plan past +maybe lunch time tomorrow. + +The only thing that is constant in life is change. +Health changes. Plans change. People change. +We should change with them. + +The more resistant we are to change, and the more expectations we possess, +the more difficult it is to adapt when things don't pan out. + +If we can somehow accept things as they are and remove our attachment to potential or alternate outcomes, +the present moment becomes easier. + +If we can appreciate things as they are instead of what they can be, +there's some joy in the gentleness of it all. diff --git a/writings/_posts/2023/2023-07-02-speedrunning.md b/writings/_posts/2023/2023-07-02-speedrunning.md new file mode 100644 index 0000000..97df688 --- /dev/null +++ b/writings/_posts/2023/2023-07-02-speedrunning.md @@ -0,0 +1,28 @@ +--- +layout: writings +title: Speedrunning in your 20s +permalink: speedrunning +categories: writings +image: /assets/writings/2023/speedrunning.webp +description: This essay is about relaxing. +--- + +A common feeling amongst people my age is to have everything in their life sorted by the age of 30. + +Haven’t gotten accepted into Oxford? _Failure._ +Haven’t earned your first million? _You might never._ +No bodybuilder physique? _Your prime years are running out._ + +There’s a pressure to achieve success and there’s a perceived deadline to do so. +And when most of us don’t attain envious levels of fortune (which is statistically obvious), we often feel defeated. + +These comparisons to others have been around since the dawn of time. Your own parents weren’t exempt from these pressures. But today, our pressures are fuelled by TikTok and Instagram. 19 year old Olympic athletes and affluent young prodigies captivate our attention. Indirectly, it makes us feel as though what we have achieved thus far is insignificant in comparison. + +This is toxic. The dissatisfaction with the present moment leads to a negative feedback loop where our "fulfillment of potential" is always postponed to sometime in the future. + +When the time comes and you've realised you haven't achieved as much as you thought you would, you'll just push your "deadline" further into then future. And then you'll be 90 before you know it. + +I believe hard work and reasonable expectations can exist together. We can look toward realistic goals and take our time. Block the noise out and understand that everyone takes their own time. + +So relax. You’re living. You’re breathing. You’re healthy. +Enjoy it. diff --git a/writings/_posts/2023/2023-11-25-service.md b/writings/_posts/2023/2023-11-25-service.md new file mode 100644 index 0000000..71ed957 --- /dev/null +++ b/writings/_posts/2023/2023-11-25-service.md @@ -0,0 +1,54 @@ +--- +layout: writings +title: Treat People Like People +permalink: service +categories: writings +description: This essay is about customer service.. +--- + +Pick up the damn phone! +No automated voice menus. +No emails with "do not reply" in the name. +No being on hold for an hour. +Let me talk to a person - quickly. +A person will understand what I'm trying to say way better than a machine. +For some reason, we've come to believe that we can outsource all the work to robots - even empathy. + +This rant, of course, is with respect to customer service these days. +If I'm a customer with a problem, I want it sorted immediately - not in 5 to 10 working days. + +We've all had experiences with poor customer service. +Trinidad (regrettably) is notorious for this - particularly in its food service industry. +I believe this might be due to a lack of structured empathy training. + +Universities in general are similarly frustrating. +They are inherently massive and made up of smaller specialised departments. +This often leads to a bureaucratic nightmare of different departments signposting you to other departments. +There's also frequent need for approvals from people "above". +This can take a simple request upward of 3 weeks to be solved. + +I often compare these examples to my time at Octopus Energy in Manchester. +Octopus Energy are a green energy supplier and they are obsessed with customer service. +As a growing young company - they should be. +They haven't yet established decades long trust from giants such as EDF or Scottish Power. +Their approach to customer service was unique: +Despite the technical nature of the industry, each service team (often 10 people or less), +had the adequate training (in both empathy & energy) to solve a customer's problems within that very team. +If a customer called with a problem, they'd often sort the customer's problem within that very phone call. +No transferring to other departments. +No long hold times. + +The moral? +Don't outsource your customer service to computers. +Your customers are your gold. +So treat them like gold. +They aren't "problem ticket numbers." +They are people. + +As Derek Sivers puts it: +Hire the sweetest, most empathetic people, and make sure they have all the time and resources they need to make your customers very happy. +If they get so busy that their interactions are getting succinct, it’s time to hire another. +It’s worth it. + +Treat your customers well and not only will they come back - they'll tell their friends. +Loud people are loud whether it's complaining or praising. diff --git a/writings/_posts/2024/2024-01-01-work.md b/writings/_posts/2024/2024-01-01-work.md new file mode 100644 index 0000000..0e3a3f4 --- /dev/null +++ b/writings/_posts/2024/2024-01-01-work.md @@ -0,0 +1,23 @@ +--- +layout: writings +title: Go to Work +permalink: work +categories: writings +description: This essay is about doing what's in your control. +--- + +Some time ago I spoke to a man in his mid-fifties. +He had amassed a fair portfolio of real estate and capital despite his humble career throughout his life. +As is my curious nature, I asked how he did it and if he had any tips for me as I was just about to start my career. +He replied with one sentence: “Get up everyday and go to work, everything else will take care of itself.” + +I won’t write some pretentious and overly-deep reflection on that phrase. +It just happened to stick with me. +As is human nature, we tend to overcomplicate everything. +We create intricate plans in our heads on just about every aspects of our life. + +But life itself isn’t that difficult if we strip away all the unnecessary complexities. +Focus on the process and results will follow. + +I’ll leave you with that for the new year. +Good luck! diff --git a/writings/_posts/2024/2024-04-02-slow.md b/writings/_posts/2024/2024-04-02-slow.md new file mode 100644 index 0000000..36883eb --- /dev/null +++ b/writings/_posts/2024/2024-04-02-slow.md @@ -0,0 +1,28 @@ +--- +layout: writings +title: A Slow Life +permalink: slow +categories: writings +image: /assets/writings/2024/slow.webp +--- + +> "You often feel tired, not because you've done too much, but because you've done too little of what sparks a light in you" - Alexander Den Heijer + +My friend Jun & I stayed in a surf camp for a couple days in Tenerife. +It was a Spanish styled hostel in the mountain and the residents were exactly as you'd expect. +There was a finance bro, a valley girl, a middle aged couple in search of adventure... you get the idea. +Each one of them was more cheerful than the last. + +Life slowed down for a little while. +It was a routine where we woke up, surfed for a bit, took a nap, and then surfed some more before dinner. +To some, it might seem like a pointless lifestyle, where you contribute nothing to society. +To others, it might seem alluring with the prospects of finding meaning within. +For me, the answer might lie somewhere in the middle. + +A slow life doesn't mean a life devoid of responsibility. +Instead, it might translate to a life lived intentionally. +Where we focus on fewer things, but we do them better. +Where our work and play are balanced and we do things with passion and love. + +Lately, I've found myself needing to remind myself to slow down. +Because if I don't do it myself, life has a funny way of doing it for me. diff --git a/writings/_posts/2024/2024-08-17-10-years.md b/writings/_posts/2024/2024-08-17-10-years.md new file mode 100644 index 0000000..a854b79 --- /dev/null +++ b/writings/_posts/2024/2024-08-17-10-years.md @@ -0,0 +1,59 @@ +--- +layout: writings +title: The Next 10 years +permalink: 10years +categories: writings +description: This essay is about careers. +--- + +In his book So Good They Can't Ignore You, Cal Newport argues that building a remarkable career is more about cultivating rare and valuable skills through deep work and deliberate practice. + +Going deep into a field or technology requires adopting a “craftsman mindset”. + +This means focusing on deliberate practice, continuous improvement, and getting really good at something, rather than just chasing after fleeting interests. + +Newport emphasises that depth is essential to mastery. + +Surface-level knowledge won't allow you to unlock the full potential of a technology. + +To truly understand and leverage a field, you need to engage in deep work—long periods of focused, distraction-free effort. + +This deep focus enables you to build rare and valuable skills, which Newport argues are key to gaining “career capital”. + +Once you have career capital—meaning you're so good they can't ignore you—you gain control over your work life and can craft the career you want. + +According to Newport, you should focus on acquiring career capital—valuable skills that are in demand in your field. + +Once you have built up significant career capital by going deep into a field, you gain leverage. + +This can lead to more control over your work (e.g., autonomy, flexibility) and better career opportunities, which Newport argues are the real ingredients of a fulfilling career. + +Newport argues that once you have developed significant skills in a particular field, you are better positioned to explore that technology's full potential. + +With deep expertise, you can start to innovate, understand what is truly possible, and push the boundaries of what can be done with that technology. + +By going deep, you gain the ability to see opportunities that others might miss, and you can apply your knowledge in creative ways that have a meaningful impact. + +So that’s what I’ll spend the next 10 years doing. + +Going deep in one field and perfecting my craft. + +What exactly will I go deep in? + +I’m not sure yet. I’m exploring. + +But that’s what Marc’s Fieldnotes is going to be about from now on - and in some senses - it’s what it’s always been about. + +Learning something new. + +Meeting new people. + +Visiting new places. + +And learning from these. + +Because all little moments of learning we collect along the way… + +They add up. + +And that’s called your life. diff --git a/writings/_posts/2024/2024-09-01-time.md b/writings/_posts/2024/2024-09-01-time.md new file mode 100644 index 0000000..1c6b09f --- /dev/null +++ b/writings/_posts/2024/2024-09-01-time.md @@ -0,0 +1,21 @@ +--- +layout: writings +title: There's Never Enough Time +permalink: time +categories: writings +description: This essay is about how you spend your time. +--- + +There are so many things to do, but it seems like there's never enough time to do them all. +Go to the gym, cook, study, go to work, read a bit, speak to friends - blah blah blah. +Sometimes it seems like there's a never ending checklist of things to do. +As I spoke about in my post [Speedrunning in your 20s](/speedrunning), we want our lives to improve but we want it to improve immediately. + +Little progress is misconstrued as no progress. +This leads to a cycle of always "re-evaluating" the plan, which leads to yo-yo gains. +Maybe the plan isn't wrong, maybe we didn't stick to it for long enough. + +-- +PS. I realise I'm verging into "self help guru" territory, which is not what I intend. +What I write about is a reflection of whatever I'm thinking about at the minute. +As for Marc's Fieldnotes, if you like the films and articles I make, the best way to support would be to share it (even if it's just to one other friend who might like it). diff --git a/writings/_posts/2024/2024-11-11-notiger.md b/writings/_posts/2024/2024-11-11-notiger.md new file mode 100644 index 0000000..a018f18 --- /dev/null +++ b/writings/_posts/2024/2024-11-11-notiger.md @@ -0,0 +1,19 @@ +--- +layout: writings +title: There is No Tiger +permalink: notiger +categories: writings +description: This essay is about anxiety. +--- + + +Humans have evolved over many years to be really good at sensing danger. +Back in the cave days we survived because we knew how to avoid being prey. +Hiding. Attacking. Avoiding it all together. + +We got so good at it, that those instincts remain with us today. +Except instead of a tiger waiting to eat us, it's probably a looming deadline. +But in reality, there are no immediate threats. +There's no life or death danger pressing us right now. + +There is no tiger. diff --git a/writings/_posts/2025/2025-03-09-cringe.md b/writings/_posts/2025/2025-03-09-cringe.md new file mode 100644 index 0000000..4ae601a --- /dev/null +++ b/writings/_posts/2025/2025-03-09-cringe.md @@ -0,0 +1,32 @@ +--- +layout: writings +title: Try not to Cringe +permalink: cringe +categories: writings +description: This essay is about trying. +--- + +A decade ago, the biggest fear around starting something new was “an aversion to failure.” +Today, it’s been replaced by something arguably worse: “an avoidance of being seen as cringe.” + +Putting yourself out there is hard - in just about anything. +In fact, doing anything that most people have not done will raise eyebrows, for better or worse. +Somewhere along the line, trying became uncool. + +Luckily for me, I’ve never been naturally good at anything in my life. +My friends that have known me throughout secondary school will happily back this up. +But this meant I was always trying to do things that I knew I’d probably never reach a level of success with. +Compare this to most others that never attempt anything that they are not immediately good at. + +I’ve always been frustrated with my lack of natural talent, but it’s now become an integral part of my personality that I’ve come to appreciate about myself. + +Back to being cringe. + +- It’s cringe to start a TikTok page where you crochet. +- It’s cringe to post YouTube covers singing. +- It’s cringe to start a blog where you write about Renaissance art. + +Basically, it’s cringe to do anything. +And things are only cringe… until they become successful. + +So you might as well be cringe. diff --git a/writings/_posts/2025/2025-04-07-fear.md b/writings/_posts/2025/2025-04-07-fear.md new file mode 100644 index 0000000..c1f874d --- /dev/null +++ b/writings/_posts/2025/2025-04-07-fear.md @@ -0,0 +1,104 @@ +--- +layout: writings +title: Fear & Uncertainty +permalink: fear +categories: writings +description: This essay is about tariffs and investing. +--- + + +Like most people in the world, my eyes have been glued to the news since Trump announced his tariffs on what he called “Liberation Day.” +I watched the event live and haven’t been able to turn my TV off since. + +If you’re not aware, Trump’s tariff plan applies to most countries around the world (including places like Norfolk Island, where mostly penguins live). + +A tariff, put simply, is not a new concept. +You might have paid “customs duty” on a package delivered from abroad. +It’s essentially the same thing. + +For example, let’s say you were a Caribbean island like Trinidad. +You might apply a 25% tariff on rum from outside CARICOM to protect the local rum industry (which is why White Oak is so cheap). + +Funnily enough, Trinidad also has a 30% tariff on DVD players. +I think someone added that and just forgot to remove it. +Bureaucracy at its finest. + +But importantly, tariffs are paid by consumers **within** a country. +They make buying foreign goods more expensive and incentivise the purchase of locally made products. +Tariffs are usually targeted at specific goods. + +So what happens when the world’s largest economy applies **blanket** tariffs on **every** country at a base rate of 10%? + +Markets panic. +That’s what happens. + +No one really knows the outcome of this economic policy, and when there’s uncertainty, that’s never good for business. +Large institutional investors need certainty, so they pull money out of equities and flock to safer assets like government bonds (or “gilts,” as they’re called in the UK). + +Long story short: millions and millions of people have opened their investment accounts and discovered that the value of their portfolios has dropped over the past three days, and there’s no end in sight. + +This, combined with media fear-mongering, will inevitably leave many feeling very worried (myself included). +This is the first major economic event that has directly impacted me (I was 7 during the 2008 financial crisis). + +If you know me personally, you know I’ve always been a big advocate for investing. +Three years ago, I opened an account and haven’t looked back since. + +That’s why I chose to write about investing during this period. +People only go on about it when things are going RIGHT. +I'd like to capture what's going on in my mind when everything seems to be going wrong. + +Firstly, you should understand my personal investing philosophy: + +| Principle | Belief | +| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| I don't invest money I need in the short term | I have an emergency fund that I don't touch. My investments are kept at a comfortable percentage of my wider portfolio. | +| I do not gamble | Money is important. I don’t invest in individual stocks or crypto or engage in day trading. | +| I keep it simple | I invest in ONE global index fund. Nothing more, nothing less. | +| I keep it diversified | That one fund (FTSE All-World UCITS ETF) includes around 3,600 stocks from major markets across the world. | +| I have a long-term time horizon | I believe in global progress over 50+ years. | +| I am consistent | I invest the same amount monthly via an automated debit, no matter what. | + +I’m not qualified to give financial advice, nor do I care to. + +But I do encourage my friends to start thinking about their savings. +People often raise the same objections to investing: + +1. **"The market is bad right now, I'll wait."** - No one, not even Warren Buffett, knows where the market will go. Think long term. +2. **“I don’t have enough money to invest.”** - This idea usually stems from the hefty account minimums that once existed in traditional investing. Nowadays, many brokers allow fractional shares, even as low as £1 or $1. The habit of consistent investing is more important than the amount. Personal finance books will tell you to "Pay Yourself First". That is, set aside some money for investing/saving first, before allocating to things like your rent/groceries etc. +3. **“I don’t know enough about it.”** - This objection disproportionately comes from my friends that are girls. My guess is that investing has not traditionally been seen as very "feminine". In my opinion, I view financial independence of a woman as a core principle of feminism. + +I do, however, believe people should consider their own circumstances and invest what they can. +This is why I will not disclose any personal details here to avoid any comparison. +People who encourage you to stick to very specific figures and percentages? +Use them as guides rather than strict rules. +Ultimately, do whatever allows you to sleep best at night. + +You should know: **the value of your investments _can_ go DOWN.** +My portfolio is currently in the red. +I can confirm first hand that the pain of losing is much greater than the joy of winning. + +But it's been red many times before. +And it's been green many times before. +I expect this will continue, with my portfolio fluctuating depending on the timeframe viewed. + +How does it feel? +Scary. +I’m human too. + +So I’ve deleted the brokerage app. +I’ll avoid watching the news. +I’ll stay level-headed and avoid impulsive decisions. + +As Charlie Munger puts it: +**“The most important thing about compounding is to never interrupt it unnecessarily.”** + +I’ll stay the course and remain disciplined. + +Because over the **long** term, this will go back to green. +I just can’t tell if it’ll take 3 months or 30 years. +Both scenarios are possible. + +And finally, before I sign off, I’ll leave you with this: + +> When you’re winning, you’re not as good as you think you are. +> When you’re losing, you’re not as bad as you think you are. diff --git a/writings/_posts/2025/2025-10-03-run.md b/writings/_posts/2025/2025-10-03-run.md new file mode 100644 index 0000000..b01ca8b --- /dev/null +++ b/writings/_posts/2025/2025-10-03-run.md @@ -0,0 +1,91 @@ +--- +layout: writings +title: A Morning Run +permalink: run +categories: writings +description: This essay is about Marc's recent morning run. +--- + +My iPhone’s alarm gently starts humming and I groggily reach over to press the Stop button. +They make it extra tiny now so you really have to focus to press it. +By now, however, I've memorised its exact positioning - an ability I'm not entirely proud of. +The air is dry and my fan is humming in the background. +The autumn weather is getting cooler, but it’s not quite terrible yet. + +I negotiate myself out of bed and scratch my crusty eyes looking at myself in the bedside mirror. +Everything is quiet. +The lock screen on my phone tells me the time is 5:03AM. +The Big Smoke hasn’t started bustling just yet. +But it soon will. + +I toss on a pair of black shorts and a black t-shirt (my de facto uniform) and give my neck a big crack. +Good night's rest. +The floor is cold but I make my way to the shoe rack and throw on my dark blue Brooks. +They’ve done me well since I bought them 2 years ago in Deansgate. +The toes are slowly giving away, but they have many more kilometres ahead of them. + +I decide to leave my AirPods inside this time, taking with me only my keys and my phone tucked inside my running belt. +I need to clear my head. +It seems as though I’ve not been able to have an independent thought for months without it being drowned out by whatever 80s rock band I was into that day. + +*** + +I begin at a measly pace jogging on the pavement of Whitechapel. +The concrete below my feet feels solid and resistant, almost as though it were pushing back at me. +It feels like a different world. +The purple bins of Liverpool have been swapped for the overflowing black bins marked “Tower Hamlets Council”. +I’ve not been here that long, yet that seems like a lifetime ago. +I pick the pace up and exhale. +I can just barely see my breath. +I can hear not much else except the thud of my shoe on the pavement. + +Another jogger joins behind me. +I turn my head to see if I can catch a glimpse of my competition but I cannot. +He too is dressed in all black, except for his Nike Cortez Shoes - just about the only thing I can make out. +They're hard to miss and outstandingly beautiful. +He's got the OG colourways: white uppers with the bright red Nike swoosh and a hint of blue on the sole. +This man is probably a professional. + +I give him a nickname in my mind: "Loser". +I try to villainise him. +He is now my arch nemesis and I cannot let him get past me. +I did not intend to race this morning, yet still I begin to quicken my pace. +In response, he also seems to pick up his own pace. +There is a certain amount of emotion building that I can't specifically identify. +He seems more fit than me, a bit taller and a bit more muscular. +Yet still, if this man were to pass me, he'd have to bleed first. +I feel as though I'm running away, though I'm not sure exactly what from. + +My heart begins to beat faster and my breathing is deep now. +For an odd moment, I think to myself: why do I even care? +This jogger probably doesn't even know he is locked into a mini race with me. +I ponder the idea for a minute before discarding it immediately. +To me, a man is most in danger not when he is fearful, but rather when he is apathetic to everything around him. + +My legs are now going near maximum speed and my vision begins to tunnel. +All I can hear now are just the sounds of his shoe and mine hitting the pavement. +The world around me seems to fade away. +The rhythm of the steps is like a song. +It's soothing like two drums working in tandem. +I lock into it. + +I forget all about the jogger behind me. +And for a moment, he even jogs alongside me on the opposite side of the road. +My eyes are focused ahead and I do not turn to meet him. +My thoughts switch from worrying about not being fast enough, to worrying if I'd ever figure out how fast I can truly go. + +I've lost track of the jogger now. +He might have turned into another street. +I am yet again on my own, the streets of Whitechapel all to myself. + +I pause for a second, taking a quick moment to look down at my phone screen. +My Strava tells me my heart rate zone is nearing 7 - whatever that means. +I take a quick peek at my notifications. +One's just come in. +It's from my mother and reads: "Happy Birthday Son." +I smile, knowing that she'd thought about when to send the message due to the time zone differences back home in Trinidad. +I look forward into the distance and mumble to myself, “Happy Birthday indeed buddy.” + +The sun is finding its way out now. +Ahead of me, I can see the London Skyline in all its poignant glory towering over me. +I take a deep breath before starting to run toward it once more. \ No newline at end of file diff --git a/writings/_posts/2025/2025-11-03-ambition.md b/writings/_posts/2025/2025-11-03-ambition.md new file mode 100644 index 0000000..2820f43 --- /dev/null +++ b/writings/_posts/2025/2025-11-03-ambition.md @@ -0,0 +1,100 @@ +--- +layout: writings +title: The Geography of Ambition +permalink: ambition +categories: writings +description: This essay is about moving to a city. +--- + +Most of my close friends know that I recently moved to London. +Telling them the news usually gets one of two reactions: +1. “Oh, that’s cool. Good luck.” +2. “Why would you ever want to do that? It’s too expensive. Too crowded.” + +During university, I took a course in Geographic Data Science where one assignment was to make choropleth maps of the UK based on different socio-economic variables. +London was an outlier for almost everything, so much so that we had to adjust the scales to stop it from skewing national averages. +The variable that stood out most to me? +Median household income. + +There’s this idea that **the strongest predictor of an individual’s future net worth is their postcode.** +It’s not a perfect rule, but it’s a useful heuristic. +Where you live probably matters. +Maybe more than we like to admit. + +--- + +## Cities and Ambition + +Let me backtrack. +There’s a well-known essay by Paul Graham (the guy who started Y Combinator, a startup accelerator in Silicon Valley) called Cities and Ambition. +He argues that every city sends an unspoken message about what it values. + +- New York tells you to get rich. +- Los Angeles tells you to get famous. +- Paris tells you to be stylish. + +Ambition, it turns out, clusters geographically. + +So what about London? +What does it value? + +It’s not helpful to think of a city as one monolith. +Instead, there are micro-ecosystems: +- Finance lives in 'the City'. +- Law firms are in Holborn. +- Media is in Soho. +- Tech companies are in Shoreditch. (That area near Old Street is actually called Silicon Roundabout. It's a terrible name, by the way.) + +Most people in London’s startup scene would tell you they’d rather be in San Francisco. +That’s where the investors are and where valuations are highest. +But for now, London is the best place to be in Europe. +So I moved as close to Shoreditch as I could afford, which ended up being Whitechapel. + +--- + +## Being Dream Adjacent + +Part of that decision was inspired by Quentin Tarantino, my favourite director, who once spoke on Joe Rogan’s podcast about hitting 25 and realising he’d spent years working a “dream-adjacent” job at a video store. +He loved talking about movies, but he wasn’t making them. +So he moved as close to Hollywood as he could (Koreatown) and that proximity put him within filmmaking’s 'orbit'. +The move didn’t make him a director overnight, but it made the dream tangible. + +His segment at the 40 minute mark is one of the greatest bits of any podcast I have ever listened to in my life. + + + +--- + +## The Network Effect + +There’s a concept called the network effect. +It's the idea that value increases with the number of participants. +Cities work the same way. +The more ambitious people you’re surrounded by, the more likely you are to encounter ideas, introductions, or coincidences that change your trajectory. + +Having now made the move to London, I’m deeply motivated by the agency of the people around me. +They move with a kind of confident optimism. +A productive delusion that things will work out. +I like to think I’m driven too, but the message from these people is clear: **work harder.** + +--- + +## The Reality + +Of course, moving to a big city, or another country entirely, comes with assumptions and privilege. +Visas, family obligations, and finances are very real barriers. + +So here’s the more general point: everywhere, in every country, has some place where ambitious people congregate. +Find that place. +Join them. + +Most people that read my essays are friends and people I've met along the way in life. +They are roughly my age (in their twenties), with relatively few responsibilities. +This essay is targeted towards a very specific subsection of that group. + +It’s probably not enough to just move somewhere and hope things click. +Success is built on a thousand small things: long nights, deep work, endless rejections. +It’s not really enough to be *near* the game. +You have to actually play with the intention to **win**. + +Still, of all the hard things on that list, moving and changing your environment might be the easiest to start with. \ No newline at end of file diff --git a/writings/_posts/2025/2025-12-03-authenticity.md b/writings/_posts/2025/2025-12-03-authenticity.md new file mode 100644 index 0000000..fbcf394 --- /dev/null +++ b/writings/_posts/2025/2025-12-03-authenticity.md @@ -0,0 +1,62 @@ +--- +layout: writings +title: Notes on Authenticity +permalink: authenticity +categories: writings +description: This essay is about being authentic. +--- + +I keep a running note on my iPhone called “Ideal Marc.” +He’s a fictionalised version of myself that I’ve invented and written down. +We won’t get into it too much here but he’s the version of myself that I aspire to be closer to and the one that I try to actualise everyday. +One of the notes for ideal Marc is that he is authentic in the way he acts and treats people. +And to explain what that means, it helps to start with the opposite. + +We can instinctively spot inauthentic people. +They’re warm in public and cold in private. +Often, they have some ulterior motive aimed at maximising their own personal gain. +The interaction feels more like a transaction. + +The relationships that have lasted the longest for me didn’t come from anything strategic. +They came from moments where I went out of my way to spend time with and help someone simply because I liked them. They weren’t immediately “useful” to me. +No quid pro quo. +No expectations. +And a surprising side effect is that people often show up for you later. +Not because you earned a debt, but because trust compounds. + +I’ve met many more people I didn’t click with than people I did. +When people first meet me, they generally have two extreme reactions: +1. This has to be the oddest person I’ve ever met. Let me move on. +2. This guy is very interesting. Let me stick around. + +Those reactions mellow a bit in professional settings, but you get my point. +Understanding that you aren’t going to be everyone’s cup of tea is an important part of being authentic. +A few years ago a friend asked me, “Marc, why do you want everyone to like you so much?” +I’ve thought about that a lot since then. +You can’t be authentic while auditioning for everybody. + +At its core, authenticity is alignment. +The insides of an authentic person line up with their outsides. +What they believe, feel, value, and want on the inside roughly matches what they say and do on the outside. +They aren’t performative. +They’re not playing a role that clashes with their real self. + +Authentic people don’t shrink or reshape themselves just to be palatable. +Often, you can feel them quickly. +Not because they’re loud, but because they’re coherent. +They act from values instead of approval seeking. +People naturally filter themselves out based on that. + +Authentic people also aren’t afraid to be seen. +That comes with vulnerability. +It means letting people see your real reactions, your confident approvals and disapprovals, showing excitement without worrying about looking cool, and admitting mistakes without turning it into a courtroom defense. + +Maybe the cleanest way I can say it is this: authenticity isn’t a perfectly defined identity you “find.” +It’s more like understanding yourself deeply and being willing to be honest with yourself about: + - what you feel in situations, and why +- how you feel about people, and whether that feeling is fair +- what actually matters to you beneath the noise + +Being authentic is like wearing clothes that fit your body, not clothes that fit someone else’s taste. +And just so you know, I generally feel most like myself in a plain white or plain black t-shirt ;) + diff --git a/writings/_posts/2026/2026-01-03-immigrants.md b/writings/_posts/2026/2026-01-03-immigrants.md new file mode 100644 index 0000000..264d4c5 --- /dev/null +++ b/writings/_posts/2026/2026-01-03-immigrants.md @@ -0,0 +1,64 @@ +--- +layout: writings +title: You should be an Immigrant +permalink: immigrant +categories: writings +description: Illustration by Michael Beepath +image: /assets/writings/2026/immigrant.png +--- + +I recently wrote an essay called [The Geography of Ambition](/ambition). +You can think of the following as a sort of addendum to that and should read it if you haven't yet. + +You should be an immigrant, even if it's for a short, defined period in your life. +Here's why. + +The word "immigrant" has been sullied and marketing terms such as "expat" have emerged. +But that's mainly so that people from countries with developed economies can differentiate themselves from the rest. +We shall do away with this pretension and uniformly refer to anyone living in a foreign country as an immigrant. + +People migrate for different reasons. +Some flee danger and others pursue opportunity. +I'm specifically referring to the latter here. + +I should point out the obvious: the ability to move to another country already implies some sort of relative privilege - whether financial or educational. +People against immigration will often argue these people should remain in their own countries and help build it out of patriotic and civic duty. +I believe this is down to an individual's own values rather than some sort of moral obligation. + +However, if we assume that humans are generally self-interested, rational agents, it then makes sense for someone to go where their skills are most highly valued. +Opportunity is not uniformly distributed around the globe. +This is a real challenge for developing economies that often face brain drains if wages and career prospects are not enticing enough for their skilled workers. + +However, to frame immigration purely as economic optimisation misses the more important point. + +Being an immigrant forces you into a position of productive discomfort. +You are suddenly illiterate again. +You don’t know the social shortcuts, the acceptable small talk and the invisible hierarchies. +Everyday tasks become exercises in humility. +You start speaking more slowly and listening more carefully. +Competence is contextual and confidence, stripped of familiarity, must be rebuilt from first principles. + +When you live in your country of origin, many things are handed to you invisibly such as language, cultural intuition, and social forgiveness. +As an immigrant, none of these are guaranteed and you earn them incrementally. +This has the useful side effect of separating what you are from what you’ve merely inherited. +You discover which parts of your identity are essential and which were simply convenient. + +There is also a moral education that comes with being foreign. +Once you have been "an outsider,” it becomes much harder to caricature outsiders elsewhere. +You gain a sort of empathy by having your own position weakened. + +You stop assuming that your norms are universal or optimal. +Many things you thought were “natural” are merely local maxima. +They are solutions optimised for a specific history, geography, and set of incentives. + +From a career perspective, immigrants tend to overlearn. +When your right to be somewhere feels provisional, in my own experience, you tend to prepare more thoroughly. +Over time, this compounds into an edge. +It isn't because immigrants are inherently superior, but because the environment demands more of them. + +So yes, opportunity may not be evenly distributed. +I'd argue that you should go wherever your skills are valued (this is increasingly changing and industry specific). +But don’t stop there. +Go where your assumptions will be challenged. +Go where you are slightly uncomfortable. +Go where you must explain yourself. \ No newline at end of file