initial-commit

This commit is contained in:
nicwands
2026-02-13 17:56:58 -05:00
commit a4fb3f2a87
60 changed files with 16775 additions and 0 deletions

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

36
.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.eslintcache
# Cypress
/cypress/videos/
/cypress/screenshots/
# Vitest
__screenshots__/

2
.npmrc Normal file
View File

@@ -0,0 +1,2 @@
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
22

6
.prettierignore Normal file
View File

@@ -0,0 +1,6 @@
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json

3
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint"]
}

34
README.md Normal file
View File

@@ -0,0 +1,34 @@
# app.takerofnotes.com
An Electron application with Vue
## Recommended IDE Setup
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
## Project Setup
### Install
```bash
$ npm install
```
### Development
```bash
$ npm run dev
```
### Build
```bash
# For windows
$ npm run build:win
# For macOS
$ npm run build:mac
# For Linux
$ npm run build:linux
```

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>

BIN
build/icon.icns Normal file

Binary file not shown.

BIN
build/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
build/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

3
dev-app-update.yml Normal file
View File

@@ -0,0 +1,3 @@
provider: generic
url: https://example.com/auto-updates
updaterCacheDirName: app.takerofnotes.com-updater

44
electron-builder.yml Normal file
View File

@@ -0,0 +1,44 @@
appId: com.electron.app
productName: app.takerofnotes.com
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintcache,eslint.config.mjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
asarUnpack:
- resources/**
win:
executableName: app.takerofnotes.com
nsis:
artifactName: ${name}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
notarize: false
dmg:
artifactName: ${name}-${version}.${ext}
linux:
target:
- AppImage
- snap
- deb
maintainer: electronjs.org
category: Utility
appImage:
artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
provider: generic
url: https://example.com/auto-updates
electronDownload:
mirror: https://npmmirror.com/mirrors/electron/

58
electron.vite.config.mjs Normal file
View File

@@ -0,0 +1,58 @@
import { resolve } from 'path'
import { defineConfig } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { toSass } from './src/renderer/src/libs/sass-utils'
import theme from './src/renderer/src/libs/theme'
export default defineConfig({
main: {},
preload: {},
renderer: {
plugins: [
vue(),
Components({
dirs: ['src/renderer/src/components'],
extensions: ['vue'],
deep: true,
dts: 'src/renderer/src/components.d.ts',
directoryAsNamespace: true,
}),
],
resolve: {
alias: {
'@': resolve('src/renderer/src'),
},
},
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler',
additionalData:
'@use "/src/styles/_functions.scss" as *; @use "/src/styles/_font-style.scss" as *;',
functions: {
'get($keys)': function (keys) {
keys = keys
.toString()
.replace(/['"]+/g, '')
.split('.')
let result = theme
for (let i = 0; i < keys.length; i++) {
result = result[keys[i]]
}
return toSass(result)
},
'getColors()': function () {
return toSass(theme.colors)
},
'getThemes()': function () {
return toSass(theme.themes)
},
},
},
},
},
},
})

46
out/main/index.js Normal file
View File

@@ -0,0 +1,46 @@
"use strict";
const electron = require("electron");
const path = require("path");
const utils = require("@electron-toolkit/utils");
const icon = path.join(__dirname, "../../resources/icon.png");
function createWindow() {
const mainWindow = new electron.BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
...process.platform === "linux" ? { icon } : {},
webPreferences: {
preload: path.join(__dirname, "../preload/index.js"),
sandbox: false
}
});
mainWindow.on("ready-to-show", () => {
mainWindow.show();
});
mainWindow.webContents.setWindowOpenHandler((details) => {
electron.shell.openExternal(details.url);
return { action: "deny" };
});
if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
} else {
mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
}
}
electron.app.whenReady().then(() => {
utils.electronApp.setAppUserModelId("com.electron");
electron.app.on("browser-window-created", (_, window) => {
utils.optimizer.watchWindowShortcuts(window);
});
electron.ipcMain.on("ping", () => console.log("pong"));
createWindow();
electron.app.on("activate", function() {
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
electron.app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
electron.app.quit();
}
});

15
out/preload/index.js Normal file
View File

@@ -0,0 +1,15 @@
"use strict";
const electron = require("electron");
const preload = require("@electron-toolkit/preload");
const api = {};
if (process.contextIsolated) {
try {
electron.contextBridge.exposeInMainWorld("electron", preload.electronAPI);
electron.contextBridge.exposeInMainWorld("api", api);
} catch (error) {
console.error(error);
}
} else {
window.electron = preload.electronAPI;
window.api = api;
}

View File

@@ -0,0 +1,10 @@
<svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="64" cy="64" r="64" fill="#2F3242"/>
<ellipse cx="63.9835" cy="23.2036" rx="4.48794" ry="4.495" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path d="M51.3954 39.5028C52.3733 39.6812 53.3108 39.033 53.4892 38.055C53.6676 37.0771 53.0194 36.1396 52.0414 35.9612L51.3954 39.5028ZM28.6153 43.5751L30.1748 44.4741L30.1748 44.4741L28.6153 43.5751ZM28.9393 60.9358C29.4332 61.7985 30.5329 62.0976 31.3957 61.6037C32.2585 61.1098 32.5575 60.0101 32.0636 59.1473L28.9393 60.9358ZM37.6935 66.7457C37.025 66.01 35.8866 65.9554 35.1508 66.6239C34.415 67.2924 34.3605 68.4308 35.029 69.1666L37.6935 66.7457ZM53.7489 81.7014L52.8478 83.2597L53.7489 81.7014ZM96.9206 89.515C97.7416 88.9544 97.9526 87.8344 97.3919 87.0135C96.8313 86.1925 95.7113 85.9815 94.8904 86.5422L96.9206 89.515ZM52.0414 35.9612C46.4712 34.9451 41.2848 34.8966 36.9738 35.9376C32.6548 36.9806 29.0841 39.1576 27.0559 42.6762L30.1748 44.4741C31.5693 42.0549 34.1448 40.3243 37.8188 39.4371C41.5009 38.5479 46.1547 38.5468 51.3954 39.5028L52.0414 35.9612ZM27.0559 42.6762C24.043 47.9029 25.2781 54.5399 28.9393 60.9358L32.0636 59.1473C28.6579 53.1977 28.1088 48.0581 30.1748 44.4741L27.0559 42.6762ZM35.029 69.1666C39.6385 74.24 45.7158 79.1355 52.8478 83.2597L54.6499 80.1432C47.8081 76.1868 42.0298 71.5185 37.6935 66.7457L35.029 69.1666ZM52.8478 83.2597C61.344 88.1726 70.0465 91.2445 77.7351 92.3608C85.359 93.4677 92.2744 92.6881 96.9206 89.515L94.8904 86.5422C91.3255 88.9767 85.4902 89.849 78.2524 88.7982C71.0793 87.7567 62.809 84.8612 54.6499 80.1432L52.8478 83.2597ZM105.359 84.9077C105.359 81.4337 102.546 78.6127 99.071 78.6127V82.2127C100.553 82.2127 101.759 83.4166 101.759 84.9077H105.359ZM99.071 78.6127C95.5956 78.6127 92.7831 81.4337 92.7831 84.9077H96.3831C96.3831 83.4166 97.5892 82.2127 99.071 82.2127V78.6127ZM92.7831 84.9077C92.7831 88.3817 95.5956 91.2027 99.071 91.2027V87.6027C97.5892 87.6027 96.3831 86.3988 96.3831 84.9077H92.7831ZM99.071 91.2027C102.546 91.2027 105.359 88.3817 105.359 84.9077H101.759C101.759 86.3988 100.553 87.6027 99.071 87.6027V91.2027Z" fill="#A2ECFB"/>
<path d="M91.4873 65.382C90.8456 66.1412 90.9409 67.2769 91.7002 67.9186C92.4594 68.5603 93.5951 68.465 94.2368 67.7058L91.4873 65.382ZM99.3169 43.6354L97.7574 44.5344L99.3169 43.6354ZM84.507 35.2412C83.513 35.2282 82.6967 36.0236 82.6838 37.0176C82.6708 38.0116 83.4661 38.8279 84.4602 38.8409L84.507 35.2412ZM74.9407 39.8801C75.9127 39.6716 76.5315 38.7145 76.323 37.7425C76.1144 36.7706 75.1573 36.1517 74.1854 36.3603L74.9407 39.8801ZM53.7836 46.3728L54.6847 47.931L53.7836 46.3728ZM25.5491 80.9047C25.6932 81.8883 26.6074 82.5688 27.5911 82.4247C28.5747 82.2806 29.2552 81.3664 29.1111 80.3828L25.5491 80.9047ZM94.2368 67.7058C97.8838 63.3907 100.505 58.927 101.752 54.678C103.001 50.4213 102.9 46.2472 100.876 42.7365L97.7574 44.5344C99.1494 46.9491 99.3603 50.0419 98.2974 53.6644C97.2323 57.2945 94.9184 61.3223 91.4873 65.382L94.2368 67.7058ZM100.876 42.7365C97.9119 37.5938 91.7082 35.335 84.507 35.2412L84.4602 38.8409C91.1328 38.9278 95.7262 41.0106 97.7574 44.5344L100.876 42.7365ZM74.1854 36.3603C67.4362 37.8086 60.0878 40.648 52.8826 44.8146L54.6847 47.931C61.5972 43.9338 68.5948 41.2419 74.9407 39.8801L74.1854 36.3603ZM52.8826 44.8146C44.1366 49.872 36.9669 56.0954 32.1491 62.3927C27.3774 68.63 24.7148 75.2115 25.5491 80.9047L29.1111 80.3828C28.4839 76.1026 30.4747 70.5062 35.0084 64.5802C39.496 58.7143 46.2839 52.7889 54.6847 47.931L52.8826 44.8146Z" fill="#A2ECFB"/>
<path d="M49.0825 87.2295C48.7478 86.2934 47.7176 85.8059 46.7816 86.1406C45.8455 86.4753 45.358 87.5055 45.6927 88.4416L49.0825 87.2295ZM78.5635 96.4256C79.075 95.5732 78.7988 94.4675 77.9464 93.9559C77.0941 93.4443 75.9884 93.7205 75.4768 94.5729L78.5635 96.4256ZM79.5703 85.1795C79.2738 86.1284 79.8027 87.1379 80.7516 87.4344C81.7004 87.7308 82.71 87.2019 83.0064 86.2531L79.5703 85.1795ZM84.3832 64.0673H82.5832H84.3832ZM69.156 22.5301C68.2477 22.1261 67.1838 22.535 66.7799 23.4433C66.3759 24.3517 66.7848 25.4155 67.6931 25.8194L69.156 22.5301ZM45.6927 88.4416C47.5994 93.7741 50.1496 98.2905 53.2032 101.505C56.2623 104.724 59.9279 106.731 63.9835 106.731V103.131C61.1984 103.131 58.4165 101.765 55.8131 99.0249C53.2042 96.279 50.8768 92.2477 49.0825 87.2295L45.6927 88.4416ZM63.9835 106.731C69.8694 106.731 74.8921 102.542 78.5635 96.4256L75.4768 94.5729C72.0781 100.235 68.0122 103.131 63.9835 103.131V106.731ZM83.0064 86.2531C85.0269 79.7864 86.1832 72.1831 86.1832 64.0673H82.5832C82.5832 71.8536 81.4723 79.0919 79.5703 85.1795L83.0064 86.2531ZM86.1832 64.0673C86.1832 54.1144 84.4439 44.922 81.4961 37.6502C78.5748 30.4436 74.3436 24.8371 69.156 22.5301L67.6931 25.8194C71.6364 27.5731 75.3846 32.1564 78.1598 39.0026C80.9086 45.7836 82.5832 54.507 82.5832 64.0673H86.1832Z" fill="#A2ECFB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M103.559 84.9077C103.559 82.4252 101.55 80.4127 99.071 80.4127C96.5924 80.4127 94.5831 82.4252 94.5831 84.9077C94.5831 87.3902 96.5924 89.4027 99.071 89.4027C101.55 89.4027 103.559 87.3902 103.559 84.9077V84.9077Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.8143 89.4027C31.2929 89.4027 33.3023 87.3902 33.3023 84.9077C33.3023 82.4252 31.2929 80.4127 28.8143 80.4127C26.3357 80.4127 24.3264 82.4252 24.3264 84.9077C24.3264 87.3902 26.3357 89.4027 28.8143 89.4027V89.4027V89.4027Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M64.8501 68.0857C62.6341 68.5652 60.451 67.1547 59.9713 64.9353C59.4934 62.7159 60.9007 60.5293 63.1167 60.0489C65.3326 59.5693 67.5157 60.9798 67.9954 63.1992C68.4742 65.4186 67.066 67.6052 64.8501 68.0857Z" fill="#A2ECFB"/>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,229 @@
:root {
--ev-c-white: #ffffff;
--ev-c-white-soft: #f8f8f8;
--ev-c-white-mute: #f2f2f2;
--ev-c-black: #1b1b1f;
--ev-c-black-soft: #222222;
--ev-c-black-mute: #282828;
--ev-c-gray-1: #515c67;
--ev-c-gray-2: #414853;
--ev-c-gray-3: #32363f;
--ev-c-text-1: rgba(255, 255, 245, 0.86);
--ev-c-text-2: rgba(235, 235, 245, 0.6);
--ev-c-text-3: rgba(235, 235, 245, 0.38);
--ev-button-alt-border: transparent;
--ev-button-alt-text: var(--ev-c-text-1);
--ev-button-alt-bg: var(--ev-c-gray-3);
--ev-button-alt-hover-border: transparent;
--ev-button-alt-hover-text: var(--ev-c-text-1);
--ev-button-alt-hover-bg: var(--ev-c-gray-2);
}
:root {
--color-background: var(--ev-c-black);
--color-background-soft: var(--ev-c-black-soft);
--color-background-mute: var(--ev-c-black-mute);
--color-text: var(--ev-c-text-1);
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
ul {
list-style: none;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background-image: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201422%20800'%20opacity='0.3'%3e%3cdefs%3e%3clinearGradient%20x1='50%25'%20y1='0%25'%20x2='50%25'%20y2='100%25'%20id='oooscillate-grad'%3e%3cstop%20stop-color='hsl(206,%2075%25,%2049%25)'%20stop-opacity='1'%20offset='0%25'%3e%3c/stop%3e%3cstop%20stop-color='hsl(331,%2090%25,%2056%25)'%20stop-opacity='1'%20offset='100%25'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3cg%20stroke-width='1'%20stroke='url(%23oooscillate-grad)'%20fill='none'%20stroke-linecap='round'%3e%3cpath%20d='M%200%20448%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20448'%20opacity='0.05'%3e%3c/path%3e%3cpath%20d='M%200%20420%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20420'%20opacity='0.11'%3e%3c/path%3e%3cpath%20d='M%200%20392%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20392'%20opacity='0.18'%3e%3c/path%3e%3cpath%20d='M%200%20364%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20364'%20opacity='0.24'%3e%3c/path%3e%3cpath%20d='M%200%20336%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20336'%20opacity='0.30'%3e%3c/path%3e%3cpath%20d='M%200%20308%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20308'%20opacity='0.37'%3e%3c/path%3e%3cpath%20d='M%200%20280%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20280'%20opacity='0.43'%3e%3c/path%3e%3cpath%20d='M%200%20252%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20252'%20opacity='0.49'%3e%3c/path%3e%3cpath%20d='M%200%20224%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20224'%20opacity='0.56'%3e%3c/path%3e%3cpath%20d='M%200%20196%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20196'%20opacity='0.62'%3e%3c/path%3e%3cpath%20d='M%200%20168%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20168'%20opacity='0.68'%3e%3c/path%3e%3cpath%20d='M%200%20140%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20140'%20opacity='0.75'%3e%3c/path%3e%3cpath%20d='M%200%20112%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20112'%20opacity='0.81'%3e%3c/path%3e%3cpath%20d='M%200%2084%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%2084'%20opacity='0.87'%3e%3c/path%3e%3cpath%20d='M%200%2056%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%2056'%20opacity='0.94'%3e%3c/path%3e%3c/g%3e%3c/svg%3e");
background-size: cover;
user-select: none;
}
code {
font-weight: 600;
padding: 3px 5px;
border-radius: 2px;
background-color: var(--color-background-mute);
font-family:
ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
Liberation Mono,
monospace;
font-size: 85%;
}
#app {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-bottom: 80px;
}
.logo {
margin-bottom: 20px;
-webkit-user-drag: none;
height: 128px;
width: 128px;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 1.2em #6988e6aa);
}
.creator {
font-size: 14px;
line-height: 16px;
color: var(--ev-c-text-2);
font-weight: 600;
margin-bottom: 10px;
}
.text {
font-size: 28px;
color: var(--ev-c-text-1);
font-weight: 700;
line-height: 32px;
text-align: center;
margin: 0 10px;
padding: 16px 0;
}
.tip {
font-size: 16px;
line-height: 24px;
color: var(--ev-c-text-2);
font-weight: 600;
}
.vue {
background: -webkit-linear-gradient(315deg, #42d392 25%, #647eff);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
}
.actions {
display: flex;
padding-top: 32px;
margin: -6px;
flex-wrap: wrap;
justify-content: flex-start;
}
.action {
flex-shrink: 0;
padding: 6px;
}
.action a {
cursor: pointer;
text-decoration: none;
display: inline-block;
border: 1px solid transparent;
text-align: center;
font-weight: 600;
white-space: nowrap;
border-radius: 20px;
padding: 0 20px;
line-height: 38px;
font-size: 14px;
border-color: var(--ev-button-alt-border);
color: var(--ev-button-alt-text);
background-color: var(--ev-button-alt-bg);
}
.action a:hover {
border-color: var(--ev-button-alt-hover-border);
color: var(--ev-button-alt-hover-text);
background-color: var(--ev-button-alt-hover-bg);
}
.versions {
position: absolute;
bottom: 30px;
margin: 0 auto;
padding: 15px 0;
font-family: 'Menlo', 'Lucida Console', monospace;
display: inline-flex;
overflow: hidden;
align-items: center;
border-radius: 22px;
background-color: #202127;
backdrop-filter: blur(24px);
}
.versions li {
display: block;
float: left;
border-right: 1px solid var(--ev-c-gray-1);
padding: 0 20px;
font-size: 14px;
line-height: 14px;
opacity: 0.8;
&:last-child {
border: none;
}
}
@media (max-width: 720px) {
.text {
font-size: 20px;
}
}
@media (max-width: 620px) {
.versions {
display: none;
}
}
@media (max-width: 350px) {
.tip,
.actions {
display: none;
}
}

File diff suppressed because it is too large Load Diff

18
out/renderer/index.html Normal file
View File

@@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Electron</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
/>
<script type="module" crossorigin src="./assets/index-C32vEYfn.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-BVu-w-qc.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

8335
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

46
package.json Normal file
View File

@@ -0,0 +1,46 @@
{
"name": "app.takerofnotes.com",
"version": "1.0.0",
"description": "An Electron application with Vue",
"main": "./out/main/index.js",
"author": "example.com",
"homepage": "https://electron-vite.org",
"prettier": {
"tabWidth": 4,
"semi": false,
"singleQuote": true
},
"scripts": {
"format": "prettier --write .",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win",
"build:mac": "npm run build && electron-builder --mac",
"build:linux": "npm run build && electron-builder --linux"
},
"dependencies": {
"@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0",
"@fuzzco/font-loader": "^1.0.2",
"@vueuse/core": "^14.2.1",
"electron-updater": "^6.3.9",
"gsap": "^3.14.2",
"lenis": "^1.3.17",
"sass": "^1.97.3",
"sass-embedded": "^1.97.3",
"tempus": "^1.0.0-dev.17"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.2",
"electron": "^39.2.6",
"electron-builder": "^26.0.12",
"electron-vite": "^5.0.0",
"prettier": "^3.7.4",
"unplugin-vue-components": "^31.0.0",
"vite": "^7.2.6",
"vue": "^3.5.25"
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

26
resources/license.txt Normal file
View File

@@ -0,0 +1,26 @@
# DON'T BE A DICK PUBLIC LICENSE
> Version 1.1, December 2016
> Copyright (C) [year] [fullname]
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document.
> DON'T BE A DICK PUBLIC LICENSE
> TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
1. Do whatever you like with the original work, just don't be a dick.
Being a dick includes - but is not limited to - the following instances:
1a. Outright copyright infringement - Don't just copy this and change the name.
1b. Selling the unmodified original with no work done what-so-ever, that's REALLY being a dick.
1c. Modifying the original work to contain hidden harmful content. That would make you a PROPER dick.
2. If you become rich through modifications, related works/services, or supporting the original work,
share the love. Only a dick would make loads off this work and not buy the original work's
creator(s) a pint.
3. Code is provided with no warranty. Using somebody else's code and bitching when it goes wrong makes
you a DONKEY dick. Fix the problem yourself. A non-dick would submit the fix back.

74
src/main/index.js Normal file
View File

@@ -0,0 +1,74 @@
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// IPC test
ipcMain.on('ping', () => console.log('pong'))
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

20
src/preload/index.js Normal file
View File

@@ -0,0 +1,20 @@
import { contextBridge } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer
const api = {}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}

17
src/renderer/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Electron</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
/>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

65
src/renderer/src/App.vue Normal file
View File

@@ -0,0 +1,65 @@
<template>
<lenis root :options="{ duration: 1 }">
<div :class="classes" :style="styles">
<main class="layout-block">
<h1>Taker of Notes</h1>
<p>Lorem ipsum</p>
</main>
</div>
</lenis>
</template>
<script setup>
import Lenis from './components/Lenis.vue'
import { ref, computed, onMounted } from 'vue'
import loadFonts from '@fuzzco/font-loader'
import { useWindowSize } from '@vueuse/core'
const { height } = useWindowSize()
const classes = computed(() => [
'container',
{ 'fonts-ready': !fontsLoading.value },
'theme-dark',
])
const fontsLoading = ref(true)
onMounted(async () => {
// Load fonts
loadFonts([
{
name: 'Leibniz Fraktur',
weights: [400],
},
{
name: 'Geist Mono',
weights: [400, 700],
},
])
.then(() => {
fontsLoading.value = false
})
.catch(() => {
fontsLoading.value = false
})
})
const styles = computed(() => ({
'--vh': height.value ? height.value / 100 + 'px' : '100vh',
}))
</script>
<style lang="scss">
.container {
min-height: calc(100 * var(--vh));
max-width: 100vw;
overflow-x: clip;
background: var(--theme-bg);
color: var(--theme-fg);
transition: opacity 1000ms;
&:not(.fonts-ready) {
opacity: 0;
}
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div v-if="!root" class="lenis" ref="wrapper">
<div ref="content">
<slot />
</div>
</div>
<slot v-else />
</template>
<script setup>
import Lenis from 'lenis'
import Tempus from 'tempus'
import { onBeforeUnmount, onMounted, provide, ref, shallowRef } from 'vue'
const { root, instance, options } = defineProps({
root: {
type: Boolean,
default: () => false,
},
instance: { type: String },
options: {
type: Object,
default: () => ({
duration: 1.2,
}),
},
})
const lenis = shallowRef()
const wrapper = ref()
const content = ref()
const removeRaf = ref()
// Provide instance for useLenis composable
const instanceKey = `lenis${instance ? `-${instance}` : ''}`
provide(instanceKey, lenis)
// Initialize with Tempus
const initLenis = () => {
if (lenis.value) return
lenis.value = new Lenis({
...options,
...(!root
? {
wrapper: wrapper.value,
content: content.value,
eventsTarget: wrapper.value,
}
: {}),
})
removeRaf.value = Tempus.add((time) => {
lenis.value.raf(time)
})
}
onMounted(() => {
if (!lenis.value) {
initLenis()
}
})
// Kill lenis before unmount
onBeforeUnmount(() => {
lenis.value?.destroy()
removeRaf.value?.()
})
</script>

View File

@@ -0,0 +1,59 @@
import { gsap } from 'gsap'
import { onMounted, onBeforeUnmount, watch, unref } from 'vue'
/**
* Infinite up/down floating animation for an element.
*
* @param {Ref|HTMLElement} target - element or ref to animate
* @param {Object} opts
* @param {number} opts.y - distance to move up/down
* @param {number} opts.duration - duration of one cycle (up or down)
* @param {number} opts.delay - initial delay
* @param {string} opts.ease - GSAP easing
*/
export default (
target,
{ y = '-3%', duration = 2, delay = 0, ease = 'power1.inOut' } = {},
) => {
let tl = null
const start = () => {
const el = unref(target)
if (!el) return
// Kill any previous timeline
if (tl) tl.kill()
// Create infinite yoyo animation
tl = gsap.to(el, {
y: y,
duration,
delay,
ease,
yoyo: true,
repeat: -1,
})
}
const stop = () => {
if (tl) {
tl.kill()
tl = null
}
}
onMounted(() => {
// If the element is a ref, wait for it to appear
watch(
() => unref(target),
(el) => {
if (el) start()
},
{ immediate: true },
)
})
onBeforeUnmount(stop)
return { start, stop }
}

