diff --git a/createwebsite.sh b/createwebsite.sh
index 646930d..f31db76 100755
--- a/createwebsite.sh
+++ b/createwebsite.sh
@@ -1,14 +1,4 @@
#!/bin/sh
-cd $(dirname "$(readlink -f "$0")")
-git clone https://centrifuge.hectabit.org/hectabit/burgernotes --depth=1
-cd burgernotes
-mkdir -p ../website/static ../website/app ../website/error ../website/login ../website/logout ../website/privacy ../website/signup
-cp -r static/* ../website/static
-cp templates/app.html ../website/app/index.html
-cp templates/error.html ../website/error/index.html
-cp templates/login.html ../website/login/index.html
-cp ../logout.html ../website/logout/index.html
-cp templates/privacy.html ../website/privacy/index.html
-cp templates/signup.html ../website/signup/index.html
-cd ..
-rm -rf burgernotes
+git clone https://centrifuge.hectabit.org/hectabit/burgernotes-client-web.git --depth=1
+mv burgernotes-client-web/* website/
+rm -r burgernotes-client-web website/index.html website/README.md website/LICENSE
diff --git a/website/app/index.html b/website/app/index.html
index 27e5ac0..72111a2 100644
--- a/website/app/index.html
+++ b/website/app/index.html
@@ -8,6 +8,16 @@
+
+
diff --git a/website/error/index.html b/website/error/index.html
index aa128e0..26bc137 100644
--- a/website/error/index.html
+++ b/website/error/index.html
@@ -7,6 +7,16 @@
+
+
diff --git a/website/login/index.html b/website/login/index.html
index 8d75c11..d8c1b0a 100644
--- a/website/login/index.html
+++ b/website/login/index.html
@@ -8,18 +8,33 @@
-
+
+
+ Image by perga (@pergagreen on discord)
+
diff --git a/website/logout/index.html b/website/logout/index.html
index 4d84463..f2c1f70 100644
--- a/website/logout/index.html
+++ b/website/logout/index.html
@@ -5,6 +5,16 @@
Burgernotes
+
+
Logging out..
diff --git a/website/signup/index.html b/website/signup/index.html
index 2d99ebd..e2dbceb 100644
--- a/website/signup/index.html
+++ b/website/signup/index.html
@@ -8,10 +8,21 @@
-
+
+
+ Image by perga (@pergagreen on discord)
+
Signup
Signup for a Burgernotes account
@@ -19,10 +30,10 @@
+
Already have an account? If so, Login instead!
Please note that it's impossible to reset your password, do not forget it!
- By signing up, you agree to our
Privacy & Terms.
-
Already have an account? If so, Login instead!
+
Your homeserver is loading...
+
Privacy & Terms
-
diff --git a/website/static/css/style.css b/website/static/css/style.css
index 93eb6a0..1212323 100644
--- a/website/static/css/style.css
+++ b/website/static/css/style.css
@@ -1,6 +1,8 @@
@import url("../fonts/inter.css");
:root {
+ --contrast: #eee;
+ --contrast2: #fff;
--invertdm: 0%;
--bar: #f4f4f4;
--editor: #ffffff;
@@ -23,6 +25,8 @@
@media (prefers-color-scheme: dark) {
:root {
--invertdm: 100%;
+ --contrast: #2d2f21;
+ --contrast2: #2d2f21;
--bar: #2d2f31;
--editor: #202124;
--text-color: #ffffff;
@@ -52,7 +56,7 @@
}
.newNote img {
- filter: invert(100%)
+ filter: invert(100%);
}
#errorDiv p {
@@ -64,7 +68,7 @@
}
.burgerSession img {
- filter: invert(100%) !important
+ filter: invert(100%) !important;
}
.links a {
@@ -81,16 +85,16 @@
.inoutdiv input {
color: white;
- background-color: #202124;
+ background-color: var(--editor);
}
.flathubLogo {
- filter: invert(100%)
+ filter: invert(100%);
}
.feature {
background-color: rgba(0, 0, 0, 0) !important;
- color: var(--text-color)
+ color: var(--text-color);
}
.mainDiv .yellow {
border-color: #e9e98d !important;
@@ -127,6 +131,14 @@ body {
font-family: "Inter", sans-serif;
}
+.hiddenButton {
+ right: 0px;
+ position: fixed;
+ background-color: var(--editor);
+ color: var(--editor);
+ padding: 20px;
+}
+
/* Web app */
.topBar {
position: fixed;
@@ -198,7 +210,6 @@ body {
background-color: var(--bar);
-
border: solid;
border-color: var(--border-color);
border-width: 1px;
@@ -210,19 +221,15 @@ body {
width: calc(100% - 14px - 4px - 7px);
color: var(--text-color);
background-color: #ffffff;
-
height: 35px;
line-height: 35px;
margin: 7px;
padding-left: 7px;
-
border: solid;
border-color: var(--border-color);
border-width: 1px;
border-radius: 8px;
-
font-size: 15px;
-
text-decoration: none;
}
@@ -248,7 +255,6 @@ body {
height: calc(100% - 50px - 30px - 1px);
background-color: var(--bar);
-
border: solid;
border-color: var(--border-color);
border-width: 0px;
@@ -270,27 +276,26 @@ body {
margin-bottom: 0;
background-color: var(--note-button);
color: var(--unselected-note-button-text-color);
- border: none;
border-radius: 8px;
border: solid;
border-color: var(--border-color);
border-width: 1px;
-
font-size: 15px;
text-align: left;
cursor: pointer;
+ white-space: nowrap;
+ overflow-x: hidden;
}
.notesBar .loadingStuff {
border: none;
-
background:
- linear-gradient(0.25turn, transparent, #fff, transparent),
- linear-gradient(#eee, #eee),
- radial-gradient(38px circle at 19px 19px, #eee 50%, transparent 51%),
- linear-gradient(#eee, #eee);
+ linear-gradient(0.25turn, transparent, var(--contrast2), transparent),
+ linear-gradient(var(--contrast), var(--contrast)),
+ radial-gradient(38px circle at 19px 19px, #eee 50%, transparent 51%),
+ linear-gradient(var(--contrast), var(--contrast));
background-repeat: no-repeat;
background-size: 315px 250px, 315px 180px, 100px 100px, 225px 30px;
background-position: -315px 0, 0 0, 0px 190px, 50px 195px;
@@ -317,7 +322,6 @@ body {
background-color: rgba(0, 0, 0, 0);
border: none;
font-size: 16px;
-
margin-bottom: 5px;
cursor: pointer;
}
@@ -344,7 +348,7 @@ body {
}
.noteBox:focus {
- outline: none
+ outline: none;
}
.optionsCoverDiv {
@@ -360,14 +364,14 @@ body {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
- width: 300px;
position: fixed;
background-color: var(--option-background);
padding: 10px;
color: var(--text-color);
border-radius: 8px;
- min-width: 338.5px;
+ min-width: 300px;
z-index: 3;
+
}
.optionsDiv button {
@@ -384,6 +388,7 @@ body {
background-color: var(--theme-color);
border-radius: 8px;
cursor: pointer;
+
}
.optionsDiv .normalButton {
@@ -398,7 +403,7 @@ body {
.optionsDiv input {
width: calc(100% - 12px);
height: 25px;
- background-color: ffffff;
+ background-color: #ffffff;
padding-left: 5px;
padding-right: 5px;
margin-bottom: 7px;
@@ -511,8 +516,11 @@ body {
/* Sign up/log in div */
.inoutdiv {
+ border-radius: 8px;
margin: 10%;
- padding: 15px;
+ padding: 30px;
+ border: solid 1px var(--border-color);
+ background-color: var(--bar);
}
.inoutdiv input {
@@ -526,6 +534,7 @@ body {
border-color: var(--border-color);
border-width: 1px;
border-radius: 8px;
+
}
.inoutdiv button {
@@ -540,6 +549,7 @@ body {
border-radius: 8px;
font-size: 14px;
+
}
.inoutdiv a {
@@ -547,6 +557,23 @@ body {
text-align: center;
}
+.background {
+ position: fixed;
+ z-index: -2;
+ top: 0;
+ width: 100%;
+ min-height: 100%;
+}
+
+.credit {
+ position: fixed;
+ left: 5px;
+ color: white;
+ z-index: -1;
+ margin: 0;
+ bottom: 5px;
+}
+
.hidden {
display: none !important;
}
@@ -661,20 +688,24 @@ body {
.links a {
margin-left: 5px;
text-decoration: none;
- background-color: #f8f8f8;
+ background-color: var(--bar);
color: var(--text-color);
padding: 10px;
+ padding-top: 2.5px;
+ margin-bottom: 10px;
border-radius: 10px;
transition: background-color .2s;
+ display: inline-block;
}
.links a:hover {
- background-color: #eaeaea;
+ background-color: var(--editor);
}
.links a img {
transform: translateY(5px);
padding-right: 10px;
+ filter: invert(var(--invertdm));
}
.links a:hover {
diff --git a/website/static/img/background.jpg b/website/static/img/background.jpg
new file mode 100644
index 0000000..585ef41
Binary files /dev/null and b/website/static/img/background.jpg differ
diff --git a/website/static/js/homeserver.js b/website/static/js/homeserver.js
new file mode 100644
index 0000000..fb3bbb0
--- /dev/null
+++ b/website/static/js/homeserver.js
@@ -0,0 +1,49 @@
+let homeserverBox = document.getElementById("homeserverBox")
+let statusBox = document.getElementById("statusBox")
+let changeButton = document.getElementById("changeButton")
+
+function showElements(yesorno) {
+ if (!yesorno) {
+ homeserverBox.classList.add("hidden")
+ changeButton.classList.add("hidden")
+ }
+ else {
+ homeserverBox.classList.remove("hidden")
+ changeButton.classList.remove("hidden")
+ }
+}
+
+changeButton.addEventListener("click", (event) => {
+ async function doStuff() {
+ let remote = homeserverBox.value
+
+ if (remote == "") {
+ statusBox.innerText = "A homeserver is required!"
+ return
+ }
+
+ showElements(false)
+ statusBox.innerText = "Connecting to homeserver..."
+
+ fetch(remote + "/api/version")
+ .then((response) => response)
+ .then((response) => {
+ async function doStuff() {
+ if (response.status == 200) {
+ localStorage.setItem("homeserverURL", remote)
+
+ window.location.href = document.referrer;
+ }
+ else if (response.status == 404) {
+ statusBox.innerText = "Not a valid homeserver!"
+ }
+ else {
+ statusBox.innerText = "Something went wrong!"
+ showElements(true)
+ }
+ }
+ doStuff()
+ });
+ }
+ doStuff()
+});
diff --git a/website/static/js/login.js b/website/static/js/login.js
index e20da4c..a415070 100644
--- a/website/static/js/login.js
+++ b/website/static/js/login.js
@@ -9,6 +9,12 @@ if (localStorage.getItem("DONOTSHARE-password") !== null) {
throw new Error();
}
+let remote = localStorage.getItem("homeserverURL")
+if (remote == null) {
+ localStorage.setItem("homeserverURL", "https://notes.hectabit.org")
+ remote = "https://notes.hectabit.org"
+}
+
let usernameBox = document.getElementById("usernameBox")
let passwordBox = document.getElementById("passwordBox")
let statusBox = document.getElementById("statusBox")
@@ -65,6 +71,10 @@ function showElements(yesorno) {
}
}
+document.addEventListener('DOMContentLoaded', function() {
+ document.getElementById("homeserver").innerText = "Your homeserver is: " + remote + ". "
+});
+
signupButton.addEventListener("click", (event) => {
if (passwordBox.classList.contains("hidden")) {
if (usernameBox.value == "") {
@@ -109,7 +119,7 @@ signupButton.addEventListener("click", (event) => {
return key
};
- fetch("https://notes.hectabit.org/api/login", {
+ fetch(remote + "/api/login", {
method: "POST",
body: JSON.stringify({
username: username,
@@ -133,7 +143,7 @@ signupButton.addEventListener("click", (event) => {
}
else if (response.status == 401) {
console.log("Trying oldhash")
- fetch("https://notes.hectabit.org/api/login", {
+ fetch(remote + "/api/login", {
method: "POST",
body: JSON.stringify({
username: username,
diff --git a/website/static/js/main.js b/website/static/js/main.js
index 0cf630f..36634b5 100644
--- a/website/static/js/main.js
+++ b/website/static/js/main.js
@@ -13,6 +13,12 @@ if (localStorage.getItem("CACHE-username") !== null) {
document.getElementById("usernameBox").innerText = localStorage.getItem("CACHE-username")
}
+let remote = localStorage.getItem("homeserverURL")
+if (remote == null) {
+ localStorage.setItem("homeserverURL", "https://notes.hectabit.org")
+ remote = "https://notes.hectabit.org"
+}
+
function formatBytes(a, b = 2) { if (!+a) return "0 Bytes"; const c = 0 > b ? 0 : b, d = Math.floor(Math.log(a) / Math.log(1000)); return `${parseFloat((a / Math.pow(1000, d)).toFixed(c))} ${["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"][d]}` }
function truncateString(str, num) {
@@ -196,8 +202,17 @@ textMinusBox.addEventListener("click", (event) => {
});
+function truncateString(str, num) {
+ if (str.length > num) {
+ return str.slice(0, num) + "..";
+ } else {
+ return str;
+ }
+}
+
+
function updateUserInfo() {
- fetch("https://notes.hectabit.org/api/userinfo", {
+ fetch(remote + "/api/userinfo", {
method: "POST",
body: JSON.stringify({
secretKey: secretkey
@@ -248,7 +263,7 @@ exitThing.addEventListener("click", (event) => {
});
deleteMyAccountButton.addEventListener("click", (event) => {
if (confirm("Are you REALLY sure that you want to delete your account? There's no going back!") == true) {
- fetch("https://notes.hectabit.org/api/deleteaccount", {
+ fetch(remote + "/api/deleteaccount", {
method: "POST",
body: JSON.stringify({
secretKey: secretkey
@@ -257,7 +272,6 @@ deleteMyAccountButton.addEventListener("click", (event) => {
"Content-Type": "application/json; charset=UTF-8"
}
})
- .then((response) => response)
.then((response) => {
if (response.status == 200) {
window.location.href = "../logout/index.html"
@@ -271,7 +285,7 @@ sessionManagerButton.addEventListener("click", (event) => {
optionsDiv.classList.add("hidden")
sessionManagerDiv.classList.remove("hidden")
- fetch("https://notes.hectabit.org/api/sessions/list", {
+ fetch(remote + "/api/sessions/list", {
method: "POST",
body: JSON.stringify({
secretKey: secretkey
@@ -280,7 +294,6 @@ sessionManagerButton.addEventListener("click", (event) => {
"Content-Type": "application/json; charset=UTF-8"
}
})
- .then((response) => response)
.then((response) => {
async function doStuff() {
let responseData = await response.json()
@@ -292,9 +305,9 @@ sessionManagerButton.addEventListener("click", (event) => {
let sessionRemoveButton = document.createElement("button")
sessionText.classList.add("w300")
if (responseData[i]["thisSession"] == true) {
- sessionText.innerText = "(current) " + truncateString(responseData[i]["device"], 18)
+ sessionText.innerText = "(current) " + responseData[i]["device"]
} else {
- sessionText.innerText = truncateString(responseData[i]["device"], 27)
+ sessionText.innerText = responseData[i]["device"]
}
sessionText.title = responseData[i]["device"]
sessionRemoveButton.innerText = "x"
@@ -311,7 +324,7 @@ sessionManagerButton.addEventListener("click", (event) => {
}
sessionRemoveButton.addEventListener("click", (event) => {
- fetch("https://notes.hectabit.org/api/sessions/remove", {
+ fetch(remote + "/api/sessions/remove", {
method: "POST",
body: JSON.stringify({
secretKey: secretkey,
@@ -321,7 +334,6 @@ sessionManagerButton.addEventListener("click", (event) => {
"Content-Type": "application/json; charset=UTF-8"
}
})
- .then((response) => response)
.then((response) => {
if (responseData[i]["thisSession"] == true) {
window.location.replace("../logout/index.html")
@@ -362,7 +374,7 @@ function selectNote(nameithink) {
let thingArray = Array.from(document.querySelectorAll(".noteButton")).find(el => el.id == nameithink);
thingArray.classList.add("selected")
- fetch("https://notes.hectabit.org/api/readnote", {
+ fetch(remote + "/api/readnote", {
method: "POST",
body: JSON.stringify({
secretKey: secretkey,
@@ -378,7 +390,6 @@ function selectNote(nameithink) {
noteBox.placeholder = ""
displayError("Something went wrong... Please try again later!")
})
- .then((response) => response)
.then((response) => {
selectedNote = nameithink
noteBox.readOnly = false
@@ -397,21 +408,28 @@ function selectNote(nameithink) {
updateWordCount()
clearTimeout(timer);
timer = setTimeout(() => {
+ let encryptedTitle = "New note"
+ if (noteBox.value.substring(0, noteBox.value.indexOf("\n")) != "") {
+ let firstTitle = noteBox.value.substring(0, noteBox.value.indexOf("\n"));
+
+ document.getElementById(nameithink).innerText = firstTitle
+ encryptedTitle = CryptoJS.AES.encrypt(firstTitle, password).toString();
+ }
let encryptedText = CryptoJS.AES.encrypt(noteBox.value, password).toString();
if (selectedNote == nameithink) {
- fetch("https://notes.hectabit.org/api/editnote", {
+ fetch(remote + "/api/editnote", {
method: "POST",
body: JSON.stringify({
secretKey: secretkey,
noteId: nameithink,
content: encryptedText,
+ title: encryptedTitle
}),
headers: {
"Content-Type": "application/json; charset=UTF-8"
}
})
- .then((response) => response)
.then((response) => {
if (response.status == 418) {
displayError("You've ran out of storage... Changes will not be saved until you free up storage!")
@@ -429,7 +447,7 @@ function selectNote(nameithink) {
}
function updateNotes() {
- fetch("https://notes.hectabit.org/api/listnotes", {
+ fetch(remote + "/api/listnotes", {
method: "POST",
body: JSON.stringify({
secretKey: secretkey
@@ -438,7 +456,6 @@ function updateNotes() {
"Content-Type": "application/json; charset=UTF-8"
}
})
- .then((response) => response)
.then((response) => {
async function doStuff() {
document.querySelectorAll(".noteButton").forEach((el) => el.remove());
@@ -459,11 +476,11 @@ function updateNotes() {
let originalTitle = bytes.toString(CryptoJS.enc.Utf8);
noteButton.id = responseData[i]["id"]
- noteButton.innerText = originalTitle
+ noteButton.innerText = truncateString(originalTitle, 15)
noteButton.addEventListener("click", (event) => {
if (event.ctrlKey) {
- fetch("https://notes.hectabit.org/api/removenote", {
+ fetch(remote + "/api/removenote", {
method: "POST",
body: JSON.stringify({
secretKey: secretkey,
@@ -473,7 +490,6 @@ function updateNotes() {
"Content-Type": "application/json; charset=UTF-8"
}
})
- .then((response) => response)
.then((response) => {
updateNotes()
})
@@ -494,38 +510,29 @@ function updateNotes() {
updateNotes()
newNote.addEventListener("click", (event) => {
- let noteName = displayPrompt("Note name?", "E.G Shopping list", burgerFunction)
- function burgerFunction(noteName) {
- if (noteName != null) {
- if (noteName.length > 21) {
- displayError("Invalid note name: Too long (max 21 characters)");
- return;
- }
-
- let encryptedName = CryptoJS.AES.encrypt(noteName, password).toString();
- fetch("https://notes.hectabit.org/api/newnote", {
- method: "POST",
- body: JSON.stringify({
- secretKey: secretkey,
- noteName: encryptedName,
- }),
- headers: {
- "Content-Type": "application/json; charset=UTF-8"
- }
- })
- .catch((error) => {
- displayError("Failed to create new note, please try again later...")
- })
- .then((response) => {
- if (response.status !== 200) {
- updateNotes()
- displayError("Failed to create new note (HTTP error code " + response.status + ")")
- } else {
- updateNotes()
- }
- });
+ let noteName = "New note"
+ let encryptedName = CryptoJS.AES.encrypt(noteName, password).toString();
+ fetch(remote + "/api/newnote", {
+ method: "POST",
+ body: JSON.stringify({
+ secretKey: secretkey,
+ noteName: encryptedName,
+ }),
+ headers: {
+ "Content-Type": "application/json; charset=UTF-8"
}
- }
+ })
+ .catch((error) => {
+ displayError("Failed to create new note, please try again later...")
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ updateNotes()
+ displayError("Failed to create new note (HTTP error code " + response.status + ")")
+ } else {
+ updateNotes()
+ }
+ });
});
function downloadObjectAsJson(exportObj, exportName) {
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
@@ -539,7 +546,7 @@ function downloadObjectAsJson(exportObj, exportName) {
function exportNotes() {
let noteExport = []
- fetch("https://notes.hectabit.org/api/exportnotes", {
+ fetch(remote + "/api/exportnotes", {
method: "POST",
body: JSON.stringify({
secretKey: secretkey
@@ -548,7 +555,6 @@ function exportNotes() {
"Content-Type": "application/json; charset=UTF-8"
}
})
- .then((response) => response)
.then((response) => {
async function doStuff() {
let responseData = await response.json()
@@ -588,6 +594,16 @@ function isFirstTimeVisitor() {
}
}
+function firstNewVersion() {
+ if (document.cookie.indexOf("version=1.2") !== -1) {
+ return false;
+ } else {
+ var expirationDate = new Date();
+ expirationDate.setFullYear(expirationDate.getFullYear() + 1);
+ document.cookie = "version=1.2; expires=" + expirationDate.toUTCString() + "; path=/; SameSite=strict";
+ return true;
+ }
+}
exportNotesButton.addEventListener("click", (event) => {
exportNotesButton.innerText = "Downloading..."
@@ -598,7 +614,7 @@ removeBox.addEventListener("click", (event) => {
if (selectedNote == 0) {
displayError("You need to select a note first!")
} else {
- fetch("https://notes.hectabit.org/api/removenote", {
+ fetch(remote + "/api/removenote", {
method: "POST",
body: JSON.stringify({
secretKey: secretkey,
@@ -608,7 +624,6 @@ removeBox.addEventListener("click", (event) => {
"Content-Type": "application/json; charset=UTF-8"
}
})
- .then((response) => response)
.then((response) => {
updateNotes()
})
@@ -621,3 +636,7 @@ removeBox.addEventListener("click", (event) => {
if (isFirstTimeVisitor() && /Android|iPhone|iPod/i.test(navigator.userAgent)) {
displayError("To use Burgernotes:\n Swipe Right on a note to open it\n Swipe left in the text boxes to return to notes\n Click on a note to highlight it")
}
+
+if (firstNewVersion()) {
+ displayError("What's new in Burgernotes 1.2?\n\nNote titles are now the first line of a note \(will not break compatibility with older notes\)\nIntroduced improved login screen\nNote titles now scroll correctly")
+}
diff --git a/website/static/js/signup.js b/website/static/js/signup.js
index 9452651..e2a0221 100644
--- a/website/static/js/signup.js
+++ b/website/static/js/signup.js
@@ -9,6 +9,12 @@ if (localStorage.getItem("DONOTSHARE-password") !== null) {
throw new Error();
}
+let remote = localStorage.getItem("homeserverURL")
+if (remote == null) {
+ localStorage.setItem("homeserverURL", "https://notes.hectabit.org")
+ remote = "https://notes.hectabit.org"
+}
+
let usernameBox = document.getElementById("usernameBox")
let passwordBox = document.getElementById("passwordBox")
let statusBox = document.getElementById("statusBox")
@@ -27,6 +33,10 @@ function showElements(yesorno) {
}
}
+document.addEventListener('DOMContentLoaded', function() {
+ document.getElementById("homeserver").innerText = "Your homeserver is: " + remote + ". "
+});
+
signupButton.addEventListener("click", (event) => {
async function doStuff() {
let username = usernameBox.value
@@ -61,7 +71,7 @@ signupButton.addEventListener("click", (event) => {
};
- fetch("https://notes.hectabit.org/api/signup", {
+ fetch(remote + "/api/signup", {
method: "POST",
body: JSON.stringify({
username: username,
diff --git a/website/static/svg/favicon.svg b/website/static/svg/favicon.svg
new file mode 100644
index 0000000..6e7f325
--- /dev/null
+++ b/website/static/svg/favicon.svg
@@ -0,0 +1,55 @@
+
diff --git a/website/static/svg/grid.svg b/website/static/svg/grid.svg
new file mode 100644
index 0000000..fc91a30
--- /dev/null
+++ b/website/static/svg/grid.svg
@@ -0,0 +1,3535 @@
+