Compare commits
5 Commits
163c68fcb8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fe3516b9e | |||
| bef8f8414b | |||
| f3e9cdf0bd | |||
| 7ecc0f68d7 | |||
| 00a26a9e49 |
@@ -10,21 +10,7 @@ export default defineConfig({
|
|||||||
org: "mikefreno",
|
org: "mikefreno",
|
||||||
project: "freno-dev",
|
project: "freno-dev",
|
||||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||||
telemetry: false,
|
telemetry: false
|
||||||
sourcemaps: {
|
|
||||||
assets: [
|
|
||||||
{
|
|
||||||
type: "bundle",
|
|
||||||
path: "dist/client/assets/",
|
|
||||||
urlPrefix: "~/assets/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "sourcemap",
|
|
||||||
path: "dist/client/assets/",
|
|
||||||
urlPrefix: "~/assets/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
"test:watch": "bun test --watch",
|
"test:watch": "bun test --watch",
|
||||||
"test:coverage": "bun test --coverage",
|
"test:coverage": "bun test --coverage",
|
||||||
"perf": "bun run scripts/perf-test.ts",
|
"perf": "bun run scripts/perf-test.ts",
|
||||||
"perf:compare": "bun run scripts/perf-compare.ts"
|
"perf:compare": "bun run scripts/perf-compare.ts",
|
||||||
|
"nook:grant": "NODE_ENV=production bun --env-file=.env scripts/grant-nook-license.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.953.0",
|
"@aws-sdk/client-s3": "^3.953.0",
|
||||||
|
|||||||
Binary file not shown.
@@ -1,9 +1,21 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Allow: /
|
Allow: /
|
||||||
Allow: /blog
|
Allow: /blog
|
||||||
Allow: /projects
|
|
||||||
Disallow: /login
|
# Private / dynamic paths — not for indexing.
|
||||||
Disallow: /debug/
|
# Keeps crawlers off auth, admin, and API pages so they don't burn
|
||||||
|
# uncached SSR function invocations.
|
||||||
|
Disallow: /account
|
||||||
|
Disallow: /analytics
|
||||||
|
Disallow: /api/
|
||||||
|
Disallow: /blog/create
|
||||||
|
Disallow: /blog/edit
|
||||||
|
Disallow: /checkout
|
||||||
Disallow: /databaseMGMT
|
Disallow: /databaseMGMT
|
||||||
|
Disallow: /debug/
|
||||||
|
Disallow: /error-test
|
||||||
|
Disallow: /login
|
||||||
|
Disallow: /success
|
||||||
|
Disallow: /test
|
||||||
|
|
||||||
Sitemap: https://www.freno.me/sitemap.xml
|
Sitemap: https://www.freno.me/sitemap.xml
|
||||||
|
|||||||
29
src/components/EdgeCacheHeaders.tsx
Normal file
29
src/components/EdgeCacheHeaders.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { HttpHeader } from "@solidjs/start";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders edge-cache response headers for a page route.
|
||||||
|
*
|
||||||
|
* Sets `CDN-Cache-Control` so Vercel serves the rendered HTML from the edge
|
||||||
|
* (no origin re-render) for `maxAge` seconds, then stale-while-revalidate up
|
||||||
|
* to `staleSeconds`. `Cache-Control: public, max-age=0` keeps browsers
|
||||||
|
* revalidating, so visitors always get the latest version — only the CDN
|
||||||
|
* holds a cached copy. Function/CND-Cache-Control overrides the Vercel
|
||||||
|
* default, so repeated visits and bot crawls stop re-rendering at origin.
|
||||||
|
*/
|
||||||
|
export function EdgeCacheHeaders(props: {
|
||||||
|
/** Seconds the CDN may serve the response fresh. */
|
||||||
|
maxAge: number;
|
||||||
|
/** Seconds the CDN serves stale while revalidating (default: 1 day). */
|
||||||
|
staleSeconds?: number;
|
||||||
|
}) {
|
||||||
|
const stale = props.staleSeconds ?? 86400;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HttpHeader name="Cache-Control" value="public, max-age=0" />
|
||||||
|
<HttpHeader
|
||||||
|
name="CDN-Cache-Control"
|
||||||
|
value={`public, s-maxage=${props.maxAge}, stale-while-revalidate=${stale}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
import { HttpStatusCode } from "@solidjs/start";
|
import { HttpHeader, HttpStatusCode } from "@solidjs/start";
|
||||||
import { useLocation, useNavigate } from "@solidjs/router";
|
import { useLocation, useNavigate } from "@solidjs/router";
|
||||||
import { createSignal, onCleanup, onMount, Show } from "solid-js";
|
import { createSignal, onCleanup, onMount, Show } from "solid-js";
|
||||||
import { TerminalErrorPage } from "~/components/TerminalErrorPage";
|
import { TerminalErrorPage } from "~/components/TerminalErrorPage";
|
||||||
@@ -90,6 +90,14 @@ export default function NotFound() {
|
|||||||
description="404 - Page not found. The page you're looking for doesn't exist."
|
description="404 - Page not found. The page you're looking for doesn't exist."
|
||||||
/>
|
/>
|
||||||
<HttpStatusCode code={404} />
|
<HttpStatusCode code={404} />
|
||||||
|
{/* Cache 404/fallback responses at the edge (Vercel caches 404s) so
|
||||||
|
bots/monitors that probe dead paths don't re-render the full page
|
||||||
|
on every request. Function headers override vercel.json here. */}
|
||||||
|
<HttpHeader name="Cache-Control" value="public, max-age=0" />
|
||||||
|
<HttpHeader
|
||||||
|
name="CDN-Cache-Control"
|
||||||
|
value="public, s-maxage=300, stale-while-revalidate=86400"
|
||||||
|
/>
|
||||||
<TerminalErrorPage
|
<TerminalErrorPage
|
||||||
errorContent={errorContent}
|
errorContent={errorContent}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
|
|||||||
23
src/routes/api/health.ts
Normal file
23
src/routes/api/health.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { APIEvent } from "@solidjs/start/server";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight uptime/monitoring probe.
|
||||||
|
*
|
||||||
|
* Returns a tiny 200 that the edge CDN caches (`CDN-Cache-Control`), so
|
||||||
|
* health checks (Sentry uptime monitors, external monitors, `curl`) cost a
|
||||||
|
* fraction of a rendered page instead of a full SSR response. Point monitors
|
||||||
|
* at `https://freno.me/api/health`.
|
||||||
|
*/
|
||||||
|
export async function GET(_event: APIEvent) {
|
||||||
|
return new Response(JSON.stringify({ status: "ok" }), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
// Browser: always revalidate (tiny body, no reason to cache).
|
||||||
|
"Cache-Control": "public, max-age=0",
|
||||||
|
// Edge CDN: serve from cache for 1 min, then stale-while-revalidate
|
||||||
|
// for a day so repeated probe hits never touch the origin.
|
||||||
|
"CDN-Cache-Control": "public, s-maxage=60, stale-while-revalidate=86400"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
useSearchParams
|
useSearchParams
|
||||||
} from "@solidjs/router";
|
} from "@solidjs/router";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import { createAsync } from "@solidjs/router";
|
import { createAsync } from "@solidjs/router";
|
||||||
import { getRequestEvent } from "solid-js/web";
|
import { getRequestEvent } from "solid-js/web";
|
||||||
import AuthenticatedLike from "~/components/blog/AuthenticatedLike";
|
import AuthenticatedLike from "~/components/blog/AuthenticatedLike";
|
||||||
@@ -327,6 +328,7 @@ export default function PostPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={60} staleSeconds={3600} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title={p().title.replaceAll("_", " ")}
|
title={p().title.replaceAll("_", " ")}
|
||||||
description={
|
description={
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Show } from "solid-js";
|
import { Show } from "solid-js";
|
||||||
import { useSearchParams, A, query } from "@solidjs/router";
|
import { useSearchParams, A, query } from "@solidjs/router";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import { createAsync } from "@solidjs/router";
|
import { createAsync } from "@solidjs/router";
|
||||||
import PostSortingSelect from "~/components/blog/PostSortingSelect";
|
import PostSortingSelect from "~/components/blog/PostSortingSelect";
|
||||||
import TagSelector from "~/components/blog/TagSelector";
|
import TagSelector from "~/components/blog/TagSelector";
|
||||||
@@ -90,6 +91,7 @@ export default function BlogIndex() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={60} staleSeconds={3600} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Blog"
|
title="Blog"
|
||||||
description="Technical blog posts about web development, programming, and software engineering."
|
description="Technical blog posts about web development, programming, and software engineering."
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useSearchParams } from "@solidjs/router";
|
|||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import RevealDropDown from "~/components/RevealDropDown";
|
import RevealDropDown from "~/components/RevealDropDown";
|
||||||
import { ContactForm } from "~/components/ContactForm";
|
import { ContactForm } from "~/components/ContactForm";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import { buildSubdomainUrl } from "~/lib/site-context";
|
import { buildSubdomainUrl } from "~/lib/site-context";
|
||||||
import { useSite } from "~/context/SiteContext";
|
import { useSite } from "~/context/SiteContext";
|
||||||
import NessaContactPage from "./nessa/contact";
|
import NessaContactPage from "./nessa/contact";
|
||||||
@@ -125,6 +126,8 @@ function MainContactPage() {
|
|||||||
const viewer = () => searchParams.viewer ?? "default";
|
const viewer = () => searchParams.viewer ?? "default";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={60} staleSeconds={3600} />
|
||||||
<ContactForm
|
<ContactForm
|
||||||
subline={
|
subline={
|
||||||
<Show when={viewer() !== "lineage"}>
|
<Show when={viewer() !== "lineage"}>
|
||||||
@@ -134,6 +137,7 @@ function MainContactPage() {
|
|||||||
>
|
>
|
||||||
<LineageContactQuestions />
|
<LineageContactQuestions />
|
||||||
</ContactForm>
|
</ContactForm>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { createSignal, onMount, onCleanup, Switch, Match } from "solid-js";
|
import { createSignal, onMount, onCleanup, Switch, Match } from "solid-js";
|
||||||
import DownloadOnAppStore from "~/components/icons/DownloadOnAppStore";
|
import DownloadOnAppStore from "~/components/icons/DownloadOnAppStore";
|
||||||
@@ -71,6 +72,7 @@ function MainDownloadsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Downloads"
|
title="Downloads"
|
||||||
description="Download The Nook, InputHalo, Gaze, Life and Lineage, Shapes with Abigail, and Cork. Available on macOS, iOS, and Android."
|
description="Download The Nook, InputHalo, Gaze, Life and Lineage, Shapes with Abigail, and Cork. Available on macOS, iOS, and Android."
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ContactForm } from "~/components/ContactForm";
|
import { ContactForm } from "~/components/ContactForm";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +20,7 @@ import SubdomainHeader from "~/components/SubdomainHeader";
|
|||||||
export default function GazeContactPage() {
|
export default function GazeContactPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={60} staleSeconds={3600} />
|
||||||
<SubdomainHeader />
|
<SubdomainHeader />
|
||||||
<ContactForm />
|
<ContactForm />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { createSignal } from "solid-js";
|
import { createSignal } from "solid-js";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
|
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
|
||||||
import Button from "~/components/ui/Button";
|
import Button from "~/components/ui/Button";
|
||||||
@@ -48,6 +49,7 @@ export default function GazeDownloadsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Download Gaze"
|
title="Download Gaze"
|
||||||
description="Download Gaze for macOS — menu bar app for eye and posture health."
|
description="Download Gaze for macOS — menu bar app for eye and posture health."
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createSignal, For } from "solid-js";
|
import { createSignal, For } from "solid-js";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
|
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
|
||||||
import Button from "~/components/ui/Button";
|
import Button from "~/components/ui/Button";
|
||||||
@@ -60,6 +61,7 @@ export default function GazeLanding() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Home"
|
title="Home"
|
||||||
description="Gaze is a macOS menu bar app for eye and posture health — blink reminders, 20-20-20 breaks, posture check-ins, and customizable reminder intervals."
|
description="Gaze is a macOS menu bar app for eye and posture health — blink reminders, 20-20-20 breaks, posture check-ins, and customizable reminder intervals."
|
||||||
|
|||||||
@@ -15,11 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
|
|
||||||
export default function GazePrivacyPolicy() {
|
export default function GazePrivacyPolicy() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Privacy Policy"
|
title="Privacy Policy"
|
||||||
description="Privacy policy for Gaze, a macOS eye health reminder app."
|
description="Privacy policy for Gaze, a macOS eye health reminder app."
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Switch, Match, type JSX } from "solid-js";
|
import { Switch, Match, type JSX } from "solid-js";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import { DarkModeToggle } from "~/components/DarkModeToggle";
|
import { DarkModeToggle } from "~/components/DarkModeToggle";
|
||||||
import { Typewriter } from "~/components/Typewriter";
|
import { Typewriter } from "~/components/Typewriter";
|
||||||
import { useSite } from "~/context/SiteContext";
|
import { useSite } from "~/context/SiteContext";
|
||||||
@@ -57,6 +58,7 @@ export default function Home(): JSX.Element {
|
|||||||
function MainHome(): JSX.Element {
|
function MainHome(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Home"
|
title="Home"
|
||||||
description="Michael Freno - Software Engineer based in Brooklyn, NY"
|
description="Michael Freno - Software Engineer based in Brooklyn, NY"
|
||||||
@@ -111,7 +113,7 @@ function MainHome(): JSX.Element {
|
|||||||
>
|
>
|
||||||
The Nook
|
The Nook
|
||||||
</a>
|
</a>
|
||||||
<div class="mx-auto w-full max-w-2xl overflow-hidden rounded-lg">
|
<div class="mx-auto w-full max-w-4xl overflow-hidden rounded-lg">
|
||||||
<video
|
<video
|
||||||
src="/nook/demo-expansion.mp4"
|
src="/nook/demo-expansion.mp4"
|
||||||
class="h-full w-full object-cover"
|
class="h-full w-full object-cover"
|
||||||
@@ -122,9 +124,9 @@ function MainHome(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-2 text-left text-sm">
|
<div class="pt-2 text-left text-sm">
|
||||||
A native macOS island that lives in the notch: orchestration
|
A native macOS island that lives in the notch: orchestration for
|
||||||
for a fleet of coding agents, fan curve control, thermal
|
a fleet of coding agents, fan curve control, thermal gauges,
|
||||||
gauges, calendar and camera modules. One-time purchase - no
|
calendar and camera modules. One-time purchase - no
|
||||||
subscription, ever.
|
subscription, ever.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,7 +242,6 @@ function MainHome(): JSX.Element {
|
|||||||
one.
|
one.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between pb-8 text-center">
|
<div class="flex justify-between pb-8 text-center">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ContactForm } from "~/components/ContactForm";
|
import { ContactForm } from "~/components/ContactForm";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +20,7 @@ import SubdomainHeader from "~/components/SubdomainHeader";
|
|||||||
export default function InputHaloContactPage() {
|
export default function InputHaloContactPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={60} staleSeconds={3600} />
|
||||||
<SubdomainHeader />
|
<SubdomainHeader />
|
||||||
<ContactForm />
|
<ContactForm />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { createSignal } from "solid-js";
|
import { createSignal } from "solid-js";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
|
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
|
||||||
import Button from "~/components/ui/Button";
|
import Button from "~/components/ui/Button";
|
||||||
@@ -51,6 +52,7 @@ export default function InputHaloDownloadsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Download InputHalo"
|
title="Download InputHalo"
|
||||||
description="Download InputHalo for macOS — menu bar app for keyboard, mouse, and scroll visualization."
|
description="Download InputHalo for macOS — menu bar app for keyboard, mouse, and scroll visualization."
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
import { For, createSignal } from "solid-js";
|
import { For, createSignal } from "solid-js";
|
||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import Button from "~/components/ui/Button";
|
import Button from "~/components/ui/Button";
|
||||||
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
|
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
|
||||||
@@ -98,6 +99,7 @@ export default function InputHaloLanding() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Home"
|
title="Home"
|
||||||
description="A polished macOS menu bar app that visualizes keyboard presses, mouse clicks, cursor halos, and scroll events on screen — for streamers, presenters, and developers."
|
description="A polished macOS menu bar app that visualizes keyboard presses, mouse clicks, cursor halos, and scroll events on screen — for streamers, presenters, and developers."
|
||||||
|
|||||||
@@ -23,11 +23,13 @@
|
|||||||
*/
|
*/
|
||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
|
|
||||||
export default function InputHaloPrivacyPolicy() {
|
export default function InputHaloPrivacyPolicy() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Privacy Policy"
|
title="Privacy Policy"
|
||||||
description="Privacy policy for InputHalo, a macOS menu bar productivity app."
|
description="Privacy policy for InputHalo, a macOS menu bar productivity app."
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ContactForm } from "~/components/ContactForm";
|
import { ContactForm } from "~/components/ContactForm";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import { LineageContactQuestions } from "../contact";
|
import { LineageContactQuestions } from "../contact";
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ import { LineageContactQuestions } from "../contact";
|
|||||||
export default function LineageContactPage() {
|
export default function LineageContactPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={60} staleSeconds={3600} />
|
||||||
<SubdomainHeader />
|
<SubdomainHeader />
|
||||||
<ContactForm>
|
<ContactForm>
|
||||||
<LineageContactQuestions />
|
<LineageContactQuestions />
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
* the form posts to the correct tRPC mutation (Lineage-branded email).
|
* the form posts to the correct tRPC mutation (Lineage-branded email).
|
||||||
*/
|
*/
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import DeletionForm from "~/components/DeletionForm";
|
import DeletionForm from "~/components/DeletionForm";
|
||||||
import {
|
import {
|
||||||
@@ -45,6 +46,7 @@ import {
|
|||||||
export default function LineageDeletionPage() {
|
export default function LineageDeletionPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead title={PAGE_META.title} description={PAGE_META.description} />
|
<PageHead title={PAGE_META.title} description={PAGE_META.description} />
|
||||||
<SubdomainHeader />
|
<SubdomainHeader />
|
||||||
<div class="pt-20">
|
<div class="pt-20">
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { createSignal, onMount, onCleanup } from "solid-js";
|
import { createSignal, onMount, onCleanup } from "solid-js";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import DownloadOnAppStore from "~/components/icons/DownloadOnAppStore";
|
import DownloadOnAppStore from "~/components/icons/DownloadOnAppStore";
|
||||||
import Button from "~/components/ui/Button";
|
import Button from "~/components/ui/Button";
|
||||||
@@ -71,6 +72,7 @@ export default function LineageDownloadsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead title={PAGE_META.title} description={PAGE_META.description} />
|
<PageHead title={PAGE_META.title} description={PAGE_META.description} />
|
||||||
|
|
||||||
<SubdomainHeader />
|
<SubdomainHeader />
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import SimpleParallax from "~/components/SimpleParallax";
|
import SimpleParallax from "~/components/SimpleParallax";
|
||||||
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
|
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
|
||||||
@@ -25,6 +26,7 @@ import {
|
|||||||
export default function LineageLandingPage() {
|
export default function LineageLandingPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead title={PAGE_META.title} description={PAGE_META.description} />
|
<PageHead title={PAGE_META.title} description={PAGE_META.description} />
|
||||||
<SubdomainHeader />
|
<SubdomainHeader />
|
||||||
<SimpleParallax>
|
<SimpleParallax>
|
||||||
|
|||||||
@@ -17,11 +17,13 @@
|
|||||||
*/
|
*/
|
||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
|
|
||||||
export default function LineagePrivacyPolicy() {
|
export default function LineagePrivacyPolicy() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Privacy Policy"
|
title="Privacy Policy"
|
||||||
description="Privacy policy for Life and Lineage mobile game, outlining data collection, usage, and user rights."
|
description="Privacy policy for Life and Lineage mobile game, outlining data collection, usage, and user rights."
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ContactForm } from "~/components/ContactForm";
|
import { ContactForm } from "~/components/ContactForm";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,6 +21,7 @@ import SubdomainHeader from "~/components/SubdomainHeader";
|
|||||||
export default function NessaContactPage() {
|
export default function NessaContactPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={60} staleSeconds={3600} />
|
||||||
<SubdomainHeader />
|
<SubdomainHeader />
|
||||||
<ContactForm />
|
<ContactForm />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
* Acceptance: `nessa.localhost:3000/deletion` renders the deletion form.
|
* Acceptance: `nessa.localhost:3000/deletion` renders the deletion form.
|
||||||
*/
|
*/
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import DeletionForm from "~/components/DeletionForm";
|
import DeletionForm from "~/components/DeletionForm";
|
||||||
import {
|
import {
|
||||||
@@ -36,6 +37,7 @@ import {
|
|||||||
export default function NessaDeletionPage() {
|
export default function NessaDeletionPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead title={PAGE_META.title} description={PAGE_META.description} />
|
<PageHead title={PAGE_META.title} description={PAGE_META.description} />
|
||||||
<SubdomainHeader />
|
<SubdomainHeader />
|
||||||
<div class="pt-20">
|
<div class="pt-20">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
import { For, Show } from "solid-js";
|
import { For, Show } from "solid-js";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import { useDarkMode } from "~/context/darkMode";
|
import { useDarkMode } from "~/context/darkMode";
|
||||||
import { useSite } from "~/context/SiteContext";
|
import { useSite } from "~/context/SiteContext";
|
||||||
@@ -59,6 +60,7 @@ export default function NessaLanding() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead {...NESSA_LANDING_META} />
|
<PageHead {...NESSA_LANDING_META} />
|
||||||
|
|
||||||
<SubdomainHeader />
|
<SubdomainHeader />
|
||||||
|
|||||||
@@ -29,11 +29,13 @@
|
|||||||
*/
|
*/
|
||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
|
|
||||||
export default function NessaPrivacyPolicy() {
|
export default function NessaPrivacyPolicy() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Privacy Policy"
|
title="Privacy Policy"
|
||||||
description="Privacy policy for Nessa, a community platform for clubs, challenges, and social features."
|
description="Privacy policy for Nessa, a community platform for clubs, challenges, and social features."
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ export default function NookCheckout() {
|
|||||||
|
|
||||||
{error() && <p class="text-red mt-4 text-sm">{error()}</p>}
|
{error() && <p class="text-red mt-4 text-sm">{error()}</p>}
|
||||||
<p class="text-subtext1 mt-4 text-xs">
|
<p class="text-subtext1 mt-4 text-xs">
|
||||||
Billed once through Stripe. License delivered immediately after
|
Billed once through Stripe. License delivered immediately and by
|
||||||
payment.
|
email after payment.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
import FeatureCollage from "~/components/nook/FeatureCollage";
|
import FeatureCollage from "~/components/nook/FeatureCollage";
|
||||||
import FeatureBreakdowns from "~/components/nook/FeatureBreakdowns";
|
import FeatureBreakdowns from "~/components/nook/FeatureBreakdowns";
|
||||||
@@ -57,6 +58,7 @@ export default function NookLanding() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Home"
|
title="Home"
|
||||||
description="The Nook — a native macOS utility for coding-agent orchestration, fan and thermal control."
|
description="The Nook — a native macOS utility for coding-agent orchestration, fan and thermal control."
|
||||||
|
|||||||
@@ -7,11 +7,13 @@
|
|||||||
*/
|
*/
|
||||||
import { buildMainSiteUrl } from "~/lib/subdomain-url";
|
import { buildMainSiteUrl } from "~/lib/subdomain-url";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
import SubdomainHeader from "~/components/SubdomainHeader";
|
import SubdomainHeader from "~/components/SubdomainHeader";
|
||||||
|
|
||||||
export default function NookPrivacyPolicy() {
|
export default function NookPrivacyPolicy() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Privacy Policy"
|
title="Privacy Policy"
|
||||||
description="Privacy policy for The Nook, a coding-agent orchestration and hardware control app."
|
description="Privacy policy for The Nook, a coding-agent orchestration and hardware control app."
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main-site privacy policy — `freno.me/privacy-policy`.
|
* Main-site privacy policy — `freno.me/privacy-policy`.
|
||||||
@@ -14,6 +15,7 @@ import { PageHead } from "~/components/PageHead";
|
|||||||
export default function PrivacyPolicy() {
|
export default function PrivacyPolicy() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Privacy Policy"
|
title="Privacy Policy"
|
||||||
description="Privacy policy for the freno.me blog and personal site, covering accounts, comments, and contact forms."
|
description="Privacy policy for the freno.me blog and personal site, covering accounts, comments, and contact forms."
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
|
|
||||||
export default function PrivacyPolicy() {
|
export default function PrivacyPolicy() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Privacy Policy - Shapes with Abigail"
|
title="Privacy Policy - Shapes with Abigail"
|
||||||
description="Privacy policy for Shapes with Abigail app, explaining our commitment to child safety and non-collection of personal data."
|
description="Privacy policy for Shapes with Abigail app, explaining our commitment to child safety and non-collection of personal data."
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { onCleanup, onMount } from "solid-js";
|
import { onCleanup, onMount } from "solid-js";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
|
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
|
||||||
|
|
||||||
export default function Resume() {
|
export default function Resume() {
|
||||||
let iframeRef: HTMLIFrameElement | undefined;
|
let iframeRef: HTMLIFrameElement | undefined;
|
||||||
@@ -25,6 +26,7 @@ export default function Resume() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EdgeCacheHeaders maxAge={300} />
|
||||||
<PageHead
|
<PageHead
|
||||||
title="Resume"
|
title="Resume"
|
||||||
description="View Michael Freno's resume - Software Engineer."
|
description="View Michael Freno's resume - Software Engineer."
|
||||||
|
|||||||
Reference in New Issue
Block a user