@@ -14,6 +14,7 @@
|
||||
"clean:modules": "rm -rf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/permanent-marker": "^5.3.0",
|
||||
"@fontsource/work-sans": "^5.2.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc" viewBox="0 0 512 512">
|
||||
<title id="title">Effect Lens</title>
|
||||
<desc id="desc">The layered Effect mark beneath a magnifying glass that highlights lines of code.</desc>
|
||||
<defs>
|
||||
<linearGradient id="background" x1="20" y1="16" x2="492" y2="496" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#17152A"/><stop offset="1" stop-color="#0B1020"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="layer-back" x1="52" y1="112" x2="260" y2="248" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6E56CF"/><stop offset="1" stop-color="#3B2F86"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="layer-middle" x1="60" y1="80" x2="266" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9B7BFF"/><stop offset="1" stop-color="#5A43BE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="layer-front" x1="66" y1="54" x2="274" y2="194" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#272442"/><stop offset="1" stop-color="#17162A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="lens-ring" x1="122" y1="62" x2="278" y2="242" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#82F7FF"/><stop offset=".55" stop-color="#24D6E7"/><stop offset="1" stop-color="#00A9C0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="handle" x1="230" y1="204" x2="300" y2="284" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#42E7F2"/><stop offset="1" stop-color="#8B7BFF"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="lens-glass" cx="0" cy="0" r="1" gradientTransform="translate(166 108) rotate(48) scale(132)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E5FDFF" stop-opacity=".2"/><stop offset=".62" stop-color="#3AE2EE" stop-opacity=".1"/><stop offset="1" stop-color="#101529" stop-opacity=".16"/>
|
||||
</radialGradient>
|
||||
<filter id="shadow" x="-30%" y="-30%" width="160%" height="170%" color-interpolation-filters="sRGB">
|
||||
<feDropShadow dx="0" dy="12" stdDeviation="12" flood-color="#02040C" flood-opacity=".45"/>
|
||||
</filter>
|
||||
<filter id="glow" x="-40%" y="-40%" width="180%" height="180%" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation="5" result="blur"/><feFlood flood-color="#38E8F2" flood-opacity=".34" result="color"/>
|
||||
<feComposite in="color" in2="blur" operator="in" result="glow"/>
|
||||
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
<clipPath id="lens-clip"><circle cx="190" cy="142" r="67"/></clipPath>
|
||||
</defs>
|
||||
<rect x="12" y="12" width="488" height="488" rx="92" fill="url(#background)"/>
|
||||
<rect x="13" y="13" width="486" height="486" rx="91" fill="none" stroke="#fff" stroke-opacity=".09" stroke-width="2"/>
|
||||
<g transform="translate(-19 15) scale(1.75)">
|
||||
<g filter="url(#shadow)">
|
||||
<path d="m48 164 106-56 120 65-106 57-120-66Z" fill="url(#layer-back)" stroke="#B7A8FF" stroke-opacity=".3" stroke-width="2"/>
|
||||
<path d="m48 132 106-56 120 65-106 57-120-66Z" fill="url(#layer-middle)" stroke="#C7BBFF" stroke-opacity=".45" stroke-width="2"/>
|
||||
<path d="m48 99 106-56 120 65-106 57L48 99Z" fill="url(#layer-front)" stroke="#B9ACFF" stroke-opacity=".55" stroke-width="2"/>
|
||||
</g>
|
||||
<g opacity=".55" stroke-linecap="round">
|
||||
<path d="m92 92 53-28" stroke="#8B7BFF" stroke-width="7"/><path d="m105 107 67-35" stroke="#F0ECFF" stroke-width="7"/>
|
||||
<path d="m119 122 37-20" stroke="#5F577F" stroke-width="7"/><path d="m166 97 45 24" stroke="#5F577F" stroke-width="7"/>
|
||||
</g>
|
||||
<g clip-path="url(#lens-clip)">
|
||||
<circle cx="190" cy="142" r="67" fill="#11182C"/>
|
||||
<path d="m126 112 60-32" stroke="#9C8CFF" stroke-width="10" stroke-linecap="round"/>
|
||||
<path d="m136 132 85-45" stroke="#F6F3FF" stroke-width="10" stroke-linecap="round"/>
|
||||
<path d="m147 153 55-29" stroke="#50E5EF" stroke-width="10" stroke-linecap="round"/>
|
||||
<path d="m157 174 68-36" stroke="#B9F27C" stroke-width="10" stroke-linecap="round"/>
|
||||
<path d="m169 195 38-20" stroke="#8B7BFF" stroke-width="10" stroke-linecap="round"/>
|
||||
<circle cx="190" cy="142" r="67" fill="url(#lens-glass)"/>
|
||||
<path d="M145 92c13-11 29-17 46-17" fill="none" stroke="#fff" stroke-opacity=".4" stroke-width="5" stroke-linecap="round"/>
|
||||
</g>
|
||||
<circle cx="190" cy="142" r="72" fill="none" stroke="#06111D" stroke-opacity=".85" stroke-width="16"/>
|
||||
<circle cx="190" cy="142" r="73" fill="none" stroke="url(#lens-ring)" stroke-width="9" filter="url(#glow)"/>
|
||||
<path d="m240 196 48 53" fill="none" stroke="#07111D" stroke-width="30" stroke-linecap="round"/>
|
||||
<path d="m240 196 48 53" fill="none" stroke="url(#handle)" stroke-width="19" stroke-linecap="round"/>
|
||||
<path d="m250 207 25 27" fill="none" stroke="#fff" stroke-opacity=".3" stroke-width="4" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,36 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Effect View</title>
|
||||
<defs>
|
||||
<linearGradient id="surface" x1="14" y1="10" x2="116" y2="122" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#16A3A1"/>
|
||||
<stop offset=".58" stop-color="#08777B"/>
|
||||
<stop offset="1" stop-color="#102F31"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="warmth" cx="0" cy="0" r="1" gradientTransform="translate(112 111) rotate(-135) scale(65)">
|
||||
<stop stop-color="#F0A13B" stop-opacity=".85"/>
|
||||
<stop offset="1" stop-color="#F0A13B" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="150%">
|
||||
<feDropShadow dx="0" dy="3" stdDeviation="2.5" flood-color="#062426" flood-opacity=".22"/>
|
||||
</filter>
|
||||
<clipPath id="tile"><rect width="128" height="128" rx="30"/></clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#tile)">
|
||||
<rect width="128" height="128" fill="url(#surface)"/>
|
||||
<circle cx="112" cy="111" r="65" fill="url(#warmth)"/>
|
||||
<path d="M-8 30 55-4h73v14L-8 83V30Z" fill="#fff" fill-opacity=".055"/>
|
||||
</g>
|
||||
<rect x="1" y="1" width="126" height="126" rx="29" stroke="#fff" stroke-opacity=".14" stroke-width="2"/>
|
||||
<g transform="translate(24 24) scale(2.5)" fill="#fff" filter="url(#shadow)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.802 24.317a.966.966 0 0 0 .362-1.322.999.999 0 0 0-1.349-.354l-12.894 7.295-12.846-7.268a.999.999 0 0 0-1.349.354.966.966 0 0 0 .361 1.322l13.303 7.526a1 1 0 0 0 .768.092c.12-.019.236-.059.342-.119l13.302-7.526Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.13 16.601a.98.98 0 0 0-.512-1.052L16.555 7.631a1 1 0 0 0-.357-.124 1.017 1.017 0 0 0-.812.096L1.323 15.521a.974.974 0 0 0-.499 1.118c-.044.352.094.72.504.953l14.062 7.956a1.017 1.017 0 0 0 .813.097c.127-.02.25-.062.361-.125l14.064-7.957c.413-.234.55-.607.502-.962Zm-2.903-.042L15.946 9.645 3.672 16.555l12.281 6.948 12.274-6.944Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.343 10.61a1.06 1.06 0 0 0 .401-1.468 1.1 1.1 0 0 0-1.498-.393L15.927 16.85 1.661 8.78a1.1 1.1 0 0 0-1.498.393 1.06 1.06 0 0 0 .401 1.468l14.773 8.358c.257.145.562.18.853.101.134-.02.262-.065.38-.131l14.773-8.358Z"/>
|
||||
<path d="m2.74 9.68 13.159-8.06 13.159 8.059-13.16 7.522L2.74 9.68Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.326 8.49c.525.296.707.953.406 1.467-.301.514-.972.69-1.498.394L15.913 2.288 1.643 10.322c-.526.296-1.196.12-1.498-.394a1.06 1.06 0 0 1 .407-1.467L15.325.144a1.1 1.1 0 0 1 .853-.101c.131.02.258.064.374.13L31.326 8.49Z"/>
|
||||
</g>
|
||||
<g filter="url(#shadow)">
|
||||
<path d="M43 61.5c5.8-7.4 12.8-11 21-11s15.2 3.6 21 11c-5.8 7.4-12.8 11-21 11s-15.2-3.6-21-11Z" fill="#102F31"/>
|
||||
<circle cx="64" cy="61.5" r="7.5" fill="#F0A13B"/>
|
||||
<circle cx="66.5" cy="59" r="2.2" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1,4 +1,5 @@
|
||||
import { ArrowUpRight } from "lucide-react"
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
interface ProjectCardProps {
|
||||
readonly index: string
|
||||
@@ -9,6 +10,9 @@ interface ProjectCardProps {
|
||||
readonly href?: string
|
||||
readonly linkLabel?: string
|
||||
readonly variant?: "light" | "dark" | "outline"
|
||||
readonly accent?: "teal" | "violet" | "chef"
|
||||
readonly logo?: ReactNode
|
||||
readonly categoryIcon?: ReactNode
|
||||
}
|
||||
|
||||
export function ProjectCard({
|
||||
@@ -20,15 +24,21 @@ export function ProjectCard({
|
||||
href,
|
||||
linkLabel = "View project",
|
||||
variant = "light",
|
||||
accent,
|
||||
logo,
|
||||
categoryIcon,
|
||||
}: ProjectCardProps) {
|
||||
return (
|
||||
<article className={`project-card project-card--${variant}`}>
|
||||
<article className={accent
|
||||
? `project-card project-card--${accent} project-card--${variant}`
|
||||
: `project-card project-card--${variant}`}>
|
||||
<div className="project-card__topline">
|
||||
<span>{index}</span>
|
||||
<span>{category}</span>
|
||||
<span className="project-card__category">{categoryIcon}{category}</span>
|
||||
</div>
|
||||
|
||||
<div className="project-card__body">
|
||||
{logo && <div className="project-card__logo">{logo}</div>}
|
||||
<h3>{title}</h3>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@import "@fontsource/work-sans/600.css";
|
||||
@import "@fontsource/work-sans/700.css";
|
||||
@import "@fontsource/work-sans/800.css";
|
||||
@import "@fontsource/permanent-marker/400.css";
|
||||
|
||||
@font-face {
|
||||
font-family: "Pixelated MS Sans Serif";
|
||||
@@ -141,7 +142,7 @@ button:hover {
|
||||
|
||||
.site-rail nav a {
|
||||
display: grid;
|
||||
grid-template-columns: 28px 1fr;
|
||||
grid-template-columns: 18px 28px 1fr;
|
||||
align-items: center;
|
||||
min-height: 42px;
|
||||
padding: 0 18px;
|
||||
@@ -151,6 +152,11 @@ button:hover {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.site-rail nav a > svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.site-rail nav a span {
|
||||
color: var(--muted);
|
||||
font-family: "Pixelated MS Sans Serif", monospace;
|
||||
@@ -259,6 +265,17 @@ button:hover {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hero__kicker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.hero__kicker svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
max-width: 720px;
|
||||
margin: 26px 0 22px;
|
||||
@@ -439,6 +456,9 @@ button:hover {
|
||||
}
|
||||
|
||||
.capability-strip li {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 12px 16px;
|
||||
border-right: 1px solid #555;
|
||||
font-size: 10px;
|
||||
@@ -447,6 +467,11 @@ button:hover {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.capability-strip svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.work-section,
|
||||
.upcoming-section,
|
||||
.repo-section {
|
||||
@@ -531,6 +556,27 @@ button:hover {
|
||||
var(--paper);
|
||||
}
|
||||
|
||||
.project-card--teal {
|
||||
border-top: 5px solid #08777b;
|
||||
background:
|
||||
radial-gradient(circle at 100% 100%, rgb(240 161 59 / 22%), transparent 42%),
|
||||
linear-gradient(145deg, #f8ffff, #e4f4f2);
|
||||
}
|
||||
|
||||
.project-card--violet {
|
||||
border-top: 5px solid #6e56cf;
|
||||
background:
|
||||
radial-gradient(circle at 100% 100%, rgb(36 214 231 / 20%), transparent 42%),
|
||||
linear-gradient(145deg, #fbfaff, #ede8ff);
|
||||
}
|
||||
|
||||
.project-card--chef {
|
||||
border-top: 5px solid #d59c8d;
|
||||
background:
|
||||
radial-gradient(circle at 100% 100%, rgb(213 156 141 / 22%), transparent 42%),
|
||||
linear-gradient(145deg, #fffdfa, #f7eeea);
|
||||
}
|
||||
|
||||
.project-card__topline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -543,8 +589,62 @@ button:hover {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.project-card__category {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.project-card__category svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.project-card__body {
|
||||
margin: 34px 0;
|
||||
margin: 28px 0 32px;
|
||||
}
|
||||
|
||||
.project-card__logo {
|
||||
display: flex;
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.project-card__logo img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 15px;
|
||||
box-shadow: 3px 3px 0 rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
.comme-le-chef-logo {
|
||||
display: flex;
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 8px 6px;
|
||||
border: 2px solid var(--ink);
|
||||
background: #fffaf7;
|
||||
box-shadow: 3px 3px 0 #d59c8d;
|
||||
font-family: "Permanent Marker", "Comic Sans MS", cursive;
|
||||
font-size: 17px;
|
||||
letter-spacing: -0.07em;
|
||||
line-height: 0.78;
|
||||
text-transform: uppercase;
|
||||
transform: rotate(-2deg);
|
||||
}
|
||||
|
||||
.comme-le-chef-logo span:last-child {
|
||||
margin-left: 10px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.project-card h3 {
|
||||
@@ -809,6 +909,7 @@ button:hover {
|
||||
.site-rail__meta,
|
||||
.site-rail nav p,
|
||||
.site-rail nav a span,
|
||||
.site-rail nav a svg,
|
||||
.site-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { ArrowDown, ArrowUpRight, Code2, GitBranch } from "lucide-react"
|
||||
import {
|
||||
AppWindow,
|
||||
ArrowDown,
|
||||
ArrowUpRight,
|
||||
BriefcaseBusiness,
|
||||
ChefHat,
|
||||
Code2,
|
||||
CodeXml,
|
||||
Database,
|
||||
GitBranch,
|
||||
Hammer,
|
||||
Home,
|
||||
Library,
|
||||
MapPin,
|
||||
PackageOpen,
|
||||
PanelsTopLeft,
|
||||
Radio,
|
||||
UserRound,
|
||||
} from "lucide-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { ProjectCard } from "@/components/portfolio/ProjectCard"
|
||||
import { SectionHeading } from "@/components/portfolio/SectionHeading"
|
||||
@@ -10,11 +28,11 @@ export const Route = createFileRoute("/_main/")({
|
||||
})
|
||||
|
||||
const sections = [
|
||||
{ id: "top", number: "00", label: "Home" },
|
||||
{ id: "work", number: "01", label: "Selected work" },
|
||||
{ id: "workbench", number: "02", label: "Workbench" },
|
||||
{ id: "about", number: "03", label: "About" },
|
||||
{ id: "repositories", number: "04", label: "Repositories" },
|
||||
{ id: "top", number: "00", label: "Home", icon: Home },
|
||||
{ id: "work", number: "01", label: "Selected work", icon: BriefcaseBusiness },
|
||||
{ id: "workbench", number: "02", label: "Workbench", icon: Hammer },
|
||||
{ id: "about", number: "03", label: "About", icon: UserRound },
|
||||
{ id: "repositories", number: "04", label: "Repositories", icon: GitBranch },
|
||||
] as const
|
||||
|
||||
function RouteComponent() {
|
||||
@@ -60,6 +78,7 @@ function RouteComponent() {
|
||||
className={activeSection === section.id ? "is-active" : undefined}
|
||||
aria-current={activeSection === section.id ? "location" : undefined}
|
||||
>
|
||||
<section.icon aria-hidden="true" />
|
||||
<span>{section.number}</span>
|
||||
{section.label}
|
||||
</a>
|
||||
@@ -81,7 +100,7 @@ function RouteComponent() {
|
||||
|
||||
<section className="hero" id="top">
|
||||
<div className="hero__intro">
|
||||
<p className="hero__kicker">Full-stack engineer · Product builder</p>
|
||||
<p className="hero__kicker"><MapPin aria-hidden="true" /> Full-stack engineer · Product builder</p>
|
||||
<h1>
|
||||
Useful systems,
|
||||
<span>built properly.</span>
|
||||
@@ -102,11 +121,11 @@ function RouteComponent() {
|
||||
</section>
|
||||
|
||||
<ul className="capability-strip" aria-label="Core capabilities">
|
||||
<li>TypeScript</li>
|
||||
<li>Effect</li>
|
||||
<li>React</li>
|
||||
<li>Product engineering</li>
|
||||
<li>Open source</li>
|
||||
<li><CodeXml aria-hidden="true" /> TypeScript</li>
|
||||
<li><Radio aria-hidden="true" /> Effect</li>
|
||||
<li><PanelsTopLeft aria-hidden="true" /> React</li>
|
||||
<li><AppWindow aria-hidden="true" /> Product engineering</li>
|
||||
<li><PackageOpen aria-hidden="true" /> Open source</li>
|
||||
</ul>
|
||||
|
||||
<section className="work-section" id="work">
|
||||
@@ -119,32 +138,44 @@ function RouteComponent() {
|
||||
<div className="project-grid">
|
||||
<ProjectCard
|
||||
index="01"
|
||||
title="effect-lens"
|
||||
category="Open source / library"
|
||||
description="Focus, subscribe to, and update nested state without losing typed failures or requirements."
|
||||
tags={["Effect", "State", "TypeScript"]}
|
||||
href="https://www.npmjs.com/package/effect-lens"
|
||||
linkLabel="View on npm"
|
||||
variant="dark"
|
||||
/>
|
||||
<ProjectCard
|
||||
index="02"
|
||||
title="effect-view"
|
||||
title="Effect View"
|
||||
category="Open source / React"
|
||||
categoryIcon={<PanelsTopLeft aria-hidden="true" />}
|
||||
description="A small bridge for writing React function components as composable Effect programs."
|
||||
tags={["Effect", "React", "DX"]}
|
||||
href="https://www.npmjs.com/package/effect-view"
|
||||
linkLabel="View on npm"
|
||||
accent="teal"
|
||||
logo={<img src="/logos/effect-view.svg" alt="Effect View" />}
|
||||
/>
|
||||
<ProjectCard
|
||||
index="02"
|
||||
title="Effect Lens"
|
||||
category="Open source / library"
|
||||
categoryIcon={<Library aria-hidden="true" />}
|
||||
description="Focus, subscribe to, and update nested state without losing typed failures or requirements."
|
||||
tags={["Effect", "State", "TypeScript"]}
|
||||
href="https://www.npmjs.com/package/effect-lens"
|
||||
linkLabel="View on npm"
|
||||
accent="violet"
|
||||
logo={<img src="/logos/effect-lens.svg" alt="Effect Lens" />}
|
||||
/>
|
||||
<ProjectCard
|
||||
index="03"
|
||||
title="Comme le Chef"
|
||||
category="SaaS / Food tech"
|
||||
categoryIcon={<ChefHat aria-hidden="true" />}
|
||||
description="A recipe workspace for nutrition, allergens, costing, and production documents."
|
||||
tags={["Product", "SaaS", "Food tech"]}
|
||||
href="https://commedeschef.com/"
|
||||
linkLabel="Visit website"
|
||||
variant="outline"
|
||||
accent="chef"
|
||||
logo={
|
||||
<span className="comme-le-chef-logo" role="img" aria-label="Comme le Chef">
|
||||
<span>Comme</span><span>le Chef</span>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
@@ -161,6 +192,7 @@ function RouteComponent() {
|
||||
index="04"
|
||||
title="FLOWpti"
|
||||
category="Upcoming / Logistics"
|
||||
categoryIcon={<MapPin aria-hidden="true" />}
|
||||
description="Route optimization that turns operational constraints into efficient delivery plans."
|
||||
tags={["Routing", "Optimization", "B2B"]}
|
||||
/>
|
||||
@@ -168,6 +200,7 @@ function RouteComponent() {
|
||||
index="05"
|
||||
title="Sentinel"
|
||||
category="Upcoming / Open source"
|
||||
categoryIcon={<Database aria-hidden="true" />}
|
||||
description="Self-hosted OBD-II telemetry for vehicle diagnostics and remote insight."
|
||||
tags={["OBD-II", "Telemetry", "Self-hosted"]}
|
||||
variant="dark"
|
||||
|
||||
Reference in New Issue
Block a user