Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion hugo-site/content/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<a href="/quickstart/" class="cta-button">Try Freenet</a>
<a href="/quickstart/" class="cta-button">Install Freenet</a>

<div class="cta-secondary-wrap">
<a href="/try/" class="cta-secondary">Or try it in your browser first →</a>
<span class="cta-note">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.</span>
</div>

<div class="action-and-news">

Expand Down
2 changes: 1 addition & 1 deletion hugo-site/content/quickstart/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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" >}}

Expand Down
25 changes: 25 additions & 0 deletions hugo-site/content/try/_index.md
Original file line number Diff line number Diff line change
@@ -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/)
48 changes: 47 additions & 1 deletion hugo-site/themes/freenet/assets/css/freenet.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions hugo-site/themes/freenet/layouts/partials/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=Source+Sans+3:wght@400;500;600&display=swap" rel="stylesheet">
{{/* 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) }}
<link rel="preconnect" href="https://gkapi.freenet.org" crossorigin>
{{ end }}
<title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ printf "%s | %s" .Title site.Title }}{{ end }}</title>
{{ with .OutputFormats.Get "RSS" }}
{{ printf `<link rel=%q type=%q href=%q title=%q>` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@

<div class="invite-container">
<div id="invite-form">
<p>Get your invite to <strong>{{ .Get "room" | default "Freenet Chat" }}</strong></p>
<button id="get-invite-btn" class="invite-btn invite-btn-primary">
Get Invite Code
<p>Join <strong>{{ .Get "room" | default "Freenet Chat" }}</strong></p>
<button id="get-invite-btn" class="invite-btn invite-btn-primary" data-river-base="{{ .Get "base" | default "http://localhost:7509/v1/contract/web/raAqMhMG7KUpXBU2SxgCQ3Vh4PYjttxdSWd9ftV7RLv/" }}" data-one-click="{{ if eq (.Get "hosted") "true" }}1{{ end }}">
{{ .Get "cta" | default "Join the Freenet chat room" }}
</button>
</div>

Expand All @@ -140,7 +140,11 @@
<p class="note" style="margin-top: 0.5rem;">Run: <code>riverctl invite accept &lt;code&gt;</code></p>
</details>

{{ if eq (.Get "hosted") "true" }}
<p class="note">This is a hosted demo running on our server, no install needed. River may take a moment to load the first time.</p>
{{ else }}
<p class="note">Make sure Freenet is running. Please be patient, River may take a while to load when your peer first starts.</p>
{{ end }}
</div>

<div id="invite-error" style="display: none;">
Expand All @@ -149,7 +153,7 @@
</div>

<div id="invite-loading" style="display: none;">
<p class="loading-text">Generating invite...</p>
<p class="loading-text">Joining the chat...</p>
</div>
</div>

Expand All @@ -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 <script>).
const riverBaseUrl = document.getElementById('get-invite-btn').getAttribute('data-river-base');

const getInviteBtn = document.getElementById('get-invite-btn');
// Hosted demo (/try/) joins in one click: mint, then navigate straight into
// River. Quickstart stays two-step (opens the LOCAL peer in a new tab and
// shows the raw code for riverctl users).
const oneClick = getInviteBtn.getAttribute('data-one-click') === '1';
const inviteForm = document.getElementById('invite-form');
const inviteResult = document.getElementById('invite-result');
const inviteError = document.getElementById('invite-error');
Expand Down Expand Up @@ -210,13 +222,17 @@

const data = await response.json();
const inviteCode = data.invite_code;
const riverUrl = `${riverBaseUrl}?invitation=${encodeURIComponent(inviteCode)}`;

// Set the invite link URL
inviteLink.href = `${riverBaseUrl}?invitation=${encodeURIComponent(inviteCode)}`;
if (oneClick) {
// Hosted demo: go straight into River, no second click.
window.location.href = riverUrl;
return;
}

// Show raw invite code for CLI users
// Two-step (quickstart): show the open link + the raw code for CLI users.
inviteLink.href = riverUrl;
document.getElementById('invite-code-text').textContent = inviteCode;

inviteLoading.style.display = 'none';
inviteResult.style.display = 'block';

Expand Down
Loading