diff --git a/404.html b/404.html index 37275cf..f392bfb 100644 --- a/404.html +++ b/404.html @@ -4,8 +4,10 @@ - - + + + + diff --git a/app/fonts/InstrumentSerif-OFL.txt b/app/fonts/InstrumentSerif-OFL.txt new file mode 100644 index 0000000..a39b7e0 --- /dev/null +++ b/app/fonts/InstrumentSerif-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Instrument Serif Project Authors (https://github.com/Instrument/instrument-serif) InstrumentSerif-Italic.ttf: Copyright 2022 The Instrument Serif Project Authors (https://github.com/Instrument/instrument-serif) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/globals.css b/app/globals.css index 4d4abd6..cb80858 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,5 +1,6 @@ @import "tailwindcss"; +/* Manrope is bundled by Vite from app/fonts. Instrument Serif is embedded below as a data URI (single 21 KB Latin subset, regular only) so the repo carries no extra font file. */ @font-face { font-family: "Manrope"; src: url("./fonts/manrope-latin-variable.woff2") format("woff2"); @@ -8,6 +9,16 @@ font-display: swap; } +/* Instrument Serif (SIL OFL 1.1), Latin subset, regular only. */ +@font-face { + font-family: "Instrument Serif"; + src: url("data:font/woff2;base64,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") format("woff2"); + font-style: normal; + font-weight: 400; + font-display: swap; + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + @theme inline { --font-display: var(--font-manrope), system-ui, sans-serif; --font-body: var(--font-manrope), system-ui, sans-serif; @@ -29,6 +40,7 @@ --font-manrope: "Manrope"; --font-pixelify: "Pixelify Sans Variable"; --font-space-mono: "Space Mono"; + --font-serif-display: "Instrument Serif", Georgia, "Times New Roman", serif; color-scheme: dark; --site-bg: #252031; --site-surface: #332a43; @@ -42,16 +54,6 @@ --site-control-radius: 999px; --ui-duration: 280ms; --ui-ease: cubic-bezier(.2,.9,.25,1.4); - --glass-fill: rgb(37 28 53 / .3); - --glass-top: rgb(226 211 255 / .14); - --glass-bottom: rgb(20 15 32 / .05); - --glass-edge: rgb(235 223 255 / .2); - --glass-rim: rgb(243 234 255 / .37); - --glass-selected: rgb(153 132 207 / .22); - --site-shadow: 0 12px 30px rgb(13 7 28 / .2), 0 2px 5px rgb(13 7 28 / .12); - --glass-shadow: var(--site-shadow), inset 0 1px 0 var(--glass-rim), inset 0 -1px 1px rgb(15 8 25 / .2); - --glass-panel-shadow: var(--site-shadow), inset 0 1px 1px rgb(243 234 255 / .3), inset 0 -1px 1px rgb(15 8 25 / .2); - --site-panel-bg: linear-gradient(135deg, rgb(69 54 90 / .93), rgb(37 28 53 / .94)); --layer-content: 1; --layer-navigation: 30; --layer-skip-link: 40; @@ -73,16 +75,6 @@ --site-accent: #754eaa; --site-border: rgb(91 65 113 / .18); --site-contrast: #fff; - --glass-fill: rgb(255 255 255 / .16); - --glass-top: rgb(255 255 255 / .46); - --glass-bottom: rgb(255 255 255 / .05); - --glass-edge: rgb(91 65 113 / .18); - --glass-rim: rgb(255 255 255 / .8); - --glass-selected: rgb(153 132 207 / .18); - --site-shadow: 0 12px 30px rgb(70 40 95 / .08), 0 2px 5px rgb(63 40 80 / .04); - --glass-shadow: var(--site-shadow), inset 0 1px 0 var(--glass-rim), inset 0 -1px 1px rgb(71 47 87 / .1); - --glass-panel-shadow: var(--site-shadow), inset 0 1px 1px rgb(255 255 255 / .88), inset 0 -1px 1px rgb(71 47 87 / .09); - --site-panel-bg: linear-gradient(135deg, rgb(249 245 253 / .96), rgb(238 232 243 / .94)); --skullet-base: color-mix(in srgb, var(--site-text) 9%, var(--site-surface-soft)); } } @@ -101,14 +93,14 @@ button, a { -webkit-tap-highlight-color: transparent; } } .skip-link { position: fixed; - top: 12px; - left: 12px; + top: max(12px, env(safe-area-inset-top)); + left: max(12px, env(safe-area-inset-left)); z-index: var(--layer-skip-link); padding: 14px 20px; border-radius: var(--site-control-radius); color: var(--site-contrast); background: var(--site-accent); - transform: translateY(calc(-100% - 20px)); + transform: translateY(calc(-100% - max(12px, env(safe-area-inset-top)))); } .skip-link:focus { transform: none; } diff --git a/app/not-found.module.css b/app/not-found.module.css index dba1c9c..e4e138e 100644 --- a/app/not-found.module.css +++ b/app/not-found.module.css @@ -1,3 +1,32 @@ +/* Glass tokens are local to the 404 page; the homepage uses its own paper/green palette. */ +.page { + --glass-fill: rgb(37 28 53 / .3); + --glass-top: rgb(226 211 255 / .14); + --glass-bottom: rgb(20 15 32 / .05); + --glass-edge: rgb(235 223 255 / .2); + --glass-rim: rgb(243 234 255 / .37); + --glass-selected: rgb(153 132 207 / .22); + --site-shadow: 0 12px 30px rgb(13 7 28 / .2), 0 2px 5px rgb(13 7 28 / .12); + --glass-shadow: var(--site-shadow), inset 0 1px 0 var(--glass-rim), inset 0 -1px 1px rgb(15 8 25 / .2); + --glass-panel-shadow: var(--site-shadow), inset 0 1px 1px rgb(243 234 255 / .3), inset 0 -1px 1px rgb(15 8 25 / .2); + --site-panel-bg: linear-gradient(135deg, rgb(69 54 90 / .93), rgb(37 28 53 / .94)); +} + +@media (prefers-color-scheme: light) { + .page { + --glass-fill: rgb(255 255 255 / .16); + --glass-top: rgb(255 255 255 / .46); + --glass-bottom: rgb(255 255 255 / .05); + --glass-edge: rgb(91 65 113 / .18); + --glass-rim: rgb(255 255 255 / .8); + --glass-selected: rgb(153 132 207 / .18); + --site-shadow: 0 12px 30px rgb(70 40 95 / .08), 0 2px 5px rgb(63 40 80 / .04); + --glass-shadow: var(--site-shadow), inset 0 1px 0 var(--glass-rim), inset 0 -1px 1px rgb(71 47 87 / .1); + --glass-panel-shadow: var(--site-shadow), inset 0 1px 1px rgb(255 255 255 / .88), inset 0 -1px 1px rgb(71 47 87 / .09); + --site-panel-bg: linear-gradient(135deg, rgb(249 245 253 / .96), rgb(238 232 243 / .94)); + } +} + .page { position: relative; display: grid; diff --git a/app/routes.tsx b/app/routes.tsx index bbf4255..e8ce044 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -13,6 +13,7 @@ type RouteMetadata = { image: string; imageAlt: string; noindex?: boolean; + themeColor?: { light: string; dark: string }; }; export type SiteRoute = { @@ -23,20 +24,21 @@ export type SiteRoute = { }; const homeMetadata: RouteMetadata = { - title: "Lu | Software Engineer", - socialTitle: "Lu | Software Engineer", + title: "Lu — I make stubborn software behave", + socialTitle: "Lu — I make stubborn software behave", description: "Lu makes stubborn software behave: native Android apps, agent systems, Linux tools, and practical software.", socialDescription: "I make stubborn software behave. Native Android, agent systems, Linux tools, and practical software.", canonical: siteConfig.siteUrl, image: `${siteConfig.siteUrl}/share-cards/luinbytes-dev-pond.png`, - imageAlt: "Lu | Software Engineer", + imageAlt: "luinbytes.dev share card: the line “I get annoyed, then I build the missing thing.” beside a pixel-art pond with koi, lily pads and stepping stones.", + themeColor: { light: "#f5f0e6", dark: "#242d26" }, }; const notFoundRoute: SiteRoute = { path: "/404", output: "404.html", Component: NotFound, - metadata: { ...homeMetadata, noindex: true }, + metadata: { ...homeMetadata, themeColor: undefined, noindex: true }, }; export const siteRoutes: readonly SiteRoute[] = [ @@ -52,7 +54,7 @@ export const siteRoutes: readonly SiteRoute[] = [ socialDescription: "Privacy policy for Lu's personal software on luinbytes.dev.", canonical: `${siteConfig.siteUrl}/privacy`, image: `${siteConfig.siteUrl}/share-cards/luinbytes-dev-pond.png`, - imageAlt: "Lu | Software Engineer", + imageAlt: homeMetadata.imageAlt, noindex: true, }, }, @@ -71,7 +73,11 @@ export function RouteHead({ route }: { route: SiteRoute }) { <> {metadata.title} - + + {metadata.themeColor && <> + + + } {metadata.noindex && } diff --git a/components/concepts/signal-desk/pond-environment.module.css b/components/concepts/signal-desk/pond-environment.module.css index fd12da8..ffeec7e 100644 --- a/components/concepts/signal-desk/pond-environment.module.css +++ b/components/concepts/signal-desk/pond-environment.module.css @@ -4,6 +4,8 @@ z-index: -2; overflow: hidden; pointer-events: none; + -webkit-user-select: none; + user-select: none; background: var(--site-bg); } @@ -12,6 +14,9 @@ inset: 0; z-index: -1; touch-action: manipulation; + -webkit-user-select: none; + user-select: none; + -webkit-touch-callout: none; } .pondImage, diff --git a/components/concepts/signal-desk/pond-input.ts b/components/concepts/signal-desk/pond-input.ts index f8f9c8c..8ba6282 100644 --- a/components/concepts/signal-desk/pond-input.ts +++ b/components/concepts/signal-desk/pond-input.ts @@ -8,45 +8,32 @@ type PondInputOptions = { onPrimaryImpact: (screen: PondPoint, world: PondPoint) => void; onFoodDrop: (screen: PondPoint, world: PondPoint) => void; onAffordance: (screen: PondPoint, visible: boolean) => void; - onGesture?: (gesture: "single-tap-impact" | "double-tap-food") => void; + onGesture?: (gesture: "tap-food") => void; }; -type TouchTap = { x: number; y: number; time: number }; - function isCompletedTap(touch: { moved: boolean; started: number }, now: number, overWater: boolean) { return !touch.moved && now - touch.started <= 430 && overWater; } -function isDoubleTap(previous: TouchTap | null, current: PondPoint, now: number) { - if (!previous) return false; - const elapsed = now - previous.time; - return elapsed >= 70 && elapsed <= 430 && Math.hypot(current.x - previous.x, current.y - previous.y) <= 34; -} +// A single click or tap on open water splashes and drops food. The browser's +// context menu is never intercepted. export function installPondInput(options: PondInputOptions) { const { interaction } = options; let previousPointer = { x: 0, y: 0, time: 0 }; let activeTouch: { id: number; x: number; y: number; started: number; moved: boolean } | null = null; - let lastTouchTap: TouchTap | null = null; - let pendingSingleTap: number | null = null; let lastFoodInput = Number.NEGATIVE_INFINITY; - const point = (event: PointerEvent | MouseEvent) => ({ x: event.clientX, y: event.clientY }); + const point = (event: PointerEvent) => ({ x: event.clientX, y: event.clientY }); const validWater = (screen: PondPoint) => options.isWater(screen.x, screen.y); const ownsPoint = (screen: PondPoint) => document.elementFromPoint(screen.x, screen.y) === interaction; - const clearPendingTap = () => { - if (pendingSingleTap !== null) window.clearTimeout(pendingSingleTap); - pendingSingleTap = null; - lastTouchTap = null; - }; - const dropFood = (screen: PondPoint) => { if (!validWater(screen)) return false; const now = performance.now(); if (now - lastFoodInput >= 140) { options.onFoodDrop(screen, options.toWorld(screen.x, screen.y)); - options.onGesture?.("double-tap-food"); + options.onGesture?.("tap-food"); lastFoodInput = now; } return true; @@ -79,7 +66,6 @@ export function installPondInput(options: PondInputOptions) { if (event.pointerType === "touch") { if (!event.isPrimary || activeTouch) { activeTouch = null; - clearPendingTap(); return; } activeTouch = { @@ -94,6 +80,7 @@ export function installPondInput(options: PondInputOptions) { } if (event.button === 0 && validWater(screen)) { options.onPrimaryImpact(screen, options.toWorld(screen.x, screen.y)); + dropFood(screen); } }; @@ -106,32 +93,14 @@ export function installPondInput(options: PondInputOptions) { const screen = point(event); if (!validWater(touch) || !isCompletedTap(touch, now, validWater(screen))) return; - const previous = lastTouchTap; - if (isDoubleTap(previous, screen, now)) { - clearPendingTap(); - if (dropFood(screen) && event.cancelable) event.preventDefault(); - return; - } - - lastTouchTap = { ...screen, time: now }; - if (pendingSingleTap !== null) window.clearTimeout(pendingSingleTap); - pendingSingleTap = window.setTimeout(() => { - if (lastTouchTap?.time === now) { - options.onPrimaryImpact({ x: touch.x, y: touch.y }, options.toWorld(touch.x, touch.y)); - options.onGesture?.("single-tap-impact"); - } - clearPendingTap(); - }, 440); + options.onPrimaryImpact({ x: touch.x, y: touch.y }, options.toWorld(touch.x, touch.y)); + if (dropFood(screen) && event.cancelable) event.preventDefault(); }; const cancel = (event: PointerEvent) => { if (activeTouch?.id === event.pointerId) activeTouch = null; }; - const contextMenu = (event: MouseEvent) => { - if (dropFood(point(event))) event.preventDefault(); - }; - const feedFromControl = (event: Event) => { for (const y of [0.5, 0.3, 0.7, 0.15, 0.85, 0.05, 0.95]) { for (const x of [0.5, 0.3, 0.7, 0.15, 0.85, 0.05, 0.95]) { @@ -147,24 +116,20 @@ export function installPondInput(options: PondInputOptions) { const resize = () => { activeTouch = null; - clearPendingTap(); }; window.addEventListener("pointermove", move, { passive: true }); interaction.addEventListener("pointerdown", press, { passive: true }); interaction.addEventListener("pointerup", release, { passive: false }); interaction.addEventListener("pointercancel", cancel, { passive: true }); - interaction.addEventListener("contextmenu", contextMenu); interaction.addEventListener("pond-feed", feedFromControl); window.addEventListener("resize", resize, { passive: true }); return () => { - clearPendingTap(); window.removeEventListener("pointermove", move); interaction.removeEventListener("pointerdown", press); interaction.removeEventListener("pointerup", release); interaction.removeEventListener("pointercancel", cancel); - interaction.removeEventListener("contextmenu", contextMenu); interaction.removeEventListener("pond-feed", feedFromControl); window.removeEventListener("resize", resize); }; diff --git a/components/concepts/signal-desk/project-explorer.module.css b/components/concepts/signal-desk/project-explorer.module.css index f7df6ae..0dd42c3 100644 --- a/components/concepts/signal-desk/project-explorer.module.css +++ b/components/concepts/signal-desk/project-explorer.module.css @@ -1,190 +1,86 @@ -.projectExplorer { +.projectGrid { display:grid; + grid-template-columns:repeat(2,minmax(0,1fr)); gap:20px; min-width:0; } -.projectNav { - display:grid; - grid-template-columns:repeat(4,minmax(0,1fr)); - gap:8px; - padding-bottom:12px; - border-bottom:1px solid var(--line); -} - -.projectNav button { - position:relative; - display:grid; - gap:5px; - align-content:center; - min-width:0; - min-height:58px; - padding:12px; - border:1px solid transparent; - border-radius:3px; - color:var(--muted); - background:transparent; - text-align:left; - cursor:pointer; -} - -.projectNav button:hover { - background:color-mix(in srgb,var(--green) 12%,var(--paper)); -} - -.projectNav .activeProject,.projectNav .activeProject:hover { - background:var(--green); - color:#fff9ee; - border-color:var(--green); -} - -.projectNav strong { - font-size:13px; - font-weight:650; -} - -.projectNav small { - font-size:12px; - line-height:1.4; -} - -.projectNav .activeProject small { - color:#f0eddb; -} - -.projectMarker { - position:absolute; - inset-block:0; - right:8px; - margin-block:auto; - width:6px; - height:6px; - border-radius:50%; - background:#eeb493; -} - -.projectStage { - display:grid; +.projectCard { + display:flex; + flex-direction:column; min-width:0; - height:480px; - overflow:hidden; border:1px solid var(--line); border-radius:3px; background:var(--paper); -} - -.projectPanel { - display:grid; - grid-area:1/1; - grid-template-columns:minmax(0,1.12fr) minmax(0,.88fr); - min-width:0; - height:100%; - min-height:0; -} - -.projectVisual,.projectMedia { - position:relative; - min-width:0; - min-height:0; overflow:hidden; - background:#dce0d0; -} - -.projectVisual > .projectMedia { - height:100%; -} - -.projectMedia > img { - object-fit:cover; -} - -.mediarakazoandroid > img { - object-fit:contain; - padding:15%; -} - -.mediahomebot > img { - object-fit:contain; - object-position:center; -} - -.projectCover { - display:flex; - flex-direction:column; - align-items:flex-start; - justify-content:center; - gap:17px; - padding:9%; - color:#29372e; -} - -.projectCover svg { - width:44px; - height:44px; - color:#a94626; - stroke-width:1.4; -} - -.projectCover strong { - font-size:clamp(2.6rem,5vw,5rem); - font-weight:600; - letter-spacing:-.06em; - line-height:.95; -} - -.projectCover span { - font-size:14px; } .projectCopy { display:flex; flex-direction:column; - justify-content:center; min-width:0; - padding:30px; - border-left:1px solid var(--line); - background:var(--paper); + padding:28px; } .projectEyebrow { color:var(--muted); - font:12px var(--font-space-mono),monospace; + font:13px var(--font-space-mono),monospace; line-height:1.6; } .projectCopy h3 { - margin:11px 0 0; - font:400 44px/.98 Georgia,serif; - letter-spacing:-2px; + margin:10px 0 0; + font:400 46px/1 var(--font-serif-display); + letter-spacing:-.02em; overflow-wrap:anywhere; } +.projectFigure { + display:flex; + align-items:baseline; + gap:12px; + margin:18px 0 0; +} + +.projectFigure strong { + color:var(--orange); + font:400 64px/.9 var(--font-serif-display); + letter-spacing:-.02em; +} + +.projectFigure span { + max-width:22ch; + font-size:15px; + font-weight:650; + line-height:1.35; +} + .projectSummary { margin:16px 0 0; - font-size:15px; + font-size:18px; font-weight:550; - line-height:1.5; + line-height:1.45; } .projectDetail { margin:10px 0 0; color:var(--muted); - font-size:13px; - line-height:1.6; + font-size:16px; + line-height:1.65; } .proofRow { display:flex; flex-wrap:wrap; - gap:7px 12px; - margin-top:16px; - padding-top:13px; + gap:7px 14px; + margin-top:18px; + padding-top:14px; border-top:1px solid var(--line); } .proofRow span { color:var(--muted); - font-size:12px; + font-size:14px; line-height:1.4; } @@ -192,7 +88,7 @@ display:flex; flex-wrap:wrap; gap:8px; - margin-top:18px; + margin-top:20px; } .projectActions a { @@ -201,100 +97,82 @@ justify-content:center; gap:8px; min-height:44px; - padding:9px 13px; - border:1px solid var(--green); + padding:9px 14px; + border:1px solid var(--control-edge); border-radius:3px; - color:#fff9ee; + color:var(--on-green); background:var(--green); - font-size:12px; + font-size:14px; font-weight:650; + white-space:nowrap; text-decoration:none; } +.projectActions a:hover { + text-decoration:underline; +} + .projectActions a:nth-child(2) { - border-color:var(--line); + border-color:var(--line-strong); color:var(--ink); background:transparent; } -.projectActions svg,.noScriptProjects svg { +.projectActions svg { width:15px; height:15px; flex:0 0 auto; } -.noScriptProjects { +.projectVisual { + margin-top:auto; + border-top:1px solid var(--line); + background:var(--art-bg); +} + +.projectIllustration { display:grid; - grid-template-columns:repeat(2,minmax(0,1fr)); - gap:8px; - margin-top:12px; + place-items:center; + height:200px; + padding:12px 0; + background:var(--paper); + color:var(--green); } -.noScriptProjects a { - display:flex; - align-items:center; - justify-content:space-between; - min-height:44px; - gap:12px; - padding:9px 12px; - border:1px solid var(--line); - color:var(--ink); - font-size:12px; - font-weight:600; +.projectIllustration svg { + display:block; + width:auto; + height:100%; + max-width:100%; } -@media(max-width:1000px) { - .projectStage { - height:auto; - } - .projectPanel { +.artWater { stroke:var(--line); stroke-width:2; } +.artAccent { stroke:var(--orange); } + +@media(prefers-color-scheme:dark) { + .projectIllustration { color:var(--muted); } +} + +@media(max-width:900px) { + .projectGrid { grid-template-columns:minmax(0,1fr); - height:auto; - } - .projectVisual { - min-height:clamp(260px,38vw,410px); - } - .projectCopy { - border-left:0; - border-top:1px solid var(--line); } } @media(max-width:800px) { - .projectNav { - grid-template-columns:repeat(2,minmax(0,1fr)); - } - .projectNav strong { - font-size:12px; - } .projectCopy { - padding:24px; + padding:22px; } .projectCopy h3 { - font-size:clamp(28px,8vw,36px); + font-size:clamp(34px,10vw,42px); } - .projectCover strong { - font-size:clamp(2rem,9vw,3rem); - } - .projectActions { - display:grid; - } -} - -@media(max-width:430px) { - .noScriptProjects { - grid-template-columns:minmax(0,1fr); + .projectIllustration { + height:170px; } } -@media(forced-colors:active) { - .projectNav .activeProject { - border:2px solid Highlight; +@media(max-width:380px) { + .projectActions { + display:grid; } } - -.projectIllustration { display:grid; place-items:center; height:100%; min-height:260px; background:var(--paper); color:var(--green); } -.projectIllustration svg { display:block; width:100%; max-width:560px; height:auto; } -.artWater { stroke:var(--line); stroke-width:2; } -.artAccent { stroke:var(--orange); } -@media(prefers-color-scheme:dark) { .projectIllustration { color:var(--muted); } } diff --git a/components/concepts/signal-desk/signal-desk.module.css b/components/concepts/signal-desk/signal-desk.module.css index 9ca3043..5f6b3f5 100644 --- a/components/concepts/signal-desk/signal-desk.module.css +++ b/components/concepts/signal-desk/signal-desk.module.css @@ -1,20 +1,52 @@ +/* Homepage palette. The shared --site-* tokens are repointed here so the purple + theme used by other pages never leaks into the homepage (loading gaps, skip + link, selection, scrollbar). */ +:global(html):has(.page) { + --site-bg: #1d4a43; + --site-text: #29372e; + --site-muted: #8fb3a6; + --site-accent: #344f3d; + --site-contrast: #fff9ee; + --site-control-radius: 3px; + scrollbar-color: #8fb3a6 #1d4a43; +} + +:global(html):has(.page) :global(.skip-link) { + border:1px solid #fff9ee; + font-weight:700; +} + .page { --paper: #f5f0e6; --ink: #29372e; --muted: #596052; --line: #c9c9b8; + --line-strong: #878c79; --orange: #a94626; --green: #344f3d; + --on-green: #fff9ee; + --control-edge: var(--green); + --pond-deep: #1d4a43; + --art-bg: #dce0d0; + --card-shadow: 7px 7px 0 rgb(0 0 0 / .15); --header-shadow:0 2px 4px rgb(41 55 46 / .1), 0 10px 24px rgb(41 55 46 / .2); + --container: min(calc(100% - 96px), 1200px); + --section-gap: clamp(72px, 9vw, 128px); + --site-bg: var(--pond-deep); + --site-accent: var(--green); + --site-contrast: var(--on-green); position: relative; isolation: isolate; - -webkit-user-select:none; - user-select:none; min-height: 100svh; overflow: clip; color: var(--ink); font-family: var(--font-manrope), sans-serif; - background: var(--site-bg); + background: var(--pond-deep); +} + +.page ::selection { + color: var(--on-green); + background: var(--green); } .page :where(a, button, input):focus-visible { @@ -47,9 +79,11 @@ .header { position:fixed; z-index:30; - top:20px; - left:32px; - right:32px; + top:max(20px, env(safe-area-inset-top)); + left:max(0px, env(safe-area-inset-left)); + right:max(0px, env(safe-area-inset-right)); + width:min(var(--container), calc(100% - max(0px, env(safe-area-inset-left)) - max(0px, env(safe-area-inset-right)))); + margin-inline:auto; display:flex; justify-content:space-between; align-items:center; @@ -69,10 +103,10 @@ } .brandMark { - font-family:Georgia,serif; - font-size:40px; - font-weight:bold; - letter-spacing:-4px; + font-family:var(--font-serif-display); + font-size:44px; + font-weight:400; + letter-spacing:-.03em; line-height:1; } @@ -82,9 +116,9 @@ .brandNote { font-family:var(--font-space-mono),monospace; - font-size:12px; - max-width:150px; - line-height:1.6; + font-size:13px; + max-width:170px; + line-height:1.5; } .header nav { @@ -98,10 +132,17 @@ min-height:44px; min-width:44px; justify-content:center; - font-size:13px; + font-size:15px; font-weight:650; } +.header nav a[aria-current="location"] { + text-decoration:underline; + text-decoration-color:var(--orange); + text-decoration-thickness:2px; + text-underline-offset:7px; +} + .content { position:relative; z-index:1; @@ -116,22 +157,22 @@ position:relative; display:flex; align-items:center; - width:calc(100% - 96px); - max-width:1344px; + width:var(--container); min-height:max(780px, 100svh); margin:auto; - padding:160px 0 100px; + padding:calc(160px + env(safe-area-inset-top)) 0 100px; pointer-events:none; } .heroCopy { - width:540px; + width:560px; max-width:100%; background:var(--paper); padding:36px 40px 34px; border:1px solid var(--line); border-radius:3px 3px 45px 3px; - box-shadow:7px 7px 0 rgb(0 0 0 / .15); + box-shadow:var(--card-shadow); + pointer-events:auto; } .introduction { @@ -165,37 +206,39 @@ align-items:center; gap:8px; font-family:var(--font-space-mono),monospace; - font-size:12px; - line-height:1.8; + font-size:13px; + line-height:1.7; color:var(--muted); } .hero h1 { margin:22px 0 0; - font-family:Georgia,'Times New Roman',serif; - font-size:clamp(76px,8.5vw,116px); - line-height:1.02; - letter-spacing:-7px; + font-family:var(--font-serif-display); + font-size:clamp(80px,9vw,124px); + line-height:1; + letter-spacing:-.03em; font-weight:400; } +/* Instrument Serif ships one upright file; keep "Lu." upright rather than faux-italic. */ .hero h1 em { color:var(--orange); + font-style:normal; font-weight:400; } .heroStatement { margin:20px 0 0; - font-size:clamp(25px,2.7vw,34px); + font-size:clamp(26px,2.7vw,34px); font-weight:600; line-height:1.14; - letter-spacing:-1.3px; + letter-spacing:-.04em; } .heroCopy > p { - max-width:41ch; - font-size:15px; - line-height:1.7; + max-width:40ch; + font-size:17px; + line-height:1.65; color:var(--muted); margin:18px 0 0; } @@ -213,53 +256,55 @@ gap:10px; align-items:center; min-height:46px; - font-size:14px; + font-size:15px; font-weight:700; - pointer-events:auto; } .heroActions a:first-child { padding:10px 16px; background:var(--green); - color:#fff9ee; + color:var(--on-green); + border:1px solid var(--control-edge); border-radius:3px; } -.heroActions svg, .contactCta svg { +.heroActions a:nth-child(2) { + text-decoration:underline; + text-underline-offset:5px; +} + +.heroActions svg, .contactCta svg, .copyButton svg, .contactEmail svg { width:16px; height:16px; + flex:0 0 auto; } +/* Pond controls: a small floating chip in the bottom corner, out of the hero. */ .pondSwitcher { - position:absolute; - top:122px; - right:0; + position:fixed; + z-index:20; + right:max(16px, env(safe-area-inset-right)); + bottom:max(16px, env(safe-area-inset-bottom)); + display:flex; + align-items:center; + gap:4px; + min-inline-size:0; margin:0; - padding:10px 14px; - border:1px solid var(--line); + padding:4px; + border:1px solid var(--line-strong); border-radius:4px; background:var(--paper); - pointer-events:auto; - width:234px; -} - -.pondSwitcher legend { - float:left; - width:100%; - padding:0; - color:var(--muted); - font-family:var(--font-space-mono),monospace; - font-size:12px; + color:var(--ink); + box-shadow:4px 4px 0 rgb(0 0 0 / .18); } .pondChoices { display:flex; - clear:both; + gap:2px; } .pondChoice { position:relative; - flex:1; cursor:pointer; } @@ -272,21 +317,22 @@ .pondChoice span { display:flex; - gap:8px; + gap:7px; align-items:center; justify-content:center; + min-width:44px; min-height:44px; - margin:5px 0; + padding:0 11px; border:1px solid transparent; + border-radius:2px; font-family:var(--font-space-mono),monospace; - font-size:12px; + font-size:13px; } .pondChoice input:checked + span { - border-color:var(--line); + border-color:var(--control-edge); background:var(--green); - color:#fff9ee; - border-radius:2px; + color:var(--on-green); } .pondChoice input:focus-visible + span { @@ -304,9 +350,26 @@ border-radius:50%; } -.pondCurrent { - font-size:12px; - color:var(--muted); +.feedButton { + display:inline-flex; + align-items:center; + gap:6px; + min-width:44px; + min-height:44px; + margin:0 0 0 2px; + padding:0 12px; + border:1px solid var(--line-strong); + border-radius:2px; + background:var(--paper); + color:var(--ink); + font-size:14px; + font-weight:650; + cursor:pointer; +} + +.feedButton:disabled { + opacity:.6; + cursor:wait; } .pondNote { @@ -317,9 +380,10 @@ padding:14px 18px; background:var(--paper); border:1px solid var(--line); - font-family:Georgia,serif; - font-size:21px; + font-family:var(--font-serif-display); + font-size:24px; transform:rotate(-3deg); + pointer-events:auto; } .pondNote > span { @@ -330,18 +394,18 @@ .pondNote small { display:block; margin-top:7px; - font:12px var(--font-space-mono),monospace; + font:13px var(--font-space-mono),monospace; color:var(--muted); } .workSection,.aboutSection,.contactSection { - width:min(calc(100% - 96px),1200px); - margin:0 auto 28px; + width:var(--container); + margin:0 auto var(--section-gap); padding:40px; background:var(--paper); border:1px solid var(--line); border-radius:4px; - scroll-margin-top:114px; + scroll-margin-top:calc(114px + env(safe-area-inset-top)); } .sectionIntro { @@ -356,17 +420,17 @@ } .sectionIntro h2,.aboutLead h2,.contactIntro h2 { - font-family:Georgia,serif; + font-family:var(--font-serif-display); font-weight:400; - font-size:clamp(34px,4vw,54px); - line-height:1.08; - letter-spacing:-2px; + font-size:clamp(40px,4.4vw,60px); + line-height:1.05; + letter-spacing:-.02em; margin:0; } .sectionIntro > p,.aboutLead > p,.iniuriaCard > p,.contactIntro > p { - font-size:14px; - line-height:1.8; + font-size:16px; + line-height:1.7; color:var(--muted); margin:0; align-self:end; @@ -391,7 +455,7 @@ .iniuriaCard h3,.moreWork h3 { font-size:27px; line-height:1.15; - letter-spacing:-1px; + letter-spacing:-.035em; font-weight:600; margin:15px 0; } @@ -425,11 +489,11 @@ } .moreGrid strong { - font-size:14px; + font-size:16px; } .moreGrid small { - font-size:12px; + font-size:14px; color:var(--muted); } @@ -455,20 +519,58 @@ } .contactLabel { - font:12px var(--font-space-mono),monospace; + font:13px var(--font-space-mono),monospace; color:var(--muted); } .contactBooking h3 { - font-size:23px; + font-size:24px; line-height:1.25; + letter-spacing:-.02em; margin:16px 0; } +.contactEmailRow { + display:flex; + flex-wrap:wrap; + align-items:center; + gap:8px 12px; + margin:0 0 20px; +} + +.contactEmail { + display:inline-flex; + align-items:center; + gap:10px; + min-height:44px; + font-size:18px; + font-weight:700; + overflow-wrap:anywhere; +} + +.copyButton { + display:inline-flex; + align-items:center; + gap:8px; + min-height:44px; + padding:0 14px; + border:1px solid var(--line-strong); + border-radius:3px; + background:transparent; + color:var(--ink); + font-size:14px; + font-weight:650; + cursor:pointer; +} + +.copyButton:hover { + background:color-mix(in srgb,var(--green) 10%,var(--paper)); +} + .contactFacts { display:grid; gap:12px; - font-size:12px; + font-size:14px; color:var(--muted); } @@ -489,11 +591,11 @@ justify-content:space-between; gap:24px; min-height:48px; - margin-top:24px; + margin-top:20px; color:var(--orange) !important; border-bottom:1px solid var(--orange); font-weight:700; - font-size:13px; + font-size:15px; } .footer { @@ -502,17 +604,20 @@ display:flex; justify-content:space-between; align-items:center; - width:min(calc(100% - 96px),1200px); - margin:0 auto 28px; + width:var(--container); + /* Leave room below the footer so the floating pond chip never covers its links. */ + margin:0 auto 88px; padding:16px 24px; + border:1px solid var(--line); border-radius:3px; background:var(--paper); - font:12px var(--font-space-mono),monospace; + font:13px var(--font-space-mono),monospace; } .footer > div { display:flex; - gap:22px; + flex-wrap:wrap; + gap:4px 22px; } .footer a { @@ -522,22 +627,40 @@ } @media (prefers-color-scheme:dark) { + :global(html):has(.page) { + --site-bg:#123a34; + --site-text:#f1ebdd; + --site-muted:#7fa587; + --site-accent:#3d5e45; + --site-contrast:#fff9ee; + scrollbar-color:#7fa587 #123a34; + } + :global(html):has(.page) :global(.skip-link) { + border-color:#7fa587; + } .page { --paper:#242d26; --ink:#f1ebdd; --muted:#b8c1b1; --line:#515e4e; + --line-strong:#6e7d6a; --orange:#f0a17b; --green:#3d5e45; + --control-edge:#7fa587; + --pond-deep:#123a34; + --art-bg:#2c372f; + --card-shadow:7px 7px 0 rgb(0 0 0 / .45); --header-shadow:0 2px 4px rgb(0 0 0 / .25), 0 10px 28px rgb(0 0 0 / .4); } } @media(max-width:800px) { + .page { + --container: calc(100% - 32px); + --section-gap: 64px; + } .header { - top:12px; - left:16px; - right:16px; + top:max(12px, env(safe-area-inset-top)); padding:0 18px; min-height:60px; } @@ -548,37 +671,31 @@ gap:20px; } .hero { - width:calc(100% - 32px); - min-height:930px; + min-height:max(640px, 100svh); align-items:flex-start; - padding-top:208px; - padding-bottom:110px; + padding-top:calc(100px + env(safe-area-inset-top)); + padding-bottom:170px; } .heroCopy { width:480px; padding:28px; } .hero h1 { - font-size:clamp(56px, 17vw, 80px); - letter-spacing:-4px; + font-size:clamp(64px, 19vw, 88px); white-space:nowrap; } .heroStatement { font-size:29px; } - .pondSwitcher { - top:90px; - right:0; - width:220px; + .heroActions a { + font-size:15px; } .pondNote { - bottom:45px; + bottom:84px; right:10px; } .workSection,.aboutSection,.contactSection { - width:calc(100% - 32px); padding:24px; - margin-bottom:20px; } .sectionIntro,.aboutSection,.moreWork,.contactSection { grid-template-columns:1fr; @@ -588,7 +705,7 @@ grid-column:auto; } .sectionIntro h2,.aboutLead h2,.contactIntro h2 { - font-size:38px; + font-size:44px; } .iniuriaCard,.contactBooking { padding:24px 0 0; @@ -599,24 +716,28 @@ grid-column:auto; } .footer { - width:calc(100% - 32px); + flex-wrap:wrap; + gap:4px; padding:12px 18px; - font-size:12px; + } + .pondSwitcher { + right:max(12px, env(safe-area-inset-right)); + bottom:max(12px, env(safe-area-inset-bottom)); } } @media(max-width:380px) { .header nav { - gap:14px; + gap:12px; } .header nav a { - font-size:12px; + font-size:14px; } .heroCopy { - padding:24px; + padding:22px; } .hero h1 { - font-size:clamp(52px, 17vw, 64px); + font-size:clamp(58px, 19vw, 72px); } .heroStatement { font-size:25px; @@ -625,10 +746,13 @@ gap:10px; } .heroActions a { - font-size:12px; + font-size:14px; } - .footer { - flex-wrap:wrap; + .pondChoice span { + padding:0 8px; + } + .feedButton { + padding:0 9px; } } @@ -637,14 +761,3 @@ border:2px solid Highlight; } } - -.heroActions a:nth-child(2) { text-decoration:underline; text-underline-offset:5px; } -@media(max-width:800px) { .heroActions a { font-size:14px; } } - -.header nav a[aria-current="location"] { text-decoration:underline; text-decoration-color:var(--orange); text-decoration-thickness:2px; text-underline-offset:7px; } - -@media(max-width:800px) { .footer { flex-wrap:wrap; gap:4px; } } -.feedButton { display:block; width:100%; min-height:44px; margin-top:8px; border:1px solid var(--line); border-radius:3px; background:var(--paper); color:var(--ink); font-size:14px; cursor:pointer; } -.feedButton:disabled { opacity:.6; cursor:wait; } - -@media(max-width:800px) { .hero { padding-top:270px; min-height:1050px; } } diff --git a/components/concepts/signal-desk/signal-desk.tsx b/components/concepts/signal-desk/signal-desk.tsx index bc9aaec..0addf20 100644 --- a/components/concepts/signal-desk/signal-desk.tsx +++ b/components/concepts/signal-desk/signal-desk.tsx @@ -1,15 +1,13 @@ import { useEffect, useRef, useState } from "react"; -import { AnimatePresence, motion } from "framer-motion"; -import { ArrowDown, ArrowUpRight, Github, Globe2, Mail, MapPin } from "lucide-react"; +import { motion } from "framer-motion"; +import { ArrowDown, ArrowUpRight, CalendarDays, Check, Copy, Github, Globe2, Mail, MapPin } from "lucide-react"; import { portfolioIdentity, portfolioProjects, type PortfolioProject, - type PortfolioProjectId, } from "@/lib/portfolio-content"; import { PondEnvironment } from "./pond-environment"; -import { Button } from "@/components/ui/button"; import { SkulletImage } from "@/components/ui/skullet-image"; import projectStyles from "./project-explorer.module.css"; import styles from "./signal-desk.module.css"; @@ -27,10 +25,6 @@ function motionValue(reduced: boolean, value: number) { return reduced ? 0 : value; } -function motionExit(reduced: boolean, value: T) { - return reduced ? undefined : value; -} - function useReducedMotionPreference() { const [preference, setPreference] = useState({ ready: false, reduced: true }); @@ -57,6 +51,7 @@ function SiteFooter({ reduced, entrance, ready }: { reduced: boolean; entrance: > © {import.meta.env.BUILD_YEAR} Lu / 6C75
+ Email Privacy GitHub @x6c75 @@ -99,108 +94,44 @@ function ProjectArtwork({ project }: { project: PortfolioProject }) { ); } -function ProjectNavigation({ - activeId, - reduced, - onSelect, -}: { - activeId: PortfolioProjectId; - reduced: boolean; - onSelect: (id: PortfolioProjectId) => void; -}) { +function ProjectCard({ project }: { project: PortfolioProject }) { return ( -
- {portfolioProjects.map((project) => { - const active = project.id === activeId; - return ( - - ); - })} -
- ); -} - -function ProjectPanel({ project, reduced }: { project: PortfolioProject; reduced: boolean }) { - return ( - - - - - - - - {project.category} -

{project.name}

-

{project.summary}

-

{project.detail}

-
- {project.proof?.map((item) => {item})} -
-
- - {project.secondaryHref && ( - - )} -
-
-
-
-
+
+
+ {project.category} +

{project.name}

+ {project.figure && ( +

+ {project.figure.value} + {project.figure.label} +

+ )} +

{project.summary}

+

{project.detail}

+ {project.proof && project.proof.length > 0 && ( +
+ {project.proof.map((item) => {item})} +
+ )} +
+ + View project {project.name} + {project.secondaryHref && ( + + {project.secondaryLabel} + )} +
+
+
+ +
+
); } function ProjectExplorer({ reduced, entrance, ready }: { reduced: boolean; entrance: boolean; ready: boolean }) { - const [activeId, setActiveId] = useState(portfolioProjects[0]?.id); - const activeIndex = Math.max(0, portfolioProjects.findIndex((project) => project.id === activeId)); - const activeProject = portfolioProjects[activeIndex] ?? portfolioProjects[0]; - - if (!activeProject) return null; - return (
01 / Things I’ve made

A few useful obsessions.

-

Native apps, agent infrastructure, Linux systems. Pick a project to inspect the work.

+

Four projects, what each one actually does, and where to find it.

- - + {portfolioProjects.map((project) => )} - -
); } +function CopyEmailButton() { + const [copied, setCopied] = useState(false); + const [status, setStatus] = useState(""); + + useEffect(() => { + if (!copied) return; + const timer = window.setTimeout(() => setCopied(false), 2400); + return () => window.clearTimeout(timer); + }, [copied]); + + return ( + <> + + {status} + + ); +} + export function SignalDesk() { const { ready, reduced } = useReducedMotionPreference(); const [pondView, setPondView] = useState<"ascii" | "pixel">("ascii"); @@ -277,7 +231,7 @@ export function SignalDesk() { return (
-

The decorative pond responds to pointer movement. Right-click open water, or double-tap it on touch devices, to feed the fish, or use the Feed fish button.

+

The decorative pond responds to pointer movement. Click or tap open water to feed the fish, or use the Feed fish button in the pond controls.

@@ -287,42 +241,17 @@ export function SignalDesk() {
- -
- Pond view -
- {(["ascii", "pixel"] as const).map((view) => ( -