We are more than labels
But people aren’t that simple. You can’t reduce a human being to one ideological checkbox. We’re ...
15 min read
<section className="w-[95%] lg:w-full border-x-[1px] border-b-[1px] border-[#FFFFFF]/[0.16] max-w-screen-lg m-auto relative flex flex-wrap">
<div className="absolute top-0 left-0 right-0 mx-auto w-1/3 h-full pointer-events-none border-[#FFFFFF]/[0.16] border-r-[1px] border-l-[1px] hidden md:block"></div>
<IconPlus className="absolute -bottom-3 -left-3 w-6 h-6" />
<IconPlus className="absolute -bottom-3 -right-3 w-6 h-6" />
<div className="w-full flex flex-col md:flex-row">
<div className="w-full md:w-1/3 flex flex-col p-6">
<Image src={ImgEstacio} width={150} alt="Estácio Logo" />
<h3 className="text-2xl font-bold w-full mt-4">
Software Engineer
</h3>
<p className="text-base mt-2">Bachelor{"'"}s Degree</p>
<small className="text-gray-400">
2022 - 2025 (Currently attending)
</small>
</div>
<div className="w-full md:w-1/3 flex flex-col p-6 border-[#FFFFFF]/[0.16] border-t-[1px] md:border-t-0">
<Image src={ImgEstacio} width={150} alt="Estácio Logo" />
<h3 className="text-2xl font-bold w-full mt-4">
Information Systems
</h3>
<p className="text-base mt-2">Bachelor{"'"}s Degree</p>
<small className="text-gray-400">2014 - 2015 (60 credits)</small>
</div>
<div className="w-full md:w-1/3 flex flex-col p-6 border-[#FFFFFF]/[0.16] border-t-[1px] md:border-t-0">
<Image src={ImgEasyComp} width={80} alt="EasyComp Logo" />
<h3 className="text-2xl font-bold w-full mt-4">Web Design</h3>
<p className="text-base mt-2">Certificate Program</p>
<small className="text-gray-400">2011 - 2012</small>
</div>
</div>
</section><section class="w-[95%] lg:w-full border-x-[1px] border-b-[1px] border-[#FFFFFF]/[0.16] max-w-screen-lg m-auto relative flex flex-wrap">
<div class="absolute top-0 left-0 right-0 mx-auto w-1/3 h-full pointer-events-none border-[#FFFFFF]/[0.16] border-r-[1px] border-l-[1px] hidden md:block"></div>
<%= lucide_icon('plus', class: 'absolute -bottom-3 -left-3 w-6 h-6') %>
<%= lucide_icon('plus', class: 'absolute -bottom-3 -right-3 w-6 h-6') %>
<div class="w-full flex flex-col md:flex-row">
<div class="w-full md:w-1/3 flex flex-col p-6">
<img src="<%= asset_path('estacio-logo-1024x260.png') %>" alt="Estácio Logo" class="w-[150px]" />
<h3 class="text-2xl font-bold w-full mt-4">
Software Engineer
</h3>
<p class="text-base mt-2">Bachelor's Degree</p>
<small class="text-gray-400">
2022 - 2026 (Currently attending)
</small>
</div>
<div class="w-full md:w-1/3 flex flex-col p-6 border-[#FFFFFF]/[0.16] border-t-[1px] md:border-t-0">
<img src="<%= asset_path('estacio-logo-1024x260.png') %>" alt="Estácio Logo" class="w-[150px]" />
<h3 class="text-2xl font-bold w-full mt-4">
Information Systems
</h3>
<p class="text-base mt-2">Bachelor's Degree</p>
<small class="text-gray-400">2014 - 2015 (60 credits)</small>
</div>
<div class="w-full md:w-1/3 flex flex-col p-6 border-[#FFFFFF]/[0.16] border-t-[1px] md:border-t-0">
<img src="<%= asset_path('logo-easy-comp.png') %>" alt="EasyComp Logo" class="w-[80px]" />
<h3 class="text-2xl font-bold w-full mt-4">Web Design</h3>
<p class="text-base mt-2">Certificate Program</p>
<small class="text-gray-400">2011 - 2012</small>
</div>
</div>
</section>'use client';
import { motion } from "framer-motion";
const HandWave = () => {
return (
<motion.span
transition={{
duration: 5,
delay: 1,
ease: "easeInOut",
}}
animate={{
rotate: [0, 35, -25, 25, -35, 0],
}}
initial={{
rotate: 0,
}}
className="inline-block"
>
👋
</motion.span>
);
};
export default HandWave;<span data-controller="hand-wave" class="hand-wave inline-block">👋</span>
@keyframes wave {
0% {
transform: rotate(0deg);
}
20% {
transform: rotate(35deg);
}
40% {
transform: rotate(-25deg);
}
60% {
transform: rotate(25deg);
}
80% {
transform: rotate(-35deg);
}
100% {
transform: rotate(0deg);
}
}
.wave-animation {
animation: wave 5s ease-in-out 1;
}import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
connect() {
setTimeout(() => {
this.element.classList.add("wave-animation");
}, 1000); // 1s delay
}
}"use client";
import { useMemo, useRef, useState } from "react";
import {
motion,
useScroll,
useTransform,
useSpring,
} from "framer-motion";
import { ProductCard } from "./ProductCard";
import ProductModal from "./ProductModal";
export interface Product {
title: string;
name: string;
thumbnail: string;
slides: string[];
description: string;
};
interface HeroParallaxProps {
products: Product[];
};
const HeroParallax: React.FC<HeroParallaxProps> = ({
products,
}) => {
const [currentProduct, setCurrentProduct] = useState<Product | null>(null);
const firstRow = useMemo(() => products.slice(0, 5), [products]);
const secondRow = useMemo(() => products.slice(5, 10), [products]);
const thirdRow = useMemo(() => products.slice(10, 15), [products]);
const ref = useRef(null);
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start start", "end start"],
});
const springConfig = { stiffness: 300, damping: 30, bounce: 100 };
const translateX = useSpring(
useTransform(scrollYProgress, [0, 1], [0, 1000]),
springConfig
);
const translateXReverse = useSpring(
useTransform(scrollYProgress, [0, 1], [0, -1000]),
springConfig
);
const rotateX = useSpring(
useTransform(scrollYProgress, [0, 0.2], [15, 0]),
springConfig
);
const opacity = useSpring(
useTransform(scrollYProgress, [0, 0.2], [0.2, 1]),
springConfig
);
const rotateZ = useSpring(
useTransform(scrollYProgress, [0, 0.2], [20, 0]),
springConfig
);
const translateY = useSpring(
useTransform(scrollYProgress, [0, 0.2], [-700, 500]),
springConfig
);
return (
<>
<div
ref={ref}
className="h-[300vh] py-40 relative flex flex-col self-auto [perspective:1000px] [transform-style:preserve-3d]"
>
<div className="max-w-screen-lg relative mx-auto py-20 md:py-40 px-4 w-full left-0 top-0">
<h1 className="text-2xl md:text-7xl font-bold text-neutral-200">
My Work
</h1>
<p className="max-w-2xl text-base md:text-xl mt-8 text-neutral-300">
I bring websites and apps to life, from idea to launch. See what I've built.
</p>
</div>
<motion.div
style={{
rotateX,
rotateZ,
translateY,
opacity,
}}
>
<motion.div className="flex flex-row-reverse space-x-reverse space-x-20 mb-20">
{firstRow.map((product) => (
<ProductCard
product={product}
translate={translateX}
key={product.title}
onClick={() => setCurrentProduct(product)}
/>
))}
</motion.div>
<motion.div className="flex flex-row mb-20 space-x-20 ">
{secondRow.map((product) => (
<ProductCard
product={product}
translate={translateXReverse}
key={product.title}
onClick={() => setCurrentProduct(product)}
/>
))}
</motion.div>
<motion.div className="flex flex-row-reverse space-x-reverse space-x-20">
{thirdRow.map((product) => (
<ProductCard
product={product}
translate={translateX}
key={product.title}
onClick={() => setCurrentProduct(product)}
/>
))}
</motion.div>
</motion.div>
</div>
<ProductModal
product={currentProduct}
onRequestClose={() => setCurrentProduct(null)}
/>
</>
);
};
export default HeroParallax;"use client";
import React from "react";
import {
motion,
MotionValue,
} from "framer-motion";
import Image from "next/image";
interface ProductCardProps {
product: {
title: string;
name: string;
thumbnail: string;
};
translate: MotionValue<number>;
onClick?: () => void;
};
export const ProductCard: React.FC<ProductCardProps> = ({
product,
translate,
onClick,
}) => {
return (
<motion.div
style={{
x: translate,
}}
whileHover={{
y: -20,
}}
key={product.title}
className="group/produc h-80 w-[35rem] relative flex-shrink-0"
onClick={onClick}
>
<div className="block group-hover/product:shadow-2xl hover:cursor-pointer">
<Image
src={product.thumbnail}
height="1280"
width="832"
className="object-cover object-left-top absolute h-full w-full inset-0 rounded-md"
alt={product.title}
/>
</div>
<div className="absolute inset-0 h-full w-full opacity-0 group-hover/product:opacity-80 bg-black pointer-events-none"></div>
<h2 className="absolute bottom-4 left-4 opacity-0 group-hover/product:opacity-100 text-white">
{product.title}
</h2>
</motion.div>
);
};<div class="antialiased overflow-hidden relative w-full" data-controller="hero-parallax">
<div class="flex flex-col items-center justify-between pt-4 sm:pt-24 pb-4 sm:pb-12">
<%= render 'shared/header-menu' %>
</div>
<div class="pb-10">
<div data-hero-parallax-target="scrollContainer" class="h-[300vh] py-40 relative flex flex-col self-auto perspective-[1000px] transform-preserve-3d">
<div class="max-w-screen-lg relative mx-auto py-20 md:py-40 px-4 w-full left-0 top-0">
<h1 class="text-2xl md:text-7xl font-bold text-neutral-200">
My Work
</h1>
<p class="max-w-2xl text-base md:text-xl mt-8 text-neutral-300">
I bring websites and apps to life, from idea to launch. See what I've built.
</p>
</div>
<div data-hero-parallax-target="productGrid" class="transition-all duration-300 ease-out will-change-transform" style="transform: perspective(1000px) rotateX(15deg) rotateZ(20deg) translateY(-700px); opacity: 0.2;">
<% @products.each_slice((@products.size / 3.0).ceil).with_index do |slice, index| %>
<div class="<%= index.odd? ? 'flex flex-row' : 'flex flex-row-reverse space-x-reverse' %> mb-20 space-x-20">
<% slice.each do |product| %>
<%= render 'product_card', product: product, reverse: index.odd? %>
<% end %>
</div>
<% end %>
</div>
</div>
</div>
<div class="fixed top-0 left-0 w-full h-full bg-black bg-opacity-80 flex items-center justify-center" style="display:none;" data-hero-parallax-target="productModal" data-action="click->hero-parallax#closeModal">
<turbo-frame id="product-modal-content" data-action="turbo:frame-load->hero-parallax#openModal"></turbo-frame>
</div>
<%= render 'shared/footer' %>
</div><%= link_to "/work/#{product[:name]}/modal",
class: "group/produc h-80 w-[35rem] relative flex-shrink-0 transform transition-all",
data: {
"hero-parallax-target": "translateX#{reverse ? 'reverse' : ''}",
"action": "mouseenter->hero-parallax#onMouseEnter mouseleave->hero-parallax#onMouseLeave",
"turbo-frame": "product-modal-content"
} do %>
<div class="block group-hover/product:shadow-2xl hover:cursor-pointer">
<img src="<%= asset_path(product[:thumbnail]) %>" height="1280" width="832" class="object-cover object-left-top absolute h-full w-full inset-0 rounded-md" alt="Product image" />
</div>
<div class="absolute inset-0 h-full w-full opacity-0 group-hover/product:opacity-80 bg-black pointer-events-none"></div>
<h2 class="absolute bottom-4 left-4 opacity-0 group-hover/product:opacity-100 text-white">
<%= product[:title] %>
</h2>
<% end %>import { Controller } from "@hotwired/stimulus";
import {
animate,
scroll,
transform,
} from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
export default class extends Controller {
static targets = [
"scrollContainer",
"productGrid",
"translateX",
"translateXreverse",
"productModal",
];
animConfig = {
type: "spring",
stiffness: 300,
damping: 30,
bounce: 100,
delay: 0,
duration: 0,
};
connect() {
this.setupScroll();
}
disconnect() {
this.cancelScroll?.();
}
setupScroll() {
this.cancelScroll = scroll(
(progress) => this.animateScrollProgress(progress),
{
axis: "y",
target: this.scrollContainerTarget,
offset: ["start start", "end start"],
},
);
}
animateScrollProgress(progress) {
const animations = [
{ target: this.productGridTarget, props: this.getGridProps(progress) },
{
target: this.translateXTargets,
props: { x: transform([0, 1], [0, 1000])(progress) },
},
{
target: this.translateXreverseTargets,
props: { x: transform([0, 1], [0, -1000])(progress) },
},
];
animations.forEach(({ target, props }) =>
animate(target, props, this.animConfig),
);
}
getGridProps(progress) {
return {
transformPerspective: 1000,
y: transform([0, 0.2], [-700, 500])(progress),
rotateX: transform([0, 0.2], [15, 0])(progress),
rotateZ: transform([0, 0.2], [20, 0])(progress),
opacity: transform([0, 0.2], [0.2, 1])(progress),
};
}
onMouseEnter(event) {
animate(event.target, { y: -20 }, this.animConfig);
}
onMouseLeave(event) {
animate(event.target, { y: 0 }, this.animConfig);
}
openModal() {
const modal = document.querySelector(".productModalContent");
const modalContainer = this.productModalTarget;
modalContainer.style.opacity = 0;
modalContainer.style.display = "flex";
animate(modalContainer, { opacity: 1 });
animate(modal, { y: "-100vh", opacity: 0 }, { duration: 0, delay: 0 }).then(
() => {
document.body.style.overflow = "hidden";
animate(
modal,
{ y: 0, opacity: 1 },
{ type: "spring", damping: 10, stiffness: 100 },
);
},
);
}
closeModal() {
const modalContainer = this.productModalTarget;
const modal = document.querySelector(".productModalContent");
document.body.style.overflow = "auto";
const backDropAnim = animate(modalContainer, { opacity: 0 });
const modalAnim = animate(
modal,
{ y: "-100vh", opacity: 0 },
{ type: "spring", damping: 10, stiffness: 100 },
);
Promise.all([backDropAnim, modalAnim]).then(() => {
modalContainer.style.display = "none";
});
}
modalClick(event) {
event.stopPropagation();
}
}rails generate authentication
flyctl launch
flyctl deploy
But people aren’t that simple. You can’t reduce a human being to one ideological checkbox. We’re ...
So I tried three self-host paths that give you a good DX without giving your infra keys to a thir...
I don’t have a static IP at home, and running a PC 24/7 costs more than hosting on Fly.io, especi...
Talk is cheap, show me the code