diff --git a/hugo-site/content/_index.md b/hugo-site/content/_index.md
index 1d1053b5..92288373 100644
--- a/hugo-site/content/_index.md
+++ b/hugo-site/content/_index.md
@@ -9,7 +9,12 @@ Freenet is a peer-to-peer platform for decentralized applications: communication
commerce without reliance on big tech. Your computer becomes part of a global network where apps
are unstoppable, interoperable, and built on open protocols.
-Try Freenet
+Install Freenet
+
+
+ Or try it in your browser first →
+ A hosted demo so you can try Freenet without installing. It runs on our server, not the network, so when you're ready, run your own peer and keep using the same rooms on the real network.
+
diff --git a/hugo-site/content/quickstart/_index.md b/hugo-site/content/quickstart/_index.md
index d873de09..1facb0b1 100644
--- a/hugo-site/content/quickstart/_index.md
+++ b/hugo-site/content/quickstart/_index.md
@@ -27,7 +27,7 @@ browser.
## Step 2: Join the room
-Click below to get an invite to the **Freenet Official** room. Invites are limited to 5 per day.
+Click below to join the **Freenet Official** room. Invites are limited to 20 per day.
{{< river-invite-button room="Freenet Official" >}}
diff --git a/hugo-site/content/try/_index.md b/hugo-site/content/try/_index.md
new file mode 100644
index 00000000..6251d338
--- /dev/null
+++ b/hugo-site/content/try/_index.md
@@ -0,0 +1,25 @@
+---
+title: "Try Freenet in your browser"
+date: 2026-07-07
+draft: false
+---
+
+Try Freenet right now, no install. This opens **River**, a group chat app that runs as a
+peer-to-peer Freenet contract, and drops you into the live **Freenet Official** room where the
+project's developers and users talk. No account, no download.
+
+{{< alert type="warning" >}} **This is a hosted demo.** It runs on our server, not the
+decentralized network, so you can try Freenet without installing anything. It's a taste, not the
+real thing: the whole point of Freenet is that apps run peer-to-peer on *your* computer, with no
+company in the middle. When you're ready, [install your own peer](/quickstart/) and keep using the same
+rooms on the real network. {{< /alert >}}
+
+{{< river-invite-button room="Freenet Official" base="https://try.freenet.org/v1/contract/web/raAqMhMG7KUpXBU2SxgCQ3Vh4PYjttxdSWd9ftV7RLv/" hosted="true" >}}
+
+## Ready for the real thing?
+
+The hosted demo is centralized, which is the opposite of what Freenet is for. Running your own peer
+takes a couple of minutes and gives you the real deal: apps that can't be taken down, don't track
+you, and run without any server.
+
+[Install Freenet →](/quickstart/)
diff --git a/hugo-site/themes/freenet/assets/css/freenet.css b/hugo-site/themes/freenet/assets/css/freenet.css
index 6581d239..3bedfd89 100644
--- a/hugo-site/themes/freenet/assets/css/freenet.css
+++ b/hugo-site/themes/freenet/assets/css/freenet.css
@@ -128,10 +128,17 @@
font-size: 0.9rem;
}
+/* Hero spacing: group the pitch (headline + paragraph) tightly, then a clear
+ gap to the action group (button + try link + note), which is itself tight.
+ Even gaps made it unclear what grouped with what (the button floated). */
+.home > h1.title {
+ margin-bottom: 0.9rem;
+}
+
/* Primary CTA button */
.cta-button {
display: inline-block;
- margin: 1.5rem 0 2rem 0;
+ margin: 1.75rem 0 0 0;
background: linear-gradient(135deg, #0066CC 0%, #004C99 100%);
color: #ffffff !important;
border: none;
@@ -150,6 +157,45 @@
color: #ffffff !important;
}
+/* Secondary CTA: the hosted-demo "try in your browser" entry. Kept visually
+ subordinate to the primary Install button so the real (self-hosted) path
+ stays front and center. */
+.cta-secondary-wrap {
+ margin: 1rem 0 3.5rem 0;
+ max-width: 620px;
+}
+
+.cta-secondary {
+ display: inline-block;
+ font-family: var(--font-display);
+ font-weight: 600;
+ color: #0066CC;
+ text-decoration: none;
+}
+
+.cta-secondary:hover {
+ text-decoration: underline;
+}
+
+.cta-note {
+ display: block;
+ margin-top: 0.4rem;
+ font-size: 0.9rem;
+ line-height: 1.45;
+ color: #64748b;
+}
+
+/* Dark mode: match the site's existing link (#4da6ff) and muted-text (#94a3b8)
+ dark overrides so the new hero links aren't dim / low-contrast. */
+@media (prefers-color-scheme: dark) {
+ .cta-secondary {
+ color: #4da6ff;
+ }
+ .cta-note {
+ color: #94a3b8;
+ }
+}
+
/* Global HTML and body styling */
html {
background-color: #F8FAFC;
diff --git a/hugo-site/themes/freenet/layouts/partials/head.html b/hugo-site/themes/freenet/layouts/partials/head.html
index 9331c04f..0ec6fcee 100644
--- a/hugo-site/themes/freenet/layouts/partials/head.html
+++ b/hugo-site/themes/freenet/layouts/partials/head.html
@@ -3,6 +3,14 @@
+{{/* Warm the connection to the Ghost Key API (DNS + TCP + TLS) on the pages
+ that call it, so the first request (e.g. minting a River invite on /try/)
+ doesn't pay connection setup on click. Same top-level site as the fetch, so
+ the warmed connection is actually reused. Prod only; dev API is localhost.
+ crossorigin = anonymous, matching the CORS fetch. */}}
+{{ if and (not hugo.IsServer) (in (slice "/try/" "/quickstart/" "/donate/") .RelPermalink) }}
+
+{{ end }}
{{ if .IsHome }}{{ site.Title }}{{ else }}{{ printf "%s | %s" .Title site.Title }}{{ end }}
{{ with .OutputFormats.Get "RSS" }}
{{ printf `` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
diff --git a/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html b/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html
index 498817fd..bc0ff6e3 100644
--- a/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html
+++ b/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html
@@ -117,9 +117,9 @@
-
Get your invite to {{ .Get "room" | default "Freenet Chat" }}
-
@@ -140,7 +140,11 @@
Run: riverctl invite accept <code>
+ {{ if eq (.Get "hosted") "true" }}
+
This is a hosted demo running on our server, no install needed. River may take a moment to load the first time.
+ {{ else }}
Make sure Freenet is running. Please be patient, River may take a while to load when your peer first starts.
+ {{ end }}
@@ -149,7 +153,7 @@
-
Generating invite...
+
Joining the chat...
@@ -160,9 +164,17 @@
// Windows: the node binds its API on the IPv6 loopback by default, and
// Windows resolves "localhost" to ::1 (reachable) while the literal IPv4
// address is refused. localhost is reachable on every platform.
- const riverBaseUrl = 'http://localhost:7509/v1/contract/web/raAqMhMG7KUpXBU2SxgCQ3Vh4PYjttxdSWd9ftV7RLv/';
+ // Default base is the user's LOCAL peer (post-install, quickstart flow); the
+ // try page passes base="https://try.freenet.org/.../" for the no-install demo.
+ // Read from the button's data attribute (HTML-attr escaping is well-defined;
+ // avoids the JS-context double-escaping Hugo does inside