-
+
+

+
{{ with .DisplayName }}
{{.}}
diff --git a/cmd/hello/helloserver/helloserver.go b/cmd/hello/helloserver/helloserver.go
index 8d5972b83..41e7dbce2 100644
--- a/cmd/hello/helloserver/helloserver.go
+++ b/cmd/hello/helloserver/helloserver.go
@@ -6,7 +6,7 @@ package helloserver
import (
"crypto/tls"
- _ "embed"
+ "embed"
"html/template"
"log"
"net/http"
@@ -21,6 +21,11 @@ import (
//go:embed hello.tmpl.html
var embeddedTemplate string
+//go:embed static/*
+var staticFiles embed.FS
+
+var staticHandler = http.FileServerFS(staticFiles)
+
var tmpl = template.Must(template.New("home").Parse(embeddedTemplate))
// Server is an HTTP server for hello.ts.net.
@@ -116,6 +121,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://"+host, http.StatusFound)
return
}
+
+ if strings.HasPrefix(r.RequestURI, "/static/") {
+ staticHandler.ServeHTTP(w, r)
+ return
+ }
+
if r.RequestURI != "/" {
http.Redirect(w, r, "/", http.StatusFound)
return
diff --git a/cmd/hello/helloserver/static/script.js b/cmd/hello/helloserver/static/script.js
new file mode 100644
index 000000000..db9bcd0f3
--- /dev/null
+++ b/cmd/hello/helloserver/static/script.js
@@ -0,0 +1,12 @@
+(function () {
+ var lastSeen = localStorage.getItem("lastSeen");
+ if (!lastSeen) {
+ document.body.classList.add("animate");
+ window.addEventListener("load", function () {
+ setTimeout(function () {
+ document.body.classList.add("animating");
+ localStorage.setItem("lastSeen", Date.now());
+ }, 100);
+ });
+ }
+})();
diff --git a/cmd/hello/helloserver/static/style.css b/cmd/hello/helloserver/static/style.css
new file mode 100644
index 000000000..8ad55edc6
--- /dev/null
+++ b/cmd/hello/helloserver/static/style.css
@@ -0,0 +1,366 @@
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ font-size: 100%;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+html,
+body,
+main {
+ height: 100%;
+}
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+ border-width: 0;
+ border-style: solid;
+ border-color: #dad6d5;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin: 0;
+ font-size: 1rem;
+ font-weight: inherit;
+}
+
+a {
+ color: inherit;
+}
+
+p {
+ margin: 0;
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ max-width: 24rem;
+ width: 95%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.p-2 {
+ padding: 0.5rem;
+}
+
+.p-4 {
+ padding: 1rem;
+}
+
+.px-2 {
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+}
+
+.pl-3 {
+ padding-left: 0.75rem;
+}
+
+.pr-3 {
+ padding-right: 0.75rem;
+}
+
+.pt-4 {
+ padding-top: 1rem;
+}
+
+.mr-2 {
+ margin-right: 0.5rem;
+;
+}
+
+.mb-1 {
+ margin-bottom: 0.25rem;
+}
+
+.mb-2 {
+ margin-bottom: 0.5rem;
+}
+
+.mb-4 {
+ margin-bottom: 1rem;
+}
+
+.mb-6 {
+ margin-bottom: 1.5rem;
+}
+
+.mb-8 {
+ margin-bottom: 2rem;
+}
+
+.mb-12 {
+ margin-bottom: 3rem;
+}
+
+.width-full {
+ width: 100%;
+}
+
+.min-width-0 {
+ min-width: 0;
+}
+
+.rounded-lg {
+ border-radius: 0.5rem;
+}
+
+.relative {
+ position: relative;
+}
+
+.flex {
+ display: flex;
+}
+
+.justify-between {
+ justify-content: space-between;
+}
+
+.items-center {
+ align-items: center;
+}
+
+.border {
+ border-width: 1px;
+}
+
+.border-t-1 {
+ border-top-width: 1px;
+}
+
+.border-gray-100 {
+ border-color: #f7f5f4;
+}
+
+.border-gray-200 {
+ border-color: #eeebea;
+}
+
+.border-gray-300 {
+ border-color: #dad6d5;
+}
+
+.bg-white {
+ background-color: white;
+}
+
+.bg-gray-0 {
+ background-color: #faf9f8;
+}
+
+.bg-gray-100 {
+ background-color: #f7f5f4;
+}
+
+.text-green-600 {
+ color: #0d4b3b;
+}
+
+.text-blue-600 {
+ color: #3f5db3;
+}
+
+.hover\:text-blue-800:hover {
+ color: #253570;
+}
+
+.text-gray-600 {
+ color: #444342;
+}
+
+.text-gray-700 {
+ color: #2e2d2d;
+}
+
+.text-gray-800 {
+ color: #232222;
+}
+
+.text-center {
+ text-align: center;
+}
+
+.text-sm {
+ font-size: 0.875rem;
+}
+
+.font-title {
+ font-size: 1.25rem;
+ letter-spacing: -0.025em;
+}
+
+.font-semibold {
+ font-weight: 600;
+}
+
+.font-medium {
+ font-weight: 500;
+}
+
+.font-regular {
+ font-weight: 400;
+}
+
+.truncate {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.overflow-hidden {
+ overflow: hidden;
+}
+
+.profile-pic {
+ width: 2.5rem;
+ height: 2.5rem;
+ background-size: cover;
+ margin-right: 0.5rem;
+ flex-shrink: 0;
+}
+
+.profile-pic-img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ border-radius: 9999px;
+}
+
+.panel {
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+}
+
+.animate .panel {
+ transform: translateY(10%);
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.0), 0 10px 10px -5px rgba(0, 0, 0, 0.0);
+ transition: transform 1200ms ease, opacity 1200ms ease, box-shadow 1200ms ease;
+}
+
+.animate .panel-interior {
+ opacity: 0.0;
+ transition: opacity 1200ms ease;
+}
+
+.animate .logo {
+ transform: translateY(2rem);
+ opacity: 0.0;
+ transition: transform 1200ms ease, opacity 1200ms ease;
+}
+
+.animate .header-title {
+ transform: translateY(1.6rem);
+ opacity: 0.0;
+ transition: transform 1200ms ease, opacity 1200ms ease;
+}
+
+.animate .header-text {
+ transform: translateY(1.2rem);
+ opacity: 0.0;
+ transition: transform 1200ms ease, opacity 1200ms ease;
+}
+
+.animate .footer {
+ transform: translateY(-0.5rem);
+ opacity: 0.0;
+ transition: transform 1200ms ease, opacity 1200ms ease;
+}
+
+.animating .panel {
+ transform: translateY(0);
+ opacity: 1.0;
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+}
+
+.animating .panel-interior {
+ opacity: 1.0;
+}
+
+.animating .spinner {
+ opacity: 0.0;
+}
+
+.animating .logo,
+.animating .header-title,
+.animating .header-text,
+.animating .footer {
+ transform: translateY(0);
+ opacity: 1.0;
+}
+
+.spinner {
+ display: inline-flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ align-items: center;
+ transition: opacity 200ms ease;
+}
+
+.spinner span {
+ display: inline-block;
+ background-color: currentColor;
+ border-radius: 9999px;
+ animation-name: loading-dots-blink;
+ animation-duration: 1.4s;
+ animation-iteration-count: infinite;
+ animation-fill-mode: both;
+ width: 0.35em;
+ height: 0.35em;
+ margin: 0 0.15em;
+}
+
+.spinner span:nth-child(2) {
+ animation-delay: 200ms;
+}
+
+.spinner span:nth-child(3) {
+ animation-delay: 400ms;
+}
+
+.spinner {
+ display: none;
+}
+
+.animate .spinner {
+ display: inline-flex;
+}
+
+@keyframes loading-dots-blink {
+ 0% {
+ opacity: 0.2;
+ }
+ 20% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0.2;
+ }
+}
+
+@media (prefers-reduced-motion) {
+ * {
+ animation-duration: 0ms !important;
+ transition-duration: 0ms !important;
+ transition-delay: 0ms !important;
+ }
+}