View File

@@ -0,0 +1,14 @@
import { inject, onBeforeUnmount } from 'vue'
export default (callback = () => {}, instanceId) => {
const instanceKey = `lenis${instanceId ? `-${instanceId}` : ''}`
const lenis = inject(instanceKey)
if (lenis.value) {
lenis.value.on('scroll', callback)
}
onBeforeUnmount(() => lenis.value?.off('scroll', callback))
return lenis
}

View File

@@ -0,0 +1,29 @@
import { useWindowSize } from '@vueuse/core'
import { viewports } from '@/libs/theme'
const { width: wWidth, height: wHeight } = useWindowSize()
export default () => {
// Desktop
const dvw = (pixels) => {
return (pixels / viewports.desktop.width) * wWidth.value
}
const dvh = (pixels) => {
return (pixels / viewports.desktop.height) * wHeight.value
}
// Mobile
const mvw = (pixels) => {
return (pixels / viewports.mobile.width) * wWidth.value
}
const mvh = (pixels) => {
return (pixels / viewports.mobile.height) * wHeight.value
}
return {
dvw,
dvh,
mvw,
mvh,
}
}

View File

@@ -0,0 +1,46 @@
import {
useElementBounding,
useIntersectionObserver,
useWindowSize,
} from '@vueuse/core'
import { ref } from 'vue'
import { mapRange, clamp } from '@/libs/math'
import useLenis from '@/composables/useLenis'
const { height: wHeight } = useWindowSize()
export const useScrollProgress = (el, callback, entry = 0.5, exit = 0.5) => {
const isActive = ref(true)
const smoothProgress = ref(0)
const { height, top } = useElementBounding(el)
const isIntersected = ref(false)
const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {
isIntersected.value = isIntersecting
})
useLenis(({ scroll }) => {
if (!isActive.value) return
if (!height.value || !wHeight.value) return
if (!isIntersected.value) return
const pageTop = scroll + top.value
const start = pageTop - wHeight.value * entry
const end = pageTop + height.value - wHeight.value * exit
let rawProgress = mapRange(start, end, scroll, 0, 1)
rawProgress = clamp(0, rawProgress, 1)
smoothProgress.value += (rawProgress - smoothProgress.value) * 0.1
callback?.(smoothProgress.value)
})
const destroy = () => {
isActive.value = false
stop?.()
}
return { destroy }
}

