Redesign home page

This commit is contained in:
2024-11-18 17:09:27 +01:00
parent e56fa4d4da
commit b5b5caa329
18 changed files with 299 additions and 179 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

BIN
res/static/img/map.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
res/static/img/map.xcf Normal file

Binary file not shown.

View File

@@ -190,15 +190,15 @@ pre>code {
.page_content { .page_content {
background: var(--body_background); background: var(--body_background);
border-radius: 8px; border-radius: 6px;
overflow: hidden; overflow: hidden;
} }
.page_content, .page_content,
.page_margins, .page_margins,
footer { footer {
margin-right: 16px; margin-right: 20px;
margin-left: 16px; margin-left: 20px;
} }
@media (max-width: 1100px) { @media (max-width: 1100px) {

View File

@@ -3,9 +3,8 @@
menu menu
</button> </button>
<nav id="page_navigation" class="page_navigation"> <nav id="page_navigation" class="page_navigation">
<a href="/#">Home</a> <a href="/home#">Home</a>
<a href="/#pro">Subscriptions</a> <a href="/home#pro">Get Premium</a>
<a href="/#prepaid">For creators</a>
<hr /> <hr />
{{if .Authenticated}} {{if .Authenticated}}
<a href="/user">{{.User.Username}}</a> <a href="/user">{{.User.Username}}</a>
@@ -21,7 +20,6 @@
{{else}} {{else}}
<a href="/login">Login</a> <a href="/login">Login</a>
<a href="/register">Register</a> <a href="/register">Register</a>
<a href="/history">Upload History</a>
{{end}} {{end}}
<hr /> <hr />
<a href="/about">Questions & Answers</a> <a href="/about">Questions & Answers</a>

View File

@@ -414,8 +414,8 @@ onMount(() => {
width: 100%; width: 100%;
max-width: 1000px; max-width: 1000px;
margin: auto; margin: auto;
padding-top: 4px; padding-top: 2px;
padding-bottom: 4px; padding-bottom: 2px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }

View File

@@ -220,8 +220,8 @@ const window_keydown = (e) => {
margin: auto; margin: auto;
width: 1000px; width: 1000px;
max-width: 100%; max-width: 100%;
padding-top: 4px; padding-top: 2px;
padding-bottom: 4px; padding-bottom: 2px;
border-bottom: 1px solid var(--separator); border-bottom: 1px solid var(--separator);
} }
@@ -230,7 +230,10 @@ const window_keydown = (e) => {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
} }
.term { .search_form > * {
flex: 0 0 auto;
}
.search_form > .term {
flex: 1 1 auto; flex: 1 1 auto;
} }

View File

@@ -22,7 +22,7 @@ export let nav
</button> </button>
</IconBlock> </IconBlock>
{#if $nav.base.path === "/me/.search_index.gz"} {#if $nav.base.name === ".search_index.gz"}
<TextBlock> <TextBlock>
<p> <p>
Congratulations! You have found the search index. One of the Congratulations! You have found the search index. One of the

View File

@@ -14,16 +14,12 @@ onMount(() => {
}) })
</script> </script>
<header id="pro">
<h1>Getting more out of pixeldrain</h1>
</header>
<section> <section>
<p> <p>
By purchasing a subscription you support pixeldrain on its mission Pixeldrain features two different payment modes. We offer a monthly
to make content sharing easier, safer and faster for everyone. If subscription which is managed by Patreon, and a prepaid service which
you're on mobile the feature table might be easier to read supports a dozen different payment providers. For low usage Prepaid is
horizontally. usually better as there's no monthly fee.
</p> </p>
</section> </section>
@@ -101,7 +97,7 @@ onMount(() => {
</div> </div>
<div class="feature_cell prepaid_feat"> <div class="feature_cell prepaid_feat">
<span class="bold">€4 / TB / month</span><br/> <span class="bold">€4 / TB / month</span><br/>
There is no limit. You only pay for what you use No limit, you only pay for what you use
</div> </div>
<div class="left_col"> <div class="left_col">
@@ -111,16 +107,15 @@ onMount(() => {
</button> </button>
</div> </div>
<div class="feature_cell free_feat"> <div class="feature_cell free_feat">
<span class="bold">120 days</span> (4 months)<br/> <span class="bold">120 days</span> (4 months)
After last download or view
</div> </div>
<div class="feature_cell pro_feat"> <div class="feature_cell pro_feat">
<span class="bold">240 days</span> (8 months)<br/> <span class="bold">240 days</span> (8 months)<br/>
After last download or view. Plans without expiry are available on Patreon Plans without expiry are available
</div> </div>
<div class="feature_cell prepaid_feat"> <div class="feature_cell prepaid_feat">
<span class="bold">Never</span><br/> <span class="bold">Files do not expire</span><br/>
While subscription is active While prepaid plan is active
</div> </div>
<div class="left_col"> <div class="left_col">
@@ -209,15 +204,8 @@ onMount(() => {
</div> </div>
<section> <section>
<br/> <h2>Other plans available on Patreon</h2>
<br/>
<br/>
<div style="text-align: center;">
These plans can also be subscribed to through Patreon:
</div>
<br/>
<OtherPlans/> <OtherPlans/>
<br/>
</section> </section>
<Modal bind:this={file_expiry} title="File Expiry Postponing" padding> <Modal bind:this={file_expiry} title="File Expiry Postponing" padding>
@@ -316,21 +304,6 @@ onMount(() => {
</Modal> </Modal>
<style> <style>
header {
background-image: url("/res/img/inflating_star.webp");
background-repeat: no-repeat;
background-attachment: fixed;
background-position: center;
background-size: cover;
background-blend-mode: normal;
}
header > h1 {
margin-top: 60px;
margin-bottom: 60px;
color: #ffffff;
text-shadow: 0 0 3px #000000;
}
.bold { .bold {
font-weight: bold; font-weight: bold;
} }

View File

@@ -0,0 +1,41 @@
<script>
import LoginRegister from "../login/LoginRegister.svelte";
import MollieDeposit from "../user_home/MollieDeposit.svelte";
import Euro from "../util/Euro.svelte";
</script>
{#if window.user.username !== ""}
<div class="page_content">
<section>
{#if window.user.subscription.type === "patreon"}
<p>
You already have a Patreon subscription active. You cannot use
Prepaid while that subscription is active.
</p>
{:else if window.user.subscription.type === "prepaid"}
<p>
You already have a Prepaid subscription active. Your account
balance is <Euro amount={window.user.balance_micro_eur}/>. Use
the form below to top up your balance.
</p>
<MollieDeposit/>
{:else}
<p>
You are currently logged in as '{window.user.username}'. Use the
form below to activate prepaid on this account.
</p>
<MollieDeposit/>
{/if}
</section>
</div>
{:else}
<LoginRegister/>
{/if}
<style>
.page_content {
margin-top: 16px;
margin-bottom: 16px;
}
</style>

View File

@@ -1,67 +1,163 @@
<script> <script>
import Menu from "../filesystem/Menu.svelte"; import Menu from "../filesystem/Menu.svelte";
import Footer from "../layout/Footer.svelte"; import Footer from "../layout/Footer.svelte";
import { drop_target } from "../lib/DropTarget";
import AddressReputation from "./AddressReputation.svelte"; import AddressReputation from "./AddressReputation.svelte";
import FeatureTable from "./FeatureTable.svelte"; import FeatureTable from "./FeatureTable.svelte";
import ForCreators from "./ForCreators.svelte"; import GetStarted from "./GetStarted.svelte";
import UploadLoginWall from "./UploadLoginWall.svelte"; import UploadWidget from "./UploadWidget.svelte";
let upload_widget
</script> </script>
<header class="logo_header">
<div class="menu_button_container">
<Menu no_login_label="Not logged in" hide_name={false} hide_logo style="border-radius: 0 0 0 8px; margin: 0"/>
</div>
<div class="header_image_container"></div>
</header>
<AddressReputation/>
<div class="page_content"> <div class="page_content">
<header class="header" style="text-align: initial"> {#if window.user && window.user.username && window.user.username !== ""}
<div class="menu_button_container"> <div
<Menu no_login_label="Not logged in" hide_name={false} hide_logo style="border-radius: 0 0 0 8px; margin: 0"/> class="drop_target"
use:drop_target={{
upload: (files) => upload_widget.upload_files(files),
shadow: "var(--highlight_color) 0 0 10px 2px inset",
}}
>
<UploadWidget bind:this={upload_widget}/>
</div> </div>
<div class="header_image_container"></div> {:else}
</header> <section>
<p>
<AddressReputation/> Pixeldrain offers services for efficiently moving and storing
digital files on the internet.
<UploadLoginWall/> </p>
<h2>What pixeldrain is good at</h2>
<ul>
<li>
Serving large files to millions of people worldwide
</li>
<li>
Storing files for less money than all the competition
</li>
</ul>
<h2>Things we take very seriously</h2>
<ul>
<li>
<b>Performance</b> - Slow software is a waste of time. We
don't want to make you wait, so pixeldrain is completely
tuned for maximum performance
</li>
<li>
<b>Privacy</b> - There is too much tracking on the web
nowadays. Pixeldrain goes in the other direction, this site
does not contain any advertisements or third party tracking
scripts. We don't even require you to enter an e-mail
address to register an account
</li>
<li>
Bullet lists
</li>
</ul>
</section>
{/if}
</div> </div>
<header>
<h1>Our pricing!</h1>
<span>We're not afraid to say it</span>
</header>
<div class="page_content"> <div class="page_content">
<header>
<h1>What is pixeldrain?</h1>
</header>
<section> <section>
<p> <h2>Prepaid plan</h2>
Pixeldrain is a file hosting website built for speed and ease of <ul>
use. You can upload files you want to share online to our servers <li>
and we will hold on to them for at least four months. During this Storage<br/>
time anyone with the link will be able to download your files. <span class="bold">€ 4 per TB per month</span>
Pixeldrain is built to be as fast as possible, so you don't have to </li>
do any unnecessary waiting when downloading files. <li>
</p> Data egress (downloading and sharing files)<br/>
<h2>Privacy</h2> <span class="bold">€ 2 per TB</span>
<p> </li>
Privacy is an important value for pixeldrain. There is too much <li>
tracking on the web nowadays. Pixeldrain goes in the other No other charges!
direction, this site does not contain any advertisements or third </li>
party tracking scripts. We don't even require you to enter an e-mail </ul>
address to register an account. <h3>What you get</h3>
</p> <ul>
<p> <li>
Not running ads does mean that we miss out on a lot of revenue of <span class="bold">Unlimited</span> storage space
course. Running a site like this is a very expensive endeavour. </li>
Please consider supporting the project with one of the premium plans <li>
below. Cloud storage with <a href="/filesystem#toc_7">rclone</a> and <a
</p> href="/filesystem#toc_10">FTPS</a> support
</li>
<li>
<span class="bold">Customizable</span> download pages (custom
branding colours, header and background images)
</li>
<li>
<span class="bold">Fault tolerance</span> for up to four storage
server failures
</li>
<li>
Total bandwidth capacity of <span class="bold">900
Gigabits</span> per second
</li>
<li>
Nine high-performance caching servers optimized for data
transfer over long distances
</li>
<li>
File caching in
<span class="bold">Frankfurt</span>,
<span class="bold">Singapore</span> and
<span class="bold">Portland</span>
</li>
</ul>
<img class="image" src="/res/img/map.webp" alt="A globe showing datacenter locations"/>
<h3>What you don't get</h3>
<ul>
<li>
Data backups - Please keep a local copy of everything you store
in case of emergency
</li>
<li>
Commercial support - If you have a question, please check the <a
href="/about">Q&A page</a> first. If that doesn't answer it, try
the <a href="https://discord.gg/UDjaBGwr4p"
target="_blank">support channel</a> on Discord.
</li>
</ul>
</section> </section>
</div> </div>
<header>
<h1>Get started</h1>
</header>
<GetStarted/>
<header id="pro">
<h1>Premium plans</h1>
</header>
<div class="page_content"> <div class="page_content">
<FeatureTable/> <FeatureTable/>
</div> </div>
<div class="page_content"> <Footer nobg/>
<ForCreators/>
</div>
<Footer/>
<style> <style>
:global(.page_body) {
background-image: url("/res/img/inflating_star.webp");
background-repeat: no-repeat;
background-attachment: fixed;
background-position: center;
background-size: cover;
}
.page_content { .page_content {
margin-top: 16px; margin-top: 16px;
margin-bottom: 16px; margin-bottom: 16px;
@@ -73,18 +169,20 @@ import UploadLoginWall from "./UploadLoginWall.svelte";
} }
header { header {
background-image: url("/res/img/inflating_star.webp"); padding-top: 20px;
background-repeat: no-repeat; padding-bottom: 20px;
background-attachment: fixed;
background-position: center;
background-size: cover;
background-blend-mode: normal;
} }
header > h1 { .logo_header {
margin-top: 60px; text-align: initial;
margin-bottom: 60px; padding-top: 0;
padding-bottom: 0;
}
header > h1,
header > span {
color: #ffffff; color: #ffffff;
text-shadow: 0 0 3px #000000; text-shadow: 0 0 4px #000000;
margin-top: 30px;
margin-bottom: 30px;
} }
.header_image_container { .header_image_container {
@@ -103,4 +201,16 @@ header > h1 {
display: flex; display: flex;
justify-content: end; justify-content: end;
} }
.image {
max-width: 100%;
border-radius: 12px;
}
.drop_target {
border-radius: 8px;
}
.bold {
font-weight: bold;
color: var(--highlight_color);
text-shadow: 1px 1px 3px var(--shadow_color);
}
</style> </style>

View File

@@ -1,73 +0,0 @@
<script>
import Login from "../login/Login.svelte";
import Register from "../login/Register.svelte";
import UploadWidget from "./UploadWidget.svelte";
import { drop_target } from "../lib/DropTarget.ts"
const finish_login = async e => {
location.reload()
}
let upload_widget
let page = "login"
</script>
{#if window.user && window.user.username && window.user.username !== ""}
<div
class="drop_target"
use:drop_target={{
upload: (files) => upload_widget.upload_files(files),
shadow: "var(--highlight_color) 0 0 10px 2px inset",
}}
>
<UploadWidget bind:this={upload_widget}/>
</div>
{:else}
<section>
<p>
Pixeldrain requires an account to upload files to. If you already
have an account use the login form below to log in to your account.
If not, use the right tab button to register a new account.
</p>
</section>
<div class="tab_bar">
<button on:click={() => page = "login"} class:button_highlight={page === "login"}>
<i class="icon small">login</i>
<span>Login</span>
</button>
<button on:click={() => page = "register"} class:button_highlight={page === "register"}>
<i class="icon small">how_to_reg</i>
<span>Register</span>
</button>
</div>
{#if page === "login"}
<section class="highlight_border">
<Login on:login={finish_login}/>
<p>
If you have lost your password, you can <a
href="password_reset">request a new password here</a>.
</p>
</section>
{:else if page === "register"}
<section class="highlight_border">
<Register on:login={finish_login}/>
</section>
{/if}
<br/>
{/if}
<style>
.drop_target {
border-radius: 8px;
}
.tab_bar > button {
width: 40%;
max-width: 10em;
font-size: 1.3em;
justify-content: center;
}
</style>

View File

@@ -7,6 +7,7 @@ import Patreon from "../icons/Patreon.svelte";
import Reddit from "../icons/Reddit.svelte"; import Reddit from "../icons/Reddit.svelte";
import { formatDataVolumeBits } from "../util/Formatting.svelte"; import { formatDataVolumeBits } from "../util/Formatting.svelte";
export let nobg = false
let server_tx = 0 let server_tx = 0
let cache_tx = 0 let cache_tx = 0
let storage_tx = 0 let storage_tx = 0
@@ -26,7 +27,7 @@ onMount(async () => {
}) })
</script> </script>
<footer> <footer class:nobg>
<div class="footer_content"> <div class="footer_content">
<div style="display: inline-block; margin: 0 8px;"> <div style="display: inline-block; margin: 0 8px;">
Pixeldrain is a product by Pixeldrain is a product by
@@ -62,3 +63,11 @@ onMount(async () => {
</span> </span>
</div> </div>
</footer> </footer>
<style>
.nobg {
background-image: none;
background-color: unset;
box-shadow: none;
}
</style>

View File

@@ -0,0 +1,42 @@
<script>
import Login from "./Login.svelte";
import Register from "./Register.svelte";
const finish_login = async e => {
location.reload()
}
let page = "login"
</script>
<div class="tab_bar">
<button on:click={() => page = "login"} class:button_highlight={page === "login"}>
<i class="icon small">login</i>
<span>Login</span>
</button>
<button on:click={() => page = "register"} class:button_highlight={page === "register"}>
<i class="icon small">how_to_reg</i>
<span>Register</span>
</button>
</div>
<div class="page_content">
{#if page === "login"}
<Login on:login={finish_login}/>
<p>
If you have lost your password, you can <a
href="password_reset">request a new password here</a>.
</p>
{:else if page === "register"}
<Register on:login={finish_login}/>
{/if}
</div>
<style>
.tab_bar > button {
width: 40%;
max-width: 10em;
font-size: 1.3em;
justify-content: center;
}
</style>

View File

@@ -16,7 +16,7 @@ let form = {
name: "email", name: "email",
label: "E-mail address", label: "E-mail address",
type: "email", type: "email",
description: "Your e-mail address is only used for recovering lost passwords", description: "Your e-mail address is only used for recovering lost passwords and billing notifications",
}, { }, {
name: "password", name: "password",
label: "Password", label: "Password",

View File

@@ -87,4 +87,4 @@ let pages = [
] ]
</script> </script>
<TabMenu pages={pages} title="Welcome home, {window.user.username}!"/> <TabMenu pages={pages} title="Welcome, {window.user.username}!"/>

View File

@@ -132,7 +132,8 @@ func New(r *httprouter.Router, prefix string, conf Config) (wc *WebController) {
handler httprouter.Handle // The function to run when this API is called handler httprouter.Handle // The function to run when this API is called
}{ }{
// General navigation // General navigation
{GET, "" /* */, wc.serveTemplate("home", handlerOpts{})}, {GET, "" /* */, wc.serveLandingPage()},
{GET, "home" /* */, wc.serveTemplate("home", handlerOpts{})},
{GET, "api" /* */, wc.serveMarkdown("api.md", handlerOpts{})}, {GET, "api" /* */, wc.serveMarkdown("api.md", handlerOpts{})},
{GET, "history" /* */, wc.serveTemplate("upload_history", handlerOpts{})}, {GET, "history" /* */, wc.serveTemplate("upload_history", handlerOpts{})},
{GET, "u/:id" /* */, wc.serveFileViewer}, {GET, "u/:id" /* */, wc.serveFileViewer},
@@ -237,6 +238,22 @@ type handlerOpts struct {
NoExec bool NoExec bool
} }
func (wc *WebController) serveLandingPage() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var td = wc.newTemplateData(w, r)
var template = "home"
// If the user is logged in, run user home template
if td.Authenticated {
template = "user_home"
}
if err := wc.templates.Run(w, r, template, td); err != nil && !util.IsNetError(err) {
log.Error("Error executing template '%s': %s", template, err)
}
}
}
func (wc *WebController) serveTemplate(tpl string, opts handlerOpts) httprouter.Handle { func (wc *WebController) serveTemplate(tpl string, opts handlerOpts) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
if opts.NoEmbed { if opts.NoEmbed {