From c5aa364e0e372d0e29063a27bd17811446db8b6a Mon Sep 17 00:00:00 2001 From: Alexis Hovorka Date: Fri, 16 Feb 2024 23:48:02 -0700 Subject: [feat] Flesh out basic UI --- app/public/icons.svg | 1 + app/public/index.html | 71 +++++--- app/public/logo-icon.svg | 96 +++++++++++ app/public/main.js | 119 +++++++++++--- app/public/style.css | 414 ++++++++++++++++++++++++++++++++++++++++++++++- design/cards.html | 37 ++++- design/icons.svg | 68 ++++++-- design/logo-icon.svg | 96 +++++++++++ 8 files changed, 834 insertions(+), 68 deletions(-) create mode 100644 app/public/icons.svg create mode 100644 app/public/logo-icon.svg create mode 100644 design/logo-icon.svg diff --git a/app/public/icons.svg b/app/public/icons.svg new file mode 100644 index 0000000..47e13fb --- /dev/null +++ b/app/public/icons.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/public/index.html b/app/public/index.html index c7b44c7..593e6b3 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -56,26 +56,59 @@ -

Notes

+ +
+

Notes

+

+
+ + + + +
+ +
+
-
- - - - -
-
- - -
- - -
- - - -
- + + +
+
+ + + + + +
diff --git a/app/public/logo-icon.svg b/app/public/logo-icon.svg new file mode 100644 index 0000000..d66678f --- /dev/null +++ b/app/public/logo-icon.svg @@ -0,0 +1,96 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/app/public/main.js b/app/public/main.js index 5dc9531..5746142 100644 --- a/app/public/main.js +++ b/app/public/main.js @@ -28,13 +28,25 @@ function debounce(fn, delay) { //} function drawNote(note) { - const el = m(`
-

${note.id}

- -
`); + const el = m(`
+ + +
`); - $("textarea", el).addEventListener("input", - debounce(() => { saveNote(note.id, $("textarea", el).value); }, 500)); + const ta = $("textarea", el) + + ta.addEventListener("input", + debounce(() => { saveNote(note.id, ta.value); }, 500)); + + function resizeTextarea() { // TODO simplify + el.style.height = (el.scrollHeight) + "px"; + ta.style.height = ""; ta.style.height = (ta.scrollHeight) + "px"; + el.style.height = ""; + } + ta.addEventListener("input", resizeTextarea); + ta.addEventListener("focus", resizeTextarea); + window.addEventListener("resize", resizeTextarea); + setTimeout(resizeTextarea); $("#notes").appendChild(el); } @@ -51,9 +63,7 @@ async function getList() { const list = await res.json(); console.log(list); - for (let i=0;i getNote(id))); } async function saveNote(id, content) { @@ -77,13 +87,12 @@ async function newNote() { drawNote(note); } -$("#new-note").addEventListener("click", newNote); +$("#add-btn").addEventListener("click", newNote); async function getUserData() { const res = await fetch(`/user`); const user = await res.json(); console.log(user); - if (user.uid) $("#uid").value = user.uid; } async function getUserSessions() { @@ -92,26 +101,75 @@ async function getUserSessions() { console.log(sessions); } -$("#login").addEventListener("click", async () => { +function showSignIn() { + const hash = location.hash.slice(2).replace(/^sign-in(\/to\/)?/,""); + history.replaceState("sign-in","","#/sign-in"+(hash?"/to/"+hash:"")); + $("#sign-in-dialog").showModal(); +} + +$("#sign-in-dialog").addEventListener("cancel", e => e.preventDefault()); +$("#sign-in-username").addEventListener("input", e => e.target.classList.remove("error")); +$("#sign-in-password").addEventListener("input", e => e.target.classList.remove("error")); +$("#sign-in-basic-go").addEventListener("click", async e => { + e.preventDefault(); + $("#sign-in-error").textContent = ""; + + const inputs = [ + $("#sign-in-username"), + $("#sign-in-password"), + $("#sign-in-keep-session"), + $("#sign-in-basic-go"), + ]; + const validatedInputs = inputs.slice(0,2); + + if (validatedInputs.map(e => { + if (!e.checkValidity()) { + e.classList.add("error"); + return true; } + }).reduce((a,e) => a||e, false)) return; + + inputs.forEach(e => e.disabled = true); + $("#sign-in-form").classList.add("loading"); + const res = await fetch("/login", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ - username: $("#username").value, - password: $("#password").value, - keepSession: $("#keep-session").checked, + username: $("#sign-in-username").value, + password: $("#sign-in-password").value, + keepSession: $("#sign-in-keep-session").checked, }) }); - // TODO check return code + inputs.forEach(e => e.disabled = false); + $("#sign-in-form").classList.remove("loading"); + if (!res.ok) { + $("#sign-in-error").textContent = res.status+" "+res.statusText; + return; + } const json = await res.json(); console.log(json); - // TODO "Must Change Password" flow + if (!json.success) { + $("#sign-in-error").textContent = json.msg; + return; + } + + if (json.mustChangePassword) { + alert("Must change password (TODO)"); + // TODO + return; + } + + const hash = location.hash.slice(9); + history.replaceState("","","#/"+hash); + // TODO load location getUserData(); getList(); + + $("#sign-in-dialog").close(); }); $("#change-password").addEventListener("click", async () => { @@ -119,8 +177,8 @@ $("#change-password").addEventListener("click", async () => { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ - username: $("#username").value, - password: $("#password").value, + username: $("#username").value, // TODO + password: $("#password").value, // TODO newPassword: $("#new-password").value, keepSession: $("#keep-session").checked, }) @@ -138,7 +196,7 @@ $("#change-username").addEventListener("click", async () => { headers: {"Content-Type": "application/json"}, body: JSON.stringify({ newUsername: $("#new-username").value, - password: $("#password").value, + password: $("#password").value, // TODO }) }); @@ -148,7 +206,7 @@ $("#change-username").addEventListener("click", async () => { getUserData(); }); -$("#logout").addEventListener("click", async () => { +$("#log-out").addEventListener("click", async () => { const res = await fetch("/logout", { method: "POST", }); @@ -156,19 +214,28 @@ $("#logout").addEventListener("click", async () => { //const json = await res.json(); //console.log(json); console.log("Logged out"); - $("#uid").value = ""; + $("#sign-in-dialog").showModal(); }); -$("#logout-everywhere").addEventListener("click", async () => { +$("#log-out-everywhere").addEventListener("click", async () => { const res = await fetch("/deauth-all", { method: "POST", }); console.log("Logged out everywhere"); - $("#uid").value = ""; + showSignIn(); }); -try { getUserData(); getList(); getUserSessions(); } catch(e) {} +try { await getUserData(); await getList(); await getUserSessions(); } catch(e) { showSignIn(); } + +(() => { + let prevScroll = 0; + window.addEventListener("scroll", () => { + document.querySelector("#toolbar").classList[ + (window.scrollY && window.scrollY > prevScroll)?"add":"remove"]("hide"); + prevScroll = window.scrollY; + }); +})(); // TODO make sure to save unsynced delta even if token has expired, then sync when logged in // TODO Cookie consent on signup; "only necessary for maintaining user session and cached data" @@ -190,4 +257,6 @@ try { getUserData(); getList(); getUserSessions(); } catch(e) {} // https://github.com/cure53/DOMPurify/blob/main/src/attrs.js // https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html +// TODO note kebab menu could be a element? + }); diff --git a/app/public/style.css b/app/public/style.css index 655e181..e7dd049 100644 --- a/app/public/style.css +++ b/app/public/style.css @@ -3,12 +3,418 @@ padding: 0; box-sizing: border-box; font-family: "Roboto", "Noto Sans", sans-serif; - transition-timing-function: ease-in-out; + transition-timing-function: cubic-bezier(.4,0,.2,1); } -body { - user-select: none; - -webkit-user-select: none; +/* TODO + - normalize text size + - put all colors in variables + - checklists using ::marker? + - links + - CSS scroll snap for columns + - https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_scroll_snap + - box-decoration-break: clone; -webkit-box-decoration-break: clone; + - overscroll-behavior: contain; + - user-select: text; +*/ + +:root, ::backdrop { + --bg-color: #FFF; + --page-bg-color: #EEE; + --text-color: rgba(0,0,0,.87); + --error-triple: 176, 0, 32; + --error-color: rgb(var(--error-triple)); + --icon-opacity-active: .87; + --icon-opacity-inactive: .6; + --icon-opacity-disabled: .38; + --icon-inactive-filter: brightness(0); + --icon-checkbox-active-filter: brightness(1); + --icon-add-hover-filter: brightness(1); + --icon-menu-hover-filter: brightness(1); + --icon-search-hover-filter: brightness(1); + --icon-window-hover-filter: brightness(1); + --menu-button-hover-bg-color: #FBBC04; + --button-hover-bg-color: #FFF; + --button-hover-shadow: 0 2px 16px rgba(0,0,0,.1), 0 2px 4px rgba(0,0,0,.2); + --fab-bg: #FFF; + --fab-shadow: 0 2px 12px rgba(0,0,0,.15), 0 2px 6px rgba(0,0,0,.2); + --fab-shadow-hover: 0 4px 16px rgba(0,0,0,.2), 0 3px 6px rgba(0,0,0,.3); + --toolbar-shadow-mobile: 0 8px 8px 8px rgba(0,0,0,.2); + --toolbar-hide-distance-mobile: calc(-56px - 8px); + --toolbar-button-hover-bg-mobile: rgba(0,0,0,.1); +} + +/*@media (prefers-color-scheme: dark) { :root, ::backdrop { + --bg-color: #000; + --page-bg-color: #000; + --text-color: rgba(255,255,255,.87); + --error-triple: 245, 0, 45; + --error-color: rgb(var(--error-triple)); + --icon-opacity-active: .87; + --icon-opacity-inactive: .6; + --icon-opacity-disabled: .38; + --icon-inactive-filter: brightness(0) invert(1) hue-rotate(180deg); + --icon-add-hover-filter: brightness(1.4) invert(.1) hue-rotate(0); + --icon-menu-hover-filter: brightness(1) invert(1) hue-rotate(180deg); + --icon-search-hover-filter: brightness(1) invert(1) hue-rotate(180deg); + --icon-window-hover-filter: brightness(.75) invert(1) hue-rotate(180deg); + --menu-button-hover-bg-color: #FBBC04; + --button-hover-bg-color: #181818; + --button-hover-shadow: 0 0 0 transparent; + --fab-bg: #333; + --fab-shadow: 0 2px 12px rgba(0,0,0,.2), 0 2px 6px rgba(0,0,0,.24); + --fab-shadow-hover: 0 2px 12px rgba(0,0,0,.2), 0 2px 6px rgba(0,0,0,.24); + --toolbar-shadow-mobile: 0 8px 8px 8px #000, 0 56px 56px -56px rgba(255,255,255,.25) inset; + --toolbar-hide-distance-mobile: calc(-56px - 8px); + --toolbar-button-hover-bg-mobile: rgba(255,255,255,.1); +}}*/ + +html { + background: var(--page-bg-color); + color: var(--text-color); + overscroll-behavior: contain; +} + +body, dialog { /* TODO */ + user-select: none; -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; } + +#toolbar { position: fixed; font-size: 0; } +#toolbar > * { font-size: 16px; text-align: center; } +.toolbar-btn { height: 48px; width: 48px; } +.toolbar-btn, .toolbar-fab { + position: relative; + border-radius: 100%; + background: transparent; + border: none; + outline: none; +} + +.toolbar-btn:not(#conflict-btn)::after, .toolbar-fab::after { content: ""; display: block; height: 24px; width: 24px; background-image: url("icons.svg"); filter: var(--icon-inactive-filter); opacity: var(--icon-opacity-active); position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); } +#menu-btn::after { background-position: 0 0; } +#window-btn::after { background-position: -24px 0; } +#search-btn::after { background-position: -48px 0; } +#add-btn::after { background-position: -72px 0; } + +#conflict-btn { display: none; vertical-align: bottom; } +#conflict-btn::after { content: ""; position: absolute; top: 50%; left: 50%; display: block; height: 8px; width: 8px; border-radius: 4px; background: rgba(var(--error-triple), .8); transform: translate(-50%,-50%); } + +@media (hover:hover) { + .toolbar-btn, .toolbar-fab { cursor: pointer; transition: background-color .2s, box-shadow .2s; } + .toolbar-btn:not(#conflict-btn)::after, .toolbar-fab::after { transition: opacity .1s, filter .1s; filter: var(--icon-inactive-filter); } + .toolbar-btn:not(#conflict-btn):hover::after, .toolbar-fab:hover::after { opacity: 1; } + + #menu-btn:hover::after { filter: var(--icon-menu-hover-filter); } + #window-btn:hover::after { filter: var(--icon-window-hover-filter); } + #search-btn:hover::after { filter: var(--icon-search-hover-filter); } + #add-btn:hover::after { filter: var(--icon-add-hover-filter); } + + #conflict-btn::after { transition: box-shadow .1s; } + #conflict-btn:hover::after { box-shadow: 0 0 0 4px rgba(var(--error-triple),.25); } +} + +@media (hover:hover) and (min-width:601px) { + .notes-column { padding: 8px 80px 16px; } + #toolbar { top: 16px; left: 50%; transform: translate(-50%,0); width: 100%; max-width: calc(7in + 16px); pointer-events: none; } + #toolbar > * { pointer-events: auto; } + .toolbar-fab { height: 48px; width: 48px; } + .toolbar-btn, .toolbar-fab { display: block; margin: 0 16px; } + .toolbar-btn:not(#conflict-btn):hover, .toolbar-fab:hover { background-color: var(--button-hover-bg-color); box-shadow: var(--button-hover-shadow); } + .toolbar-btn:not(#conflict-btn)::after, .toolbar-fab::after { opacity: var(--icon-opacity-inactive) } + .toolbar-btn:not(#conflict-btn):hover::after, .toolbar-fab:hover::after { opacity: var(--icon-opacity-active) } + + #menu-btn:hover { background-color: var(--menu-button-hover-bg-color) !important; } + #search-btn { position: absolute; top: 0; right: 0; } + #add-btn { position: absolute; top: 48px; right: 0; } + #conflict-btn { height: 42px; } + #conflict-btn.show { display: block; } +} + +@media (hover:none), (max-width:600px) { + .notes-column { padding: 0 16px 16px; } + #toolbar { bottom: 0; left: 0; right: 0; width: 100%; box-shadow: var(--toolbar-shadow-mobile); padding: 0 4px; background: var(--bg-color); transition: bottom .25s; /* reentrance speed */ } + .toolbar-btn { display: inline-block; } + .toolbar-fab { position: absolute; display: block; right: 16px; top: -28px; width: 56px; height: 56px; box-shadow: var(--fab-shadow); background: var(--fab-bg); transition: top .25s; } + #conflict-btn { width: 36px; } + #conflict-btn.show { display: inline-block; } + + #toolbar.hide { bottom: var(--toolbar-hide-distance-mobile); transition: bottom .2s; /* exit speed */ } + #toolbar.hide .toolbar-fab { top: calc(var(--toolbar-hide-distance-mobile) - 16px); transition: top .2s; } + + @media (orientation:portrait) { + .toolbar-btn { margin: 4px 0; } + } + + @media (orientation:landscape) { + .toolbar-btn { margin: 0; } + } +} + +@media (hover:hover) and (max-width:600px) { + .toolbar-btn:not(#conflict-btn)::before { content: ""; display: block; position: absolute; top: 50%; left: 50%; height: 40px; width: 40px; border-radius: 20px; transform: translate(-50%,-50%); transition: background-color .2s; } + .toolbar-btn:not(#conflict-btn):hover::before { background-color: var(--toolbar-button-hover-bg-mobile); } + #menu-btn:hover::before { background-color: var(--menu-button-hover-bg-color) !important; } + .toolbar-fab { transition: top .25s, box-shadow .2s; } + #toolbar.hide .toolbar-fab { transition: top .2s, box-shadow .2s; } + .toolbar-fab:hover { box-shadow: var(--fab-shadow-hover); } + #add-btn::after { transition: opacity .15s, filter .15s, transform .2s; } + #add-btn:hover::after { filter: var(--icon-add-hover-filter); transform: translate(-50%,-50%) scale(1.25); } +} + +.logo { + font-weight: 500; + word-spacing: -.15em; +} +.logo-icon { + vertical-align: -.45em; + height: 1.6em; +} + +#sign-in-dialog { + margin: auto; + width: auto; + max-width: min(5.5in, 100vw - 32px); + outline: none; + border: none; + border-radius: 24px; + box-shadow: 0 2px 16px rgba(0,0,0,.05), 0 2px 4px rgba(0,0,0,.1); + transition: box-shadow .2s; +} +#sign-in-dialog::after { + content: ""; + position: absolute; + inset: 0; + box-shadow: 0 0 0 1px rgba(255,255,255,.2) inset; + border-radius: 24px; + pointer-events: none; + transition: box-shadow .2s; +} +#sign-in-dialog::backdrop { + background: var(--page-bg-color); +} +#sign-in-form { + display: block; + padding: 16px 16px 40px; + + & .logo { + margin: 16px 0; + padding: .1em .3em .1em 0; + font-size: min(48px, 12vw); + text-align: center; + } + + & .error-message { + text-align: center; + color: var(--error-color); + } + + & input[type=text], + & input[type=password], + & label, + & button { + display: block; + width: 100%; + max-width: 3.5in; + margin: 12px auto; + font-size: inherit; + letter-spacing: .3px; + color: var(--text-color); + border-radius: 16px; + } + + & input[type=text], + & input[type=password], + & button { + padding: 12px 18px; + outline: 0; + } + & input[type=text], + & input[type=password] { + position: relative; + background-color: #FFF; + border: 1px solid #FFF; + border-bottom: 1px solid rgba(0,0,0,.15); + box-shadow: 0 2px 1px -1px rgba(0,0,0,.1); + transition: border .2s, box-shadow .2s, opacity .2s; + } + & input[type=text]:hover:not(:disabled), + & input[type=password]:hover:not(:disabled), + & input[type=text]:focus, + & input[type=password]:focus { + border: 1px solid rgba(0,0,0,.07); + border-bottom: 1px solid rgba(0,0,0,.18); + box-shadow: 0 2px 16px rgba(0,0,0,.05), 0 2px 4px rgba(0,0,0,.15); + } + & input[type=text]::placeholder, + & input[type=password]::placeholder { + color: rgba(0,0,0,.6); + } + & input[type=text]:autofill, + & input[type=password]:autofill { + background-color: rgb(232,240,254); + border: 1px solid rgb(179,207,255); + } + & input[type=text].error, + & input[type=password].error { + background-color: rgb(254,232,232); + border: 1px solid rgb(255,191,191); + } + & input[type=text].error:focus, + & input[type=password].error:focus { + box-shadow: 0 2px 16px rgba(0,0,0,.05), 0 2px 4px rgba(0,0,0,.15), 0 0 1px 2px rgb(255,191,191) inset; + } + + & label { + width: fit-content; + padding: 8px 12px 8px 8px; + text-align: center; + cursor: pointer; + transition: background-color .2s, opacity .2s; + } + & label:focus-within:has(:focus-visible) { + background-color: rgb(232,240,254); + text-align: center; + cursor: pointer; + } + & input[type=checkbox] { + display: inline-block; + height: 0; + width: 0; + opacity: 0; + } + .checkbox-icon { + display: inline-block; + height: 24px; + width: 24px; + vertical-align: -.36em; + background-image: url("icons.svg"); + background-position: -24px -48px; + filter: var(--icon-inactive-filter); + opacity: var(--icon-opacity-inactive); + } + input[type=checkbox]:checked + .checkbox-icon { + background-position: 0 -48px; + filter: var(--icon-checkbox-active-filter); + opacity: var(--icon-opacity-active); + } + + & button { + position: relative; + font-weight: 500; + background-color: rgba(0,0,0,.06); + border: none; + cursor: pointer; + transition: background-color .2s, box-shadow .2s; + } + & button:hover, + & button:focus, + &.loading button { + background-color: #FBBC04; + } + & button:focus-visible { + box-shadow: 0 0 0 4px rgba(251,188,4,.4); + } + & input + button { + margin-top: 32px; + } + &.loading button { + color: transparent; + } + + & button .loading-spinner { + inset: 50%; + transform: translate(-50%,calc(-50% - 1em)); + display: none; + } + &.loading button .loading-spinner { + display: inline-block; + } + & .loading-spinner, + & .loading-spinner::before, + & .loading-spinner::after { + position: absolute; + border-radius: 50%; + width: .7em; + height: .7em; + animation-fill-mode: both; + animation: loading-spinner 1.8s infinite ease-in-out; + } + & .loading-spinner { + animation-delay: -0.16s; + } + & .loading-spinner::before, + & .loading-spinner::after { + content: ''; + top: 0; + } + & .loading-spinner::before { + left: -1.3em; + animation-delay: -0.32s; + } + & .loading-spinner::after { + left: 1.3em; + } + + &.loading input, + &.loading label { + opacity: .5; + } +} +@keyframes loading-spinner { + 0%, 80%, 100% { box-shadow: 0 1em 0 -1.3em #fff; } + 40% { box-shadow: 0 1em 0 0 #fff; } +} + +.notes-column { + overflow-y: auto; +} + +/* TODO :target? */ +.card-shadow { + position: relative; + margin: 16px auto; + max-width: 5.5in; + border-radius: 24px; + box-shadow: 0 2px 16px rgba(0,0,0,.05), 0 2px 4px rgba(0,0,0,.1); + transition: box-shadow .2s; +} +.card-shadow:hover, +.card-shadow:focus-within { + box-shadow: 0 2px 16px rgba(0,0,0,.15), 0 2px 4px rgba(0,0,0,.15); +} +.card-shadow::after { + content: ""; + position: absolute; + inset: 0; + box-shadow: 0 0 0 1px rgba(255,255,255,.2) inset; + border-radius: 24px; + pointer-events: none; + transition: box-shadow .2s; +} +.card-shadow:hover::after, +.card-shadow:focus-within::after { + box-shadow: 0 0 0 2px rgba(255,255,255,.25) inset; +} +.card { + /*padding-bottom: 16px;*/ + border-radius: 24px; + background-color: var(--bg-color); + color: var(--text-color); + overflow: hidden; +} + +.card > textarea { /* TODO */ + display: block; + width: 100%; + padding: 16px 24px; + border: none; + resize: none; + outline: none; + font-size: 16px; + line-height: 24px; + letter-spacing: .3px; + background: transparent; + color: inherit; +} diff --git a/design/cards.html b/design/cards.html index d6d2a7c..d2f3ad4 100644 --- a/design/cards.html +++ b/design/cards.html @@ -111,6 +111,37 @@ .card:has(img:last-child) { padding-bottom: 0; } + + code { + margin: 0 -2px; + padding: 2px 4px; + font: 14px/1.3em "Hack", monospace; + white-space: nowrap; + background: rgba(0,0,0,.05); + border-radius: 4px; + letter-spacing: 0; + } + .gfm-color_chip { + display: inline-block; + line-height: 1; + margin: 0 0 2px 4px; + vertical-align: middle; + border-radius: 3px; + width: 0.9em; + height: 0.9em; + background: #fff; + background-image: linear-gradient(135deg, #ded6ee 25%, transparent 0%, transparent 75%, #ded6ee 0%),linear-gradient(135deg, #ded6ee 25%, transparent 0%, transparent 75%, #ded6ee 0%); + background-size: 1em 1em; + background-position: 0 0, 0.5em 0.5em; + } + .gfm-color_chip>span { + display: inline-block; + width: 100%; + height: 100%; + margin-bottom: 2px; + border-radius: 3px; + border: 1px solid rgba(31,30,36,0.24); + } @@ -122,12 +153,12 @@

Example Note

Meh bold italic meh whatever lorem ipsum dolor sit amet adispicing elit

-

Foobar lipsum yeet

-

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.

+

Foobar lipsum yeet uwu

+

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Call the load() function. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.

function test() {
   return 42;
 }
-

It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

+

It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. RGBA(0,255,0,0.3)

#hash #tag more_vert
diff --git a/design/icons.svg b/design/icons.svg index 563df7f..1526d57 100644 --- a/design/icons.svg +++ b/design/icons.svg @@ -1,19 +1,19 @@ + inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:pagecheckerboard="true" + inkscape:showpageshadow="2" + inkscape:deskcolor="#d1d1d1" + inkscape:window-width="1368" + inkscape:window-height="890" + inkscape:window-x="-6" + inkscape:window-y="-6" + inkscape:window-maximized="1" /> @@ -46,7 +53,6 @@ image/svg+xml - @@ -72,7 +78,7 @@ style="stroke-width:4" /> @@ -89,7 +95,7 @@ d="M 15.824121,13.675879 14.71,14 14.43,13.73 c 1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.79,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48 l 0.27,0.28 -0.324121,1.114121 4.905161,4.905839 c 0.410071,0.410128 1.224774,0.265226 1.81196,-0.32196 0.587186,-0.587186 0.73196,-1.40196 0.32196,-1.81196 z M 9.5,12.989033 c -1.9305983,0 -3.489033,-1.558435 -3.489033,-3.489033 0,-1.9305983 1.5584347,-3.489033 3.489033,-3.489033 1.930598,0 3.489033,1.5584347 3.489033,3.489033 0,1.930598 -1.558435,3.489033 -3.489033,3.489033 z" id="path971" sodipodi:nodetypes="ccccccccccssccsssss" - style="fill:#1565c0;stroke-width:2;fill-opacity:1" /> + style="fill:#1565c0;fill-opacity:1;stroke-width:2" /> @@ -187,7 +193,35 @@ + + + + + + + + diff --git a/design/logo-icon.svg b/design/logo-icon.svg new file mode 100644 index 0000000..d66678f --- /dev/null +++ b/design/logo-icon.svg @@ -0,0 +1,96 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + -- cgit v1.2.3-54-g00ecf