View File

@@ -0,0 +1,89 @@
import gsap from 'gsap'
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { useWindowSize, useEventListener } from '@vueuse/core'
/**
* Shared global raw mouse state (only set up once)
*/
const rawMouse = {
x: ref(0),
y: ref(0),
initialized: false,
cleanup: null,
}
const useGlobalMouseListener = () => {
if (!rawMouse.initialized && !import.meta.env.SSR) {
rawMouse.initialized = true
rawMouse.cleanup = useEventListener(window, 'mousemove', (e) => {
rawMouse.x.value = e.clientX
rawMouse.y.value = e.clientY
})
}
}
/**
* Composable for smoothed mouse position with customizable smoothing and normalization.
*/
export default (options) => {
const smoothFactor = options?.smoothFactor ?? 0.1
const normalize = options?.normalize ?? false
const callback = options?.onUpdate
const { width: wWidth, height: wHeight } = useWindowSize()
const sx = ref(0)
const sy = ref(0)
useGlobalMouseListener()
const getTargetX = () =>
normalize ? rawMouse.x.value / wWidth.value : rawMouse.x.value
const getTargetY = () =>
normalize ? rawMouse.y.value / wHeight.value : rawMouse.y.value
let tween
onMounted(() => {
if (tween) tween.kill
// Start smoothing tween
tween = gsap.to(
{ x: sx.value, y: sy.value },
{
duration: 1,
ease: 'linear',
repeat: -1,
onUpdate: () => {
const newX = gsap.utils.interpolate(
sx.value,
getTargetX(),
smoothFactor,
)
const newY = gsap.utils.interpolate(
sy.value,
getTargetY(),
smoothFactor,
)
sx.value = newX
sy.value = newY
callback?.({ sx: sx.value, sy: sy.value })
},
},
)
})
onBeforeUnmount(() => {
tween?.kill()
rawMouse.cleanup()
rawMouse.initialized = false
})
return {
sx,
sy,
rawX: rawMouse.x,
rawY: rawMouse.y,
}
}

View File

@@ -0,0 +1,34 @@
import { ref } from 'vue'
export default (url) => {
const loading = ref(false)
const success = ref(false)
const error = ref('')
const submit = async (headers = {}, body = {}) => {
loading.value = true
try {
await fetch(url, {
method: 'POST',
headers,
body: new URLSearchParams(body),
})
success.value = true
error.value = ''
} catch (err) {
console.error(err)
error.value = err.toString().split('Error: ')[1]
}
loading.value = false
}
return {
loading,
success,
error,
submit,
}
}

View File

@@ -0,0 +1,37 @@
import gsap from 'gsap'
export default (timelineSetup = () => {}) => {
const timelines = new WeakMap()
const getTimeline = (el) => {
if (!timelines.has(el)) {
const tl = gsap.timeline({ paused: true })
timelines.set(el, tl)
}
return timelines.get(el)
}
const onEnter = (el, done) => {
const tl = getTimeline(el)
tl.clear()
timelineSetup?.(tl, el)
tl.eventCallback('onReverseComplete', null)
tl.eventCallback('onComplete', done)
tl.play()
}
const onLeave = (el, done) => {
el.style.pointerEvents = 'none'
const tl = getTimeline(el)
tl.eventCallback('onComplete', null)
tl.eventCallback('onReverseComplete', done)
tl.reverse()
}
return {
onEnter,
onLeave,
}
}

View File

@@ -0,0 +1,22 @@
export function clamp(min, input, max) {
return Math.max(min, Math.min(input, max))
}
export function mapRange(in_min, in_max, input, out_min, out_max) {
return (
((input - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
)
}
export function lerp(start, end, amt) {
return (1 - amt) * start + amt * end
}
export function truncate(value, decimals) {
return parseFloat(value.toFixed(decimals))
}
export function wrapValue(value, lowBound, highBound) {
const range = highBound - lowBound
return ((((value - lowBound) % range) + range) % range) + lowBound
}

View File

@@ -0,0 +1,220 @@
// https://github.com/dawaltconley/sass-cast/blob/main/index.js
import * as sass from 'sass-embedded'
import { isQuoted, unquoteString, parseString, getAttr } from './utils'
import { List, OrderedMap } from 'immutable'
/**
* Converts any Javascript object to an equivalent Sass value.
*
* This method is recursive and will convert the values of any array or object,
* as well as the array or object itself.
*
* @example
* const { toSass } = require('sass-cast');
*
* const string = toSass('a simple string');
* // quoted SassString => '"a simple string"'
*
* const map = toSass({
* key: 'value',
* nested: {
* 'complex//:key': [ null, 4 ],
* }
* });
* // SassMap => '("key": "value", "nested": ("complex//:key": (null, 4)))'
*
* @param {*} value - the value to be converted
* @param {Object} options
* @param {boolean} [options.parseUnquotedStrings=false] - whether to parse unquoted strings for colors or numbers with units
* @param {boolean|*[]} [options.resolveFunctions=false] - if true, resolve functions and attempt to cast their return values. if an array, pass as arguments when resolving
* @param {boolean} [options.quotes=true] - controls whether returned SassStrings are quoted. input strings that contain quotes will always return a quoted SassString even if this flag is false.
* @return {Value} - a {@link https://sass-lang.com/documentation/js-api/classes/Value Sass value}
*/
export const toSass = (value, options = {}) => {
let {
parseUnquotedStrings = false,
resolveFunctions = false,
quotes = true,
} = options
if (value instanceof sass.Value) {
return value
} else if (value === null || value === undefined) {
return sass.sassNull
} else if (typeof value === 'boolean') {
return value ? sass.sassTrue : sass.sassFalse
} else if (typeof value === 'number') {
return new sass.SassNumber(value)
} else if (typeof value === 'string') {
const valueIsQuoted = isQuoted(value)
if (parseUnquotedStrings && !valueIsQuoted) {
let parsed = parseString(value)
if (
parsed instanceof sass.SassColor ||
parsed instanceof sass.SassNumber
)
return parsed
}
return new sass.SassString(value, {
quotes: valueIsQuoted || quotes,
})
} else if (typeof value === 'object') {
if (Array.isArray(value)) {
let sassList = value.map((value) => toSass(value, options))
return new sass.SassList(sassList)
} else {
let sassMap = OrderedMap(value).mapEntries(([key, value]) => [
new sass.SassString(key, { quotes: true }),
toSass(value, options),
])
return new sass.SassMap(sassMap)
}
} else if (resolveFunctions && typeof value === 'function') {
const args = Array.isArray(resolveFunctions) ? resolveFunctions : []
return toSass(value(...args), options)
}
return sass.sassNull
}
const colorProperties = [
'red',
'green',
'blue',
'hue',
'lightness',
'saturation',
'whiteness',
'blackness',
'alpha',
]
/**
* Converts Sass values to their Javascript equivalents.
*
* @example
* const { fromSass, toSass } = require('sass-cast');
*
* const sassString = toSass('a sass string object');
* const string = fromSass(sassString);
* // 'a sass string object'
*
* @param {Value} object - a {@link https://sass-lang.com/documentation/js-api/classes/Value Sass value}
* @param {Object} options
* @param {boolean} [options.preserveUnits=false] - By default, only the values of numbers are returned, not their units. If true, `fromSass` will return numbers as a two-item Array, i.e. [ value, unit ]
* @param {boolean} [options.rgbColors=false] - By default, colors are returned as strings. If true, `fromSass` will return colors as an object with `r`, `g`, `b`, and `a`, properties.
* @param {boolean} [options.preserveQuotes=false] - By default, quoted Sass strings return their inner text as a string. If true, `fromSass` will preserve the quotes in the returned string value.
* @return {*} - a Javascript value corresponding to the Sass input
*/
export const fromSass = (object, options = {}) => {
let {
preserveUnits = false,
rgbColors = false,
preserveQuotes = false,
} = options
if (object instanceof sass.SassBoolean) {
return object.value
} else if (object instanceof sass.SassNumber) {
if (preserveUnits) {
return [
object.value,
object.numeratorUnits.toArray(),
object.denominatorUnits.toArray(),
]
} else if (object.numeratorUnits.size || object.denominatorUnits.size) {
return object.toString()
}
return object.value
} else if (object instanceof sass.SassColor) {
if (rgbColors) {
return colorProperties.reduce((colorObj, p) => {
colorObj[p] = object[p]
return colorObj
}, {})
}
return object.toString()
} else if (object instanceof sass.SassString) {
return preserveQuotes ? object.text : unquoteString(object.text)
} else if (object instanceof sass.SassList || List.isList(object)) {
let list = []
for (
let i = 0, value = object.get(i);
value !== undefined;
i++, value = object.get(i)
) {
list.push(fromSass(value, options))
}
return list
} else if (object instanceof sass.SassMap) {
return object.contents
.mapEntries(([k, v]) => [k.text, fromSass(v, options)])
.toObject()
} else {
return object.realNull
}
}
/**
* An object defining Sass utility functions.
*
* @example <caption>Pass to sass using the JS API</caption>
* const { sassFunctions } = require('sass-cast');
* const sass = require('sass');
*
* sass.compile('main.scss', { functions: sassFunctions });
*/
export const sassFunctions = {
/**
* Sass function for importing data from Javascript or JSON files.
* Calls the CommonJS `require` function under the hood.
*
* #### Examples
*
* ```scss
* // import config info from tailwindcss
* $tw: require('./tailwind.config.js', $parseUnquotedStrings: true);
* $tw-colors: map.get($tw, theme, extend, colors);
* ```
* @name require
* @memberof sassFunctions
* @param {SassString} $module - Path to the file or module. Relative paths are relative to the Node process running Sass compilation.
* @param {SassList} [$properties=()] - List of properties, if you only want to parse part of the module data.
* @param {SassBoolean} [$parseUnquotedStrings=false] - Passed as an option to {@link #tosass toSass}.
* @param {SassBoolean} [$resolveFunctions=false] - Passed as an option to {@link #tosass toSass}.
* @param {SassBoolean} [$quotes=true] - Passed as an option to {@link #tosass toSass}.
* @return {Value} - a {@link https://sass-lang.com/documentation/js-api/classes/Value Sass value}
*/
'require($module, $properties: (), $parseUnquotedStrings: false, $resolveFunctions: false, $quotes: true)':
(args) => {
const moduleName = args[0].assertString('module').text
const properties = args[1].realNull && fromSass(args[1].asList)
const parseUnquotedStrings = args[2].isTruthy
const resolveFunctions = args[3].isTruthy
const quotes = args[4].isTruthy
const options = {
parseUnquotedStrings,
resolveFunctions,
quotes,
}
const convert = (data) =>
toSass(properties ? getAttr(data, properties) : data, options)
let mod,
paths = [moduleName, `${process.cwd()}/${moduleName}`]
for (let path of paths) {
try {
mod = require(path)
break
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') throw e
continue
}
}
if (!mod) throw new Error(`Couldn't find module: ${moduleName}`)
if (resolveFunctions && typeof mod === 'function') mod = mod()
if (mod instanceof Promise) return mod.then(convert)
return convert(mod)
},
}

View File

@@ -0,0 +1,108 @@
// https://github.com/dawaltconley/sass-cast/blob/main/utils.js
import * as sass from 'sass-embedded'
/**
* Check if string is quoted
* @private
* @param {string} str
* @return {boolean}
*/
export const isQuoted = (str) => /^['"].*['"]$/.test(str)
/**
* Surrounds a string with quotes
* @private
* @param {string} str
* @param {string} q - quotes, double or single
* @return {string}
*/
export const quoteString = (str, q) => {
if (!q) return str
if (isQuoted(str)) {
q = str[0]
str = str.slice(1, -1)
}
let r = new RegExp(q, 'g')
return q + str.replace(r, '\\' + q) + q
}
/**
* Unquotes a string
* @private
* @param {string} str
* @return {string}
*/
export const unquoteString = (str) => (isQuoted(str) ? str.slice(1, -1) : str)
/**
* Parse a string as a Sass object
* cribbed from davidkpiano/sassport
*
* @private
* @param {string} str
* @return {Value}
*/
export const parseString = (str) => {
let result
try {
sass.compileString(`$_: ___(${str});`, {
functions: {
'___($value)': (args) => {
result = args[0]
return result
},
},
})
} catch (e) {
return str
}
return result
}
/**
* Parse a string as a legacy Sass object
* cribbed from davidkpiano/sassport
*
* @private
* @param {string} str
* @return {LegacyObject}
*/
export const parseStringLegacy = (str) => {
let result
try {
sass.renderSync({
data: `$_: ___((${str}));`,
functions: {
'___($value)': (value) => {
result = value
return value
},
},
})
} catch (e) {
return str
}
return result
}
/**
* Function to handle 'toString()' methods with legacy API.
*
* @private
* @param {LegacyObject} obj
* @return {string}
*/
export const legacyToString = (obj) => (obj.dartValue || obj).toString()
/**
* Return a value from an object and a list of keys.
* @private
* @param {Object|Array} obj
* @param {*[]} attrs
*/
export const getAttr = (obj, attrs) => attrs.reduce((o, attr) => o[attr], obj)

View File

@@ -0,0 +1,43 @@
const colors = {
black: '#000000',
white: '#d9d9d9',
pink: '#DAC3C3',
green: '#D8E3D3',
cream: '#F2F1F1',
grey: '#2B2B2B',
}
const themes = {
light: {
bg: colors.white,
fg: colors.black,
},
dark: {
bg: colors.black,
fg: colors.white,
},
}
const breakpoints = {
mobile: 800,
}
const viewports = {
mobile: {
width: 440,
height: 956,
},
desktop: {
width: 1728,
height: 1117,
},
}
export { colors, themes, breakpoints, viewports }
export default {
colors,
themes,
breakpoints,
viewports,
}

5
src/renderer/src/main.js Normal file
View File

@@ -0,0 +1,5 @@
import './styles/main.scss'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

View File

@@ -0,0 +1,7 @@
@use 'sass:color';
:root {
@each $name, $color in getColors() {
--#{$name}: #{$color};
}
}

View File

@@ -0,0 +1,22 @@
:root {
--ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
--ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
--ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22);
--ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
--ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);
--ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335);
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
--ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);
--ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
--ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
--ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
--ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
--ease-custom: cubic-bezier(0.315, 0.365, 0.23, 0.985);
}

View File

@@ -0,0 +1,31 @@
@use 'functions' as *;
@mixin size-font($ds, $ms) {
font-size: mobile-vw($ms);
&.vh {
font-size: mobile-vh($ms);
}
@include desktop {
font-size: desktop-vw($ds);
&.vh {
font-size: desktop-vh($ds);
}
}
}
@mixin h1 {
font-family: var(--font-display);
font-weight: 400;
letter-spacing: 0.1em;
@include size-font(42px, 27px);
}
@mixin p {
font-family: var(--font-mono);
font-weight: 400;
letter-spacing: 0.03em;
@include size-font(25px, 18px);
}

View File

@@ -0,0 +1,45 @@
/*
Font Weights:
100 - Thin
200 - Extra Light (Ultra Light)
300 - Light
400 - Normal
500 - Medium
600 - Semi Bold (Demi Bold)
700 - Bold
800 - Extra Bold (Ultra Bold)
900 - Black (Heavy)
*/
/* Leibniz Fraktur */
@font-face {
font-family: 'Leibniz Fraktur';
font-style: normal;
font-weight: 400;
src:
url('../../../../resources/fonts/leibniz-fraktur.woff2') format('woff2'),
url('../../../../resources/fonts/leibniz-fraktur.woff') format('woff');
}
/* Geist Mono */
@font-face {
font-family: 'Geist Mono';
font-style: normal;
font-weight: 700;
src:
url('../../../../resources/fonts/geist-mono-bold.woff2') format('woff2'),
url('../../../../resources/fonts/geist-mono-bold.woff') format('woff');
}
@font-face {
font-family: 'Geist Mono';
font-style: normal;
font-weight: 400;
src:
url('../../../../resources/fonts/geist-mono.woff2') format('woff2'),
url('../../../../resources/fonts/geist-mono.woff') format('woff');
}
:root {
--font-display: 'Leibniz Fraktur', serif;
--font-mono: 'Geist Mono', monospace;
}

View File

@@ -0,0 +1,192 @@
@use 'sass:math';
/* Breakpoints */
$mobile-breakpoint: get('breakpoints.mobile');
// Viewport Sizes
$desktop-width: get('viewports.desktop.width');
$desktop-height: get('viewports.desktop.height');
$mobile-width: get('viewports.mobile.width');
$mobile-height: get('viewports.mobile.height');
// Breakpoint
@mixin mobile {
@media (max-width: #{$mobile-breakpoint * 1px - 1px}) {
@content;
}
}
@mixin desktop {
@media (min-width: #{$mobile-breakpoint * 1px}) {
@content;
}
}
@function mobile-vw($pixels, $base-vw: $mobile-width) {
$px: math.div($pixels, $base-vw);
$perc: math.div($px, 1px);
@return calc($perc * 100vw);
}
@function mobile-vh($pixels, $base-vh: $mobile-height) {
$px: math.div($pixels, $base-vh);
$perc: math.div($px, 1px);
@return calc($perc * 100vh);
}
@function desktop-vw($pixels, $base-vw: $desktop-width) {
$px: math.div($pixels, $base-vw);
$perc: math.div($px, 1px);
@return calc($perc * 100vw);
}
@function desktop-vh($pixels, $base-vh: $desktop-height) {
$px: math.div($pixels, $base-vh);
$perc: math.div($px, 1px);
@return calc($perc * 100vh);
}
@function columns($columns) {
@return calc(
(#{$columns} * var(--layout-column-width)) +
((#{$columns} - 1) * var(--layout-column-gap))
);
}
@mixin child-grid($columns) {
grid-template-columns: repeat($columns, minmax(0, 1fr));
column-gap: var(--layout-columns-gap);
display: grid;
}
@mixin reduced-motion {
@media (prefers-reduced-motion: reduce) {
@content;
}
}
@mixin fill($position: absolute) {
position: #{$position};
bottom: 0;
right: 0;
left: 0;
top: 0;
}
@mixin fade-on-ready($class: 'ready', $duration: 400ms) {
opacity: 0;
transition: opacity $duration ease;
&.#{$class} {
opacity: 1;
}
}
// Clamp text block to number of lines
@mixin line-clamp($lines: 3, $mobile-lines: $lines) {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
-webkit-line-clamp: $lines;
@include mobile {
-webkit-line-clamp: $mobile-lines;
}
}
// Flip animations
@keyframes flip-r {
50% {
transform: translateX(100%);
opacity: 0;
}
51% {
transform: translateX(-100%);
opacity: 0;
}
}
@keyframes flip-l {
50% {
transform: translateX(-100%);
opacity: 0;
}
51% {
transform: translateX(100%);
opacity: 0;
}
}
@keyframes flip-d {
50% {
transform: translateY(100%);
opacity: 0;
}
51% {
transform: translateY(-100%);
opacity: 0;
}
}
@keyframes flip-u {
50% {
transform: translateY(-100%);
opacity: 0;
}
51% {
transform: translateY(100%);
opacity: 0;
}
}
@mixin flip-animation(
$direction: 'r',
$duration: 600ms,
$easing: var(--ease-out-expo),
$iteration-count: 1
) {
overflow: hidden;
animation: flip-#{$direction} $duration $easing $iteration-count forwards;
}
@mixin link-hover {
position: relative;
&::before {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background: var(--theme-fg);
transform-origin: left;
transform: scaleX(0);
transition: transform 300ms var(--ease-out-quad);
}
@include desktop {
&:hover::before {
transform: scaleX(1);
}
}
}
@mixin stagger-animate($stagger: 100, $num-children: 10, $base-delay: 0) {
@for $i from 0 through $num-children {
&:nth-child(#{$i + 1}) {
transition-delay: #{$stagger * $i + $base-delay}ms;
}
}
}
@mixin hover {
@include desktop {
&:hover {
@content;
}
}
}
@mixin bonk-animation($delay: 0s, $duration: 1s, $angle: 25deg) {
--bonk-angle: #{$angle};
animation: bonk $duration $delay infinite step-end;
}

View File

@@ -0,0 +1,7 @@
// z-index
.lily-cursor {
z-index: 20;
}
.site-header {
z-index: 10;
}

View File

@@ -0,0 +1,91 @@
@use 'sass:list';
@use 'functions' as *;
// css variables exposed globally:
// --layout-column-count: columns count in the layout
// --layout-column-gap: gap size between columns
// --layout-margin: layout margin size (left or right)
// --layout-width: 100vw minus 2 * --layout-margin
// --layout-column-width: size of a single column
// css classes exposed globally:
// .layout-block: element takes the whole layout width
// .layout-block-inner: same as .layout-block but using padding instead of margin
// .layout-grid: extends .layout-block with grid behaviour using layout settings
// .layout-grid-inner: same as .layout-grid but using padding instead of margin
@use 'sass:map';
// config to fill
// 'variable': (mobile, desktop)
$layout: (
'columns-count': (
5,
18,
),
'columns-gap': (
20px,
20px,
),
'margin': (
30px,
60px,
),
);
//internal process, do not touch
:root {
--layout-column-count: #{list.nth(map.get($layout, 'columns-count'), 1)};
--layout-column-gap: #{mobile-vw(
list.nth(map.get($layout, 'columns-gap'), 1)
)};
--layout-margin: #{mobile-vw(list.nth(map.get($layout, 'margin'), 1))};
--layout-width: calc(100vw - (2 * var(--layout-margin)));
--layout-column-width: calc(
(
var(--layout-width) -
(
(var(--layout-column-count) - 1) *
var(--layout-column-gap)
)
) /
var(--layout-column-count)
);
@include desktop {
--layout-column-count: #{list.nth(map.get($layout, 'columns-count'), 2)};
--layout-column-gap: #{desktop-vw(
list.nth(map.get($layout, 'columns-gap'), 2)
)};
--layout-margin: #{desktop-vw(list.nth(map.get($layout, 'margin'), 2))};
}
}
.layout-block {
max-width: var(--layout-width);
margin-left: auto;
margin-right: auto;
width: 100%;
}
.layout-block-inner {
padding-left: var(--layout-margin);
padding-right: var(--layout-margin);
width: 100%;
}
.layout-grid {
@extend .layout-block;
display: grid;
grid-template-columns: repeat(var(--layout-column-count), minmax(0, 1fr));
grid-gap: var(--layout-column-gap);
}
.layout-grid-inner {
@extend .layout-block-inner;
display: grid;
grid-template-columns: repeat(var(--layout-column-count), minmax(0, 1fr));
grid-gap: var(--layout-column-gap);
}

View File

@@ -0,0 +1,99 @@
/***
The new CSS reset - version 1.9 (last updated 19.6.2023)
GitHub page: https://github.com/elad2412/the-new-css-reset
***/
/*
Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property
- The "symbol *" part is to solve Firefox SVG sprite bug
- The "html" element is excluded, otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36)
*/
*:where(
:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)
) {
all: unset;
display: revert;
}
/* Preferred box-sizing value */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Reapply the pointer cursor for anchor tags */
a,
button {
cursor: revert;
}
/* For images to not be able to exceed their container */
img {
max-inline-size: 100%;
max-block-size: 100%;
}
/* removes spacing between cells in tables */
table {
border-collapse: collapse;
}
/* Safari - solving issue when using user-select:none on the <body> text input doesn't working */
input,
textarea {
-webkit-user-select: auto;
}
/* revert the 'white-space' property for textarea elements on Safari */
textarea {
white-space: revert;
}
/* minimum style to allow to style meter element */
meter {
-webkit-appearance: revert;
appearance: revert;
}
/* preformatted text - use only for this feature */
:where(pre) {
all: revert;
}
/* reset default text opacity of input placeholder */
::placeholder {
color: unset;
}
/* remove default dot (•) sign */
::marker {
content: initial;
}
/* fix the feature of 'hidden' attribute.
display:revert; revert to element instead of attribute */
:where([hidden]) {
display: none;
}
/* revert for bug in Chromium browsers
- fix for the content editable attribute will work properly.
- webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/
:where([contenteditable]:not([contenteditable='false'])) {
-moz-user-modify: read-write;
-webkit-user-modify: read-write;
overflow-wrap: break-word;
-webkit-line-break: after-white-space;
-webkit-user-select: auto;
}
/* apply back the draggable feature - exist only in Chromium and Safari */
:where([draggable='true']) {
-webkit-user-drag: element;
}
/* Revert Modal native behavior */
:where(dialog:modal) {
all: revert;
}

View File

@@ -0,0 +1,34 @@
html {
&:not(.dev) {
&,
* {
scrollbar-width: none !important;
-ms-overflow-style: none !important;
&::-webkit-scrollbar {
width: 0 !important;
height: 0 !important;
}
}
}
}
html.lenis {
height: auto;
}
.lenis.lenis-smooth {
scroll-behavior: auto;
}
.lenis.lenis-smooth [data-lenis-prevent] {
overscroll-behavior: contain;
}
.lenis.lenis-stopped {
overflow: hidden;
}
.lenis.lenis-scrolling iframe {
pointer-events: none;
}

View File

@@ -0,0 +1,11 @@
@use 'sass:color';
:root {
@each $name, $theme in getThemes() {
.theme-#{$name} {
@each $name, $color in $theme {
--theme-#{$name}: #{$color};
}
}
}
}

View File

@@ -0,0 +1,43 @@
// Fades
.fade-enter-active,
.fade-leave-active {
transition: opacity 400ms;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.quick-fade-enter-active,
.quick-fade-leave-active {
transition: opacity 100ms;
}
.quick-fade-enter-from,
.quick-fade-leave-to {
opacity: 0;
}
.slow-fade-enter-active,
.slow-fade-leave-active {
transition: opacity 600ms;
}
.slow-fade-enter-from,
.slow-fade-leave-to {
opacity: 0;
}
// Slides
.slide-up-enter-active,
.slide-up-leave-active {
transition: transform 400ms var(--ease-out-quad);
}
.slide-up-enter-from,
.slide-up-leave-to {
transform: translateY(-100%);
}
.slide-left-enter-active,
.slide-left-leave-active {
transition: transform 400ms var(--ease-out-quad);
}
.slide-left-enter-from,
.slide-left-leave-to {
transform: translateX(-100%);
}

View File

@@ -0,0 +1,42 @@
@use 'functions' as *;
.full-width {
width: 100vw;
position: relative;
left: 50%;
right: 50%;
margin-left: -50vw;
margin-right: -50vw;
}
.overflow-hidden {
overflow: hidden;
}
.relative {
position: relative;
}
.mobile-only {
@include desktop {
display: none !important;
}
}
.desktop-only {
@include mobile {
display: none !important;
}
}
html:not(.has-scroll-smooth) {
.hide-on-native-scroll {
display: none;
}
}
html.has-scroll-smooth {
.hide-on-smooth-scroll {
display: none;
}
}

View File

@@ -0,0 +1,111 @@
@use 'colors' as *;
@use 'themes' as *;
@use 'easings' as *;
@use 'reset' as *;
@use 'layers' as *;
@use 'functions' as *;
@use 'utils' as *;
@use 'fonts' as *;
@use 'font-style' as *;
@use 'layout' as *;
@use 'scroll' as *;
@use 'transitions' as *;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
background: var(--white);
color: var(--black);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
min-height: 100vh;
}
// Type
.h1,
h1,
h2,
h3,
h4,
h5,
h6 {
@include h1;
}
.p,
p,
a,
button,
input,
pre {
@include p;
}
.entry {
img {
max-width: 100%;
height: auto;
}
p {
min-height: 1px;
}
a {
text-decoration: underline;
}
& > * {
margin-bottom: 1em;
margin-top: 1em;
}
& > *:first-child {
margin-top: 0;
}
& > *:last-child {
margin-bottom: 0;
}
ul,
ol {
li {
margin-bottom: 1em;
width: desktop-vw(577px);
}
}
ul {
list-style: disc;
padding-left: 1em;
}
}
#app {
min-height: 100vh;
}
@keyframes blink {
5% {
opacity: 0;
}
40% {
opacity: 0;
}
60% {
opacity: 1;
}
95% {
opacity: 1;
}
}
@keyframes bonk {
0% {
transform: rotate(calc(var(--bonk-angle) * -1));
}
50% {
transform: rotate(var(--bonk-angle));
}
}