Merge branch 'feature/time_machine_page'
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 4.0 MiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 91 KiB |
|
@ -91,6 +91,41 @@ func SendPasswordReset(toAddress string, toName string, username string, resetTo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TimeMachineEmailData struct {
|
||||||
|
ProfileUrl string
|
||||||
|
Username string
|
||||||
|
UserEmail string
|
||||||
|
DiscordUsername string
|
||||||
|
MediaUrl string
|
||||||
|
DeviceInfo string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendTimeMachineEmail(profileUrl, username, userEmail, discordUsername, mediaUrl, deviceInfo, description string, perf *perf.RequestPerf) error {
|
||||||
|
perf.StartBlock("EMAIL", "Time machine email")
|
||||||
|
defer perf.EndBlock()
|
||||||
|
|
||||||
|
contents, err := renderTemplate("email_time_machine.html", TimeMachineEmailData{
|
||||||
|
ProfileUrl: profileUrl,
|
||||||
|
Username: username,
|
||||||
|
UserEmail: userEmail,
|
||||||
|
DiscordUsername: discordUsername,
|
||||||
|
MediaUrl: mediaUrl,
|
||||||
|
DeviceInfo: deviceInfo,
|
||||||
|
Description: description,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sendMail("team@handmade.network", "HMN Team", "[time machine] New submission", contents)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "Failed to send email")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var EmailRegex = regexp.MustCompile(`^[^:\p{Cc} ]+@[^:\p{Cc} ]+\.[^:\p{Cc} ]+$`)
|
var EmailRegex = regexp.MustCompile(`^[^:\p{Cc} ]+@[^:\p{Cc} ]+\.[^:\p{Cc} ]+$`)
|
||||||
|
|
||||||
func IsEmail(address string) bool {
|
func IsEmail(address string) bool {
|
||||||
|
|
|
@ -105,6 +105,27 @@ func BuildJamFeed2022() string {
|
||||||
return Url("/jam/2022/feed", nil)
|
return Url("/jam/2022/feed", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var RegexTimeMachine = regexp.MustCompile("^/timemachine$")
|
||||||
|
|
||||||
|
func BuildTimeMachine() string {
|
||||||
|
defer CatchPanic()
|
||||||
|
return Url("/timemachine", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var RegexTimeMachineForm = regexp.MustCompile("^/timemachine/submit$")
|
||||||
|
|
||||||
|
func BuildTimeMachineForm() string {
|
||||||
|
defer CatchPanic()
|
||||||
|
return Url("/timemachine/submit", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var RegexTimeMachineFormDone = regexp.MustCompile("^/timemachine/thanks$")
|
||||||
|
|
||||||
|
func BuildTimeMachineFormDone() string {
|
||||||
|
defer CatchPanic()
|
||||||
|
return Url("/timemachine/thanks", nil)
|
||||||
|
}
|
||||||
|
|
||||||
// QUESTION(ben): Can we change these routes?
|
// QUESTION(ben): Can we change these routes?
|
||||||
|
|
||||||
var RegexLoginAction = regexp.MustCompile("^/login$")
|
var RegexLoginAction = regexp.MustCompile("^/login$")
|
||||||
|
|
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |
Before Width: | Height: | Size: 549 B After Width: | Height: | Size: 549 B |
Before Width: | Height: | Size: 910 B After Width: | Height: | Size: 910 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 614 B After Width: | Height: | Size: 614 B |
Before Width: | Height: | Size: 776 B After Width: | Height: | Size: 776 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 922 B After Width: | Height: | Size: 922 B |
After Width: | Height: | Size: 86 B |
After Width: | Height: | Size: 86 B |
After Width: | Height: | Size: 70 B |
After Width: | Height: | Size: 57 B |
After Width: | Height: | Size: 88 B |
After Width: | Height: | Size: 641 B |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 185 B |
After Width: | Height: | Size: 529 B |
After Width: | Height: | Size: 470 B |
After Width: | Height: | Size: 513 B |
After Width: | Height: | Size: 455 B |
After Width: | Height: | Size: 825 B |
After Width: | Height: | Size: 538 B |
After Width: | Height: | Size: 581 B |
Before Width: | Height: | Size: 894 B After Width: | Height: | Size: 894 B |
|
@ -0,0 +1,6 @@
|
||||||
|
<p>Submission by <b><a href="{{ .ProfileUrl }}">{{ .Username }}</a></b> ({{ .UserEmail }}) {{ if .DiscordUsername }} On Discord: {{ .DiscordUsername }} {{ end }}</p>
|
||||||
|
<p>Submitted url: <a href="{{ .MediaUrl }}">{{ .MediaUrl }}</a></p>
|
||||||
|
Device info:<br />
|
||||||
|
<pre>{{ .DeviceInfo }}</pre>
|
||||||
|
Description:<br />
|
||||||
|
<pre>{{ .Description }}</pre>
|
|
@ -1,6 +1,6 @@
|
||||||
<footer class="pa3 pa4-l tc">
|
<footer class="pa3 pa4-l tc">
|
||||||
<h2>
|
<h2>
|
||||||
Community by <a href="{{ .Footer.HomepageUrl }}">handmade.network</a>
|
<a href="{{ .Footer.HomepageUrl }}">Handmade Network</a>
|
||||||
</h2>
|
</h2>
|
||||||
<ul class="list">
|
<ul class="list">
|
||||||
{{ $footerClasses := "ma0 pa0 dib-ns" }}
|
{{ $footerClasses := "ma0 pa0 dib-ns" }}
|
||||||
|
|
|
@ -111,6 +111,7 @@
|
||||||
</div>
|
</div>
|
||||||
*/}}
|
*/}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
<div class="mb3 ph3 ph0-ns">
|
<div class="mb3 ph3 ph0-ns">
|
||||||
<style>
|
<style>
|
||||||
#jam-banner {
|
#jam-banner {
|
||||||
|
@ -177,6 +178,7 @@
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
*/}}
|
||||||
|
|
||||||
<div class="mb3 ph3 ph0-ns">
|
<div class="mb3 ph3 ph0-ns">
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
{{/*
|
||||||
|
This is a copy-paste from base.html because we want to preserve the unique
|
||||||
|
style of landing pages if we change base.html in the future.
|
||||||
|
*/}}
|
||||||
|
<!DOCTYPE html{{ if .OpenGraphItems }} prefix="og: http://ogp.me/ns#"{{ end }}>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ static "visjam2023/favicon-16x16.png" }}">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ static "visjam2023/favicon-32x32.png" }}">
|
||||||
|
|
||||||
|
{{ if .CanonicalLink }}<link rel="canonical" href="{{ .CanonicalLink }}">{{ end }}
|
||||||
|
{{ range .OpenGraphItems }}
|
||||||
|
{{ if .Property }}
|
||||||
|
<meta property="{{ .Property }}" content="{{ .Value }}" />
|
||||||
|
{{ else }}
|
||||||
|
<meta name="{{ .Name }}" content="{{ .Value }}" />
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Title }}
|
||||||
|
<title>{{ .Title }} | Handmade Network</title>
|
||||||
|
{{ else }}
|
||||||
|
<title>Handmade Network</title>
|
||||||
|
{{ end }}
|
||||||
|
<meta name="theme-color" content="#346ba6">
|
||||||
|
|
||||||
|
<script src="{{ static "js/templates.js" }}"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ static "fonts/mohave/stylesheet.css" }}">
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Fira+Sans:300,400,500,600' rel='stylesheet' type='text/css'>
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Fira+Mono:300,400,500,700' rel='stylesheet' type='text/css'>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ static "style.css" }}">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="left white">
|
||||||
|
<div class="mt4-ns mw8 margin-center ph3-m ph4-l">
|
||||||
|
{{ template "header.html" . }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="jam-sections">
|
||||||
|
{{ block "content" . }}{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mw8 margin-center ph3-m ph4-l">
|
||||||
|
{{ template "footer.html" . }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,199 @@
|
||||||
|
{{/*
|
||||||
|
This is a copy-paste from base.html because we want to preserve the unique
|
||||||
|
style of landing pages if we change base.html in the future.
|
||||||
|
*/}}
|
||||||
|
<!DOCTYPE html{{ if .OpenGraphItems }} prefix="og: http://ogp.me/ns#"{{ end }}>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ static "visjam2023/favicon-16x16.png" }}">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ static "visjam2023/favicon-32x32.png" }}">
|
||||||
|
|
||||||
|
{{ if .CanonicalLink }}<link rel="canonical" href="{{ .CanonicalLink }}">{{ end }}
|
||||||
|
{{ range .OpenGraphItems }}
|
||||||
|
{{ if .Property }}
|
||||||
|
<meta property="{{ .Property }}" content="{{ .Value }}" />
|
||||||
|
{{ else }}
|
||||||
|
<meta name="{{ .Name }}" content="{{ .Value }}" />
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Title }}
|
||||||
|
<title>{{ .Title }} | Handmade Network</title>
|
||||||
|
{{ else }}
|
||||||
|
<title>Handmade Network</title>
|
||||||
|
{{ end }}
|
||||||
|
<meta name="theme-color" content="#346ba6">
|
||||||
|
|
||||||
|
<script src="{{ static "js/templates.js" }}"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ static "fonts/mohave/stylesheet.css" }}">
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Fira+Sans:300,400,500,600' rel='stylesheet' type='text/css'>
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Fira+Mono:300,400,500,700' rel='stylesheet' type='text/css'>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ static "style.css" }}">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
/*
|
||||||
|
This is too light for accessiblity imo. I'll darken them a bit and see how Ben feels
|
||||||
|
about it. -- Jake 5/26/2023
|
||||||
|
background: linear-gradient(15deg, #b4c4fe, #eb8cbf ); */
|
||||||
|
background: linear-gradient(15deg, #7391fd, #e25ca4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-options,
|
||||||
|
header form,
|
||||||
|
header .menu-bar .wiki,
|
||||||
|
header .menu-bar .library {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom-color: white;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hmn-logo {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a,
|
||||||
|
footer a {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .submenu {
|
||||||
|
background-color: #d661ad;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo {
|
||||||
|
width: 16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
font-family: "MohaveHMN", sans-serif;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* '95-esque styles */
|
||||||
|
.frame {
|
||||||
|
width: 100%;
|
||||||
|
background: #c3c3c3;
|
||||||
|
border-width: 2px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #fff #333 #333 #fff;
|
||||||
|
border-image: url('{{ dataimg "timemachine/win95-border.gif" }}') 4;
|
||||||
|
padding: 1px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame ol,
|
||||||
|
.frame ul {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame ul {
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame .title {
|
||||||
|
padding: 0 0.1875rem; /* 0 3px */
|
||||||
|
height: 1.125rem; /* 18px */
|
||||||
|
line-height: 1.125rem; /* 18px */
|
||||||
|
font-size: 0.8125rem; /* 13px */
|
||||||
|
width: 100%;
|
||||||
|
background: rgb(0, 0, 130);
|
||||||
|
color: #fff;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame .frame-close {
|
||||||
|
width: 1rem; /* 16px */
|
||||||
|
height: 0.875rem; /* 14px */
|
||||||
|
position: absolute;
|
||||||
|
right: 0.125rem; /* 2px */
|
||||||
|
top: 0.125rem; /* 2px */
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame * {
|
||||||
|
font-family: "MS Sans Serif", "Microsoft Sans Serif", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inset {
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #828282 #ffffff #ffffff #828282;
|
||||||
|
border-style: solid;
|
||||||
|
border-image: url('{{ dataimg "timemachine/win95-border-inset.gif" }}') 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.pixelated {
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-shadow {
|
||||||
|
box-shadow: 2px 2px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win95-btn {
|
||||||
|
min-width: 5.6rem;
|
||||||
|
height: 1.4375rem; /* 23px */
|
||||||
|
border-width: 2px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #fff #333 #333 #fff;
|
||||||
|
border-image: url('{{ dataimg "timemachine/win95-border-btn.gif" }}') 4;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: normal;
|
||||||
|
padding: 0 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win95-btn:active {
|
||||||
|
border-image: url('{{ dataimg "timemachine/win95-border-btn-pressed.gif" }}') 4;
|
||||||
|
padding-top: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win95-input {
|
||||||
|
border-width: 2px;
|
||||||
|
border-style: solid;
|
||||||
|
border-image: url('{{ dataimg "timemachine/win95-border-input.gif" }}') 4;
|
||||||
|
outline: none;
|
||||||
|
background-color: white !important;
|
||||||
|
resize: vertical; /* only applies to textareas so whatever */
|
||||||
|
}
|
||||||
|
|
||||||
|
.less-spacing p {
|
||||||
|
margin: 0.2rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="left white">
|
||||||
|
<div class="mt4-ns mw7 margin-center ph3-m ph4-l">
|
||||||
|
{{ template "header.html" . }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="jam-sections">
|
||||||
|
{{ block "content" . }}{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mw8 margin-center ph3-m ph4-l">
|
||||||
|
{{ template "footer.html" . }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,158 @@
|
||||||
|
{{ template "timemachine_base.html" . }}
|
||||||
|
|
||||||
|
{{ define "frame title" }}
|
||||||
|
<div class="title">
|
||||||
|
{{ . }}
|
||||||
|
<img class="frame-close" src="{{ dataimg "timemachine/win95-close.gif" }}">
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "content" }}
|
||||||
|
|
||||||
|
<div class="center-layout content mw7 ph3 flex flex-column g3">
|
||||||
|
<div>
|
||||||
|
<div class="mv5 flex justify-center">
|
||||||
|
<img
|
||||||
|
class="pixelated drop-shadow"
|
||||||
|
src="{{ static "timemachine/splash.gif" }}"
|
||||||
|
alt="Time Machine"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="frame mv4">
|
||||||
|
{{ template "frame title" "Overview" }}
|
||||||
|
<div class="post-content pa3 flex g3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="pixelated" src="{{ dataimg "timemachine/win95-computer.png" }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
This summer, we're digging out old devices and seeing what they were actually like to use.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Our phones today are far more powerful than our desktop computers from decades ago. And yet, those old computers worked just fine! The Time Machine project is an opportunity to boot up those old devices and see how they feel to use.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
As our hardware has improved, how has the user experience changed? Which parts have stayed the same? Which parts have gotten worse? There's only one way to find out.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="frame mv4">
|
||||||
|
{{ template "frame title" "How To Participate" }}
|
||||||
|
<div class="post-content pa3">
|
||||||
|
<div class="flex flex-column g3">
|
||||||
|
<div class="flex g3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="pixelated" src="{{ dataimg "timemachine/win95-camera.png" }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<strong>Take video of yourself using an older device.</strong> Any personal device from any time period is great. We're particularly interested in the history of day-to-day devices like personal computers and mobile phones.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex g3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="pixelated" src="{{ dataimg "timemachine/win95-upload.png" }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<strong>Upload the video somewhere.</strong> Upload to Google Drive, Dropbox, or even YouTube - anywhere we can download it from.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex g3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="pixelated" src="{{ dataimg "timemachine/win95-pad.png" }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<strong>Send us the video link + extra info.</strong> Tell us about this device, when you got it, what specs it has, and anything else you think is interesting. We'll get the video posted here on the website for everyone to see.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
At the end of the summer, we'll compile all the submissions into a final report.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end g3">
|
||||||
|
<a class="win95-btn" href="{{ .SubmitUrl }}"><u>S</u>ubmit your own</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mv5 flex justify-center">
|
||||||
|
<div class="frame mw6">
|
||||||
|
{{ template "frame title" "New Videos" }}
|
||||||
|
<div class="pa3 flex g3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="pixelated" src="{{ dataimg "timemachine/win95-info.png" }}">
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-column flex-grow-1 g3">
|
||||||
|
<div>
|
||||||
|
1 video has been submitted! Would you like to see it?
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end g3">
|
||||||
|
<div class="win95-btn"><u>Y</u>es</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="frame mv4">
|
||||||
|
{{ template "frame title" "2009 iPod Touch" }}
|
||||||
|
<div>
|
||||||
|
<div class="pa3 flex flex-column g3">
|
||||||
|
<div class="flex flex-column flex-row-ns g3">
|
||||||
|
<div class="flex-shrink-0 relative">
|
||||||
|
<img
|
||||||
|
class="pixelated inset"
|
||||||
|
src="{{ static "timemachine/ipodtouch-dither.gif" }}"
|
||||||
|
alt="Video Thumbnail"
|
||||||
|
/>
|
||||||
|
<a href="https://youtu.be/2eBFk1yV6mE" target="_blank">
|
||||||
|
<div class="absolute absolute--fill bg-black-30 flex justify-center items-center">
|
||||||
|
<img class="pixelated" alt="Play Video" src="{{ dataimg "timemachine/win95-play.png" }}">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-column g1">
|
||||||
|
<div><strong>Device:</strong> iPod Touch 3rd gen, model MC008LL</div>
|
||||||
|
<div><strong>Submitted by:</strong> Ben Visness</div>
|
||||||
|
<div><strong>Release year:</strong> 2009</div>
|
||||||
|
<div><strong>Processor:</strong> 600MHz Samsung S5L8922, single-core</div>
|
||||||
|
<div><strong>Memory:</strong> 256MB LPDDR2 @ 200 MHz</div>
|
||||||
|
<div><strong>Operating system:</strong> iOS 5</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="post-content">
|
||||||
|
<p>
|
||||||
|
This is the iPod Touch I got when I was 13. It was my first major
|
||||||
|
tech purchase and an early device in the iOS lineup. When I
|
||||||
|
purchased this I think it was running iOS 3; at this point it has
|
||||||
|
iOS 5. I was pleased to see that the battery still holds a charge
|
||||||
|
quite well, and it consistently runs at about 30 to 60 frames per
|
||||||
|
second.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In the video you can see several built-in apps. Media playback
|
||||||
|
still works great, and scrubbing around in songs is instantaneous.
|
||||||
|
App switching works well. The calculator launches instantly (as
|
||||||
|
you would hope). I was shocked to see that the old Google Maps app
|
||||||
|
still works - apparently they have kept their old tile-based map
|
||||||
|
servers online. It even gave me public transit directions.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Overall, I would say this device feels only a hair slower than my
|
||||||
|
current iPhone.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
|
@ -0,0 +1,106 @@
|
||||||
|
{{ template "timemachine_base.html" . }}
|
||||||
|
|
||||||
|
{{ define "frame title" }}
|
||||||
|
<div class="title">
|
||||||
|
{{ . }}
|
||||||
|
<img class="frame-close" src="{{ dataimg "timemachine/win95-close.gif" }}">
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="center-layout content mw7 ph3 flex flex-column g3">
|
||||||
|
<div class="frame mv3 mv4-ns mw6 margin-center">
|
||||||
|
{{ template "frame title" "Submit A Video" }}
|
||||||
|
<form class="post-content" action="" method="POST">
|
||||||
|
{{ csrftoken .Session }}
|
||||||
|
<div class="post-content pa3">
|
||||||
|
<div class="flex flex-column g3">
|
||||||
|
<div class="flex g3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="pixelated" src="{{ dataimg "timemachine/win95-media.png" }}">
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-column flex-grow-1 g1">
|
||||||
|
<strong>Video URL</strong>
|
||||||
|
<input
|
||||||
|
class="win95-input"
|
||||||
|
type="url"
|
||||||
|
name="media_url"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<div class="f7 less-spacing">
|
||||||
|
<p>
|
||||||
|
Take video of yourself using an old device. The video should:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Show the entire device so viewers have necessary context</li>
|
||||||
|
<li>Clearly show the contents of the device's screen</li>
|
||||||
|
<li>Have minimal editing, no commentary, and clear sound</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Upload the video somewhere publicly accessible, then share the link here. We can download videos from file-sharing services or any video service supported by youtube-dl.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex g3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="pixelated" src="{{ dataimg "timemachine/win95-checklist.png" }}">
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-column flex-grow-1 g1">
|
||||||
|
<strong>Device Info</strong>
|
||||||
|
<textarea class="win95-input h4" name="device_info" required></textarea>
|
||||||
|
<div class="f7 less-spacing">
|
||||||
|
<p>
|
||||||
|
Include specific info about the device such as its model number, release year, CPU speed, amount of memory, etc.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex g3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="pixelated" src="{{ dataimg "timemachine/win95-scroll.png" }}">
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-column flex-grow-1 g1">
|
||||||
|
<strong>Description</strong>
|
||||||
|
<textarea class="win95-input h4" name="description" required></textarea>
|
||||||
|
<div class="f7 less-spacing">
|
||||||
|
<p>
|
||||||
|
What's the story behind this device? When did you get it? What did you use it for? Is it in good shape? Is there anything interesting or notable about it?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt3 flex justify-end g3">
|
||||||
|
<button class="win95-btn" type="submit" value="Submit"><u>S</u>ubmit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let form = document.querySelector("form");
|
||||||
|
let mediaUrl = document.querySelector("[name=media_url]");
|
||||||
|
let deviceInfo = document.querySelector("[name=device_info]");
|
||||||
|
let description = document.querySelector("[name=description]");
|
||||||
|
let submitBtn = document.querySelector("[type=submit]");
|
||||||
|
|
||||||
|
function saveData() {
|
||||||
|
localStorage.setItem("tm_media_url", mediaUrl.value);
|
||||||
|
localStorage.setItem("tm_device_info", deviceInfo.value);
|
||||||
|
localStorage.setItem("tm_description", description.value);
|
||||||
|
}
|
||||||
|
form.addEventListener("submit", function() {
|
||||||
|
saveData();
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("visibilitychange", function() {
|
||||||
|
saveData();
|
||||||
|
});
|
||||||
|
|
||||||
|
mediaUrl.value = localStorage.getItem("tm_media_url") ?? "";
|
||||||
|
deviceInfo.value = localStorage.getItem("tm_device_info") ?? "";
|
||||||
|
description.value = localStorage.getItem("tm_description") ?? "";
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
|
@ -0,0 +1,36 @@
|
||||||
|
{{ template "timemachine_base.html" . }}
|
||||||
|
|
||||||
|
{{ define "frame title" }}
|
||||||
|
<div class="title">
|
||||||
|
{{ . }}
|
||||||
|
<img class="frame-close" src="{{ dataimg "timemachine/win95-close.gif" }}">
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="center-layout content mw7 ph3 flex flex-column g3">
|
||||||
|
<div class="mv5 flex justify-center">
|
||||||
|
<div class="frame mw6">
|
||||||
|
{{ template "frame title" "Submission Complete" }}
|
||||||
|
<div class="pa3 flex g3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="pixelated" src="{{ dataimg "timemachine/win95-check.png" }}">
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-column flex-grow-1 g3">
|
||||||
|
<div>
|
||||||
|
Thank you for your submission! We will review it and get it published on the site soon.
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end g3">
|
||||||
|
<a class="win95-btn" href="{{ .TimeMachineUrl }}"><u>O</u>K</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
localStorage.removeItem("tm_media_url");
|
||||||
|
localStorage.removeItem("tm_device_info");
|
||||||
|
localStorage.removeItem("tm_description");
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
|
@ -2,9 +2,12 @@ package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -105,8 +108,24 @@ func names(ts []*template.Template) []string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed svg/*
|
//go:embed img/*
|
||||||
var SVGs embed.FS
|
var Imgs embed.FS
|
||||||
|
|
||||||
|
func GetImg(filepath string) []byte {
|
||||||
|
var imgs fs.FS
|
||||||
|
if config.Config.DevConfig.LiveTemplates {
|
||||||
|
imgs = utils.DirFS("src/templates/img")
|
||||||
|
} else {
|
||||||
|
imgs = Imgs
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := imgs.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.Must1(io.ReadAll(img))
|
||||||
|
}
|
||||||
|
|
||||||
var controlCharRegex = regexp.MustCompile(`\p{Cc}`)
|
var controlCharRegex = regexp.MustCompile(`\p{Cc}`)
|
||||||
|
|
||||||
|
@ -200,7 +219,7 @@ var HMNTemplateFuncs = template.FuncMap{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"svg": func(name string) template.HTML {
|
"svg": func(name string) template.HTML {
|
||||||
contents, err := SVGs.ReadFile(fmt.Sprintf("svg/%s.svg", name))
|
contents, err := Imgs.ReadFile(fmt.Sprintf("img/%s.svg", name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("SVG not found: " + name)
|
panic("SVG not found: " + name)
|
||||||
}
|
}
|
||||||
|
@ -218,6 +237,12 @@ var HMNTemplateFuncs = template.FuncMap{
|
||||||
"staticthemenobust": func(theme string, filepath string) string {
|
"staticthemenobust": func(theme string, filepath string) string {
|
||||||
return hmnurl.BuildTheme(filepath, theme, false)
|
return hmnurl.BuildTheme(filepath, theme, false)
|
||||||
},
|
},
|
||||||
|
"dataimg": func(filepath string) template.URL {
|
||||||
|
contents := GetImg(filepath)
|
||||||
|
mime := http.DetectContentType(contents)
|
||||||
|
contentsBase64 := base64.StdEncoding.EncodeToString(contents)
|
||||||
|
return template.URL(fmt.Sprintf("data:%s;base64,%s", mime, contentsBase64))
|
||||||
|
},
|
||||||
"string2uuid": func(s string) string {
|
"string2uuid": func(s string) string {
|
||||||
return uuid.NewSHA1(uuid.NameSpaceURL, []byte(s)).URN()
|
return uuid.NewSHA1(uuid.NameSpaceURL, []byte(s)).URN()
|
||||||
},
|
},
|
||||||
|
|
|
@ -67,6 +67,11 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
|
||||||
hmnOnly.GET(hmnurl.RegexJamFeed2023_Visibility, JamFeed2023_Visibility)
|
hmnOnly.GET(hmnurl.RegexJamFeed2023_Visibility, JamFeed2023_Visibility)
|
||||||
hmnOnly.GET(hmnurl.RegexJamRecap2023_Visibility, JamRecap2023_Visibility)
|
hmnOnly.GET(hmnurl.RegexJamRecap2023_Visibility, JamRecap2023_Visibility)
|
||||||
|
|
||||||
|
hmnOnly.GET(hmnurl.RegexTimeMachine, TimeMachine)
|
||||||
|
hmnOnly.GET(hmnurl.RegexTimeMachineForm, needsAuth(TimeMachineForm))
|
||||||
|
hmnOnly.GET(hmnurl.RegexTimeMachineFormDone, needsAuth(TimeMachineFormDone))
|
||||||
|
hmnOnly.POST(hmnurl.RegexTimeMachineForm, needsAuth(csrfMiddleware(TimeMachineFormSubmit)))
|
||||||
|
|
||||||
hmnOnly.GET(hmnurl.RegexStaffRolesIndex, StaffRolesIndex)
|
hmnOnly.GET(hmnurl.RegexStaffRolesIndex, StaffRolesIndex)
|
||||||
hmnOnly.GET(hmnurl.RegexStaffRole, StaffRole)
|
hmnOnly.GET(hmnurl.RegexStaffRole, StaffRole)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
package website
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/email"
|
||||||
|
"git.handmade.network/hmn/hmn/src/hmnurl"
|
||||||
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
"git.handmade.network/hmn/hmn/src/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TimeMachine(c *RequestContext) ResponseData {
|
||||||
|
baseData := getBaseDataAutocrumb(c, "Time Machine")
|
||||||
|
baseData.OpenGraphItems = []templates.OpenGraphItem{
|
||||||
|
{Property: "og:title", Value: "Time Machine"},
|
||||||
|
{Property: "og:site_name", Value: "Handmade Network"},
|
||||||
|
{Property: "og:type", Value: "website"},
|
||||||
|
{Property: "og:image", Value: hmnurl.BuildPublic("timemachine/opengraph.png", true)},
|
||||||
|
{Property: "og:description", Value: "This summer, dig out your old devices and see what they were actually like to use."},
|
||||||
|
{Property: "og:url", Value: hmnurl.BuildTimeMachine()},
|
||||||
|
{Name: "twitter:card", Value: "summary_large_image"},
|
||||||
|
{Name: "twitter:image", Value: hmnurl.BuildPublic("timemachine/twittercard.png", true)},
|
||||||
|
}
|
||||||
|
|
||||||
|
type TemplateData struct {
|
||||||
|
templates.BaseData
|
||||||
|
SubmitUrl string
|
||||||
|
}
|
||||||
|
tmpl := TemplateData{
|
||||||
|
BaseData: baseData,
|
||||||
|
SubmitUrl: hmnurl.BuildTimeMachineForm(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var res ResponseData
|
||||||
|
res.MustWriteTemplate("timemachine.html", tmpl, c.Perf)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeMachineForm(c *RequestContext) ResponseData {
|
||||||
|
var res ResponseData
|
||||||
|
res.MustWriteTemplate(
|
||||||
|
"timemachine_submit.html",
|
||||||
|
getBaseDataAutocrumb(c, "Time Machine"),
|
||||||
|
c.Perf,
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeMachineFormSubmit(c *RequestContext) ResponseData {
|
||||||
|
c.Req.ParseForm()
|
||||||
|
|
||||||
|
mediaUrl := strings.TrimSpace(c.Req.Form.Get("media_url"))
|
||||||
|
deviceInfo := strings.TrimSpace(c.Req.Form.Get("device_info"))
|
||||||
|
description := strings.TrimSpace(c.Req.Form.Get("description"))
|
||||||
|
|
||||||
|
discordUsername := ""
|
||||||
|
if c.CurrentUser.DiscordUser != nil {
|
||||||
|
discordUsername = c.CurrentUser.DiscordUser.Username
|
||||||
|
}
|
||||||
|
err := email.SendTimeMachineEmail(
|
||||||
|
hmnurl.BuildUserProfile(c.CurrentUser.Username),
|
||||||
|
c.CurrentUser.BestName(),
|
||||||
|
c.CurrentUser.Email,
|
||||||
|
discordUsername,
|
||||||
|
mediaUrl,
|
||||||
|
deviceInfo,
|
||||||
|
description,
|
||||||
|
c.Perf,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to send time machine email"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Redirect(hmnurl.BuildTimeMachineFormDone(), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeMachineFormDone(c *RequestContext) ResponseData {
|
||||||
|
type TemplateData struct {
|
||||||
|
templates.BaseData
|
||||||
|
TimeMachineUrl string
|
||||||
|
}
|
||||||
|
tmpl := TemplateData{
|
||||||
|
BaseData: getBaseDataAutocrumb(c, "Time Machine"),
|
||||||
|
TimeMachineUrl: hmnurl.BuildTimeMachine(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var res ResponseData
|
||||||
|
res.MustWriteTemplate(
|
||||||
|
"timemachine_thanks.html",
|
||||||
|
tmpl,
|
||||||
|
c.Perf,
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
}
|