web: dymunaf orchuddio cod hwn yn y Raffles Place Park

a taflu cerrig dros ei droed
This commit is contained in:
Mark Joshwel 2025-02-11 11:08:59 +08:00
parent d2b711e6a7
commit 9cdbfe0bf0
69 changed files with 3170 additions and 32273 deletions

5
OldWeb/.firebaserc Normal file
View file

@ -0,0 +1,5 @@
{
"projects": {
"default": "echoesbehindcloseddoors"
}
}

144
OldWeb/.gitignore vendored Normal file
View file

@ -0,0 +1,144 @@
# Logs
logs
_.log
*.log
npm-debug.log_
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
_.pid
_.seed
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# firebase cache?
.firebase/*.cache

130
OldWeb/README.md Normal file
View file

@ -0,0 +1,130 @@
# The 'Echoes Behind Closed Doors' Website (and Game Backend Documentation)
for the Developing Dyamic Applications module as part of the Year 2.2
Integrated Project for Ngee Ann Polytechnic's Diploma in Immersive Media
## Game, Stack, and Data Model
### Gameplay
the game is a vr experience not too dissimilar to storytelling-heavy
experiences like visual novels but with a few gameplay mechanics, making it
similar to games like Firewatch
current storyline/gameplay description:
> A teen is experiencing a spiral of mental health challenges exacerbated by
> domestic violence. Players navigate their world through simple, interactive
> daily tasks, which slowly reveal the layers of the teens struggles. The
> narrative emphasizes realism, empathy, and awareness.
### Stack and Usage
##### Stack
- **Firebase Auth:** for authentication
- **Firebase Realtime Database:** for player data
- **Firebase Hosting:** for hosting the website
- **Supabase Storage:** for storing the recorded messages
##### Usage Flow
because we also are presenting this via a booth-based viva voce demonstration,
we are pretending as if we are the partner Ngee Ann companies that will
presumably be at the booth, and as such this website is more for the booth
runners than the players themselves
TODO
1. **Booth Laptop:** show a registration qr code (firebase hosting)
2. **Players Personal Device:** make an account (firebase auth)
3. **Players Personal Device:** post-sign up, they are innocently asked to
"record a message for someone with mental health struggles"
(stored via supabase storage)
4. **Booth Laptop:** booth runner via a dashboard sets the user as the
"currently playing user" (firebase realtime database)
5. **Meta Quest:** player plays the game, the game using the UID of the
"currently playing user" to record the players choices
(firebase realtime database)
6. **Meta Quest:** at the crux of the game, the call from the player's 'friend' is
actually the recorded message from the booth laptop
1. fetch url from firebase realtime database
2. download from supabase storage
3. play in-game via unity
7. **Meta Quest:** finish game, ending is recorded in firebase realtime database
8. **Booth Laptop/Players Personal Device:** can see the player's choices and ending via their profile page
- players can send their profile page to their friends to show if they want to,
i'll probably this via url parameters (e.g. `profile.html?uid=...`) like the
last group project i was in because we're not using any fancy frontend
frameworks (firebase hosting)
9. **Booth Laptop:** via the same dashboard is able to see:
- the current players' real-time progress
- all past players
- their emails
- and profile pages
- maybe some global statistics if there's time
### Data Model
(for firebase realtime database)
```text
currentUser: <uid>
plays
└── <playId>
├── uid: <uid>
├── day1
│ ├── brushTeeth: true
│ └── ...
├── day2
│ └── ...
└── finalday
├── acceptCall: true
└── ...
user
└── <userid>/
└── callUrl: <supabase storage link>
```
## Development Setup
### Local Development Setup
[bun](https://bun.sh/) is used as the package manager and bundler for this project,
but npm can be used as well for tailwindcss
**note:** firebase-tools does not work well with bun,
so you will need to use npm for that
everything you need to do is available as a script in the `package.json` file
use `bun run ...` or `npm run ...` to run the scripts
| script | description | bun | npm |
|--------|-------------|-----|-----|
| `css` | build css from tailwind | ✅ | ✅ |
| `build` | buiild css and bundle the website | ✅ | ❌ |
| `dev-css` | build css and watch | ✅ | ✅ |
| `dev-build` | bundle and watch (this does not watch build css on filesystem change, please run `dev-css` separately) | ✅ | ❌ |
| `deploy-live` | deploy to firebase hosting | ✅ | ✅ |
| `deploy-staging` | deploy to firebase hosting on a staging channel | ✅ | ✅ |
| `deploy` | `build` + `deploy-live` | ✅ | ❌ |
### Cloud Development Setup
- Firebase Project
- Authentication
- Email/Password Provider
- Email/Password: enabled
- Email link (passwordless sign-in): enabled
- Realtime Database
- Security Rules: (TODO; if i have time to define them before submission)
- Hosting \
(not strictly necessary)
- Supabase Project
- Storage
- Bucket
- named it `telephonies` but like i don't think it matters
- Enable connection via S3 protocol: enabled (TODO; might not even use this way of uploading)
- Policies: (TODO; if i have time to define them before submission)

File diff suppressed because one or more lines are too long

2
OldWeb/css/styles.min.css vendored Normal file

File diff suppressed because one or more lines are too long

10
OldWeb/firebase-config.js Normal file
View file

@ -0,0 +1,10 @@
export const firebaseConfig = {
apiKey: "AIzaSyARm-eSymd2Q3AyxJXiAiiUzsXGmc6T72I",
authDomain: "echoesbehindcloseddoors.firebaseapp.com",
databaseURL:
"https://echoesbehindcloseddoors-default-rtdb.asia-southeast1.firebasedatabase.app",
projectId: "echoesbehindcloseddoors",
storageBucket: "echoesbehindcloseddoors.firebasestorage.app",
messagingSenderId: "905742343227",
appId: "1:905742343227:web:19d04f77371ef1952c901e",
};

10
OldWeb/firebase.json Normal file
View file

@ -0,0 +1,10 @@
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -153,21 +153,21 @@
<div data-uk-dropdown="mode: click">
<ul class="uk-dropdown-nav uk-nav">
<li id="nav-auth-logIn">
<a href="/login">Log In</a>
<a href="/auth.html">Log In</a>
</li>
<li id="nav-auth-signUp">
<a href="/signup">Sign Up</a>
<a href="/auth.html">Sign Up</a>
</li>
<li id="nav-auth-logOut">
<a href="/profile?logOut=true">Log Out</a>
<a href="/profile.html?logOut=true">Log Out</a>
</li>
<li id="nav-auth-profile">
<a href="/profile">Profile</a>
<a href="/profile.html">Profile</a>
</li>
</ul>
</div>
</li>
<li><a href="/booth">Manage Booth</a></li>
<li><a href="/booth.html">Manage Booth</a></li>
</ul>
</nav>
</header>

0
OldWeb/js/auth.js Normal file
View file

View file

@ -29,7 +29,7 @@
<!-- TODO: social share -->
<!-- franken-ui -->
<link rel="stylesheet" href="/css/styles.css" />
<link rel="stylesheet" href="/css/styles.min.css" />
<script
type="module"
src="/js/franken-ui.js"

View file

@ -2,8 +2,8 @@
"name": "echoesbehindcloseddoors-web",
"scripts": {
"css": "tailwindcss -i css/styles.src.css -o css/styles.css --minify",
"css-process": "lightningcss --minify --bundle css/styles.css -o css/styles.min.css",
"build": "bun run css && bun run css-process && rm -rf public/; bun build index.html --outdir=public",
"css-process": "lightningcss --minify --bundle --targets '>= 0.25%' css/styles.css -o css/styles.min.css",
"build": "bun run css && bun run css-process && rm -rf public/; bun build index.html login.html --outdir=public",
"dev-css": "tailwindcss -i css/styles.src.css -o css/styles.css --watch",
"dev-build": "bun build index.html --outdir=public --watch",
"deploy-live": "firebase deploy --only hosting",

View file

@ -90,25 +90,15 @@ user
### Local Development Setup
[bun](https://bun.sh/) is used as the package manager and bundler for this project,
but npm can be used as well for tailwindcss
~~[bun](https://bun.sh/) is used as the package manager and bundler for this project,
but npm can be used as well for tailwindcss~~
**note:** firebase-tools does not work well with bun,
so you will need to use npm for that
~~**note:** firebase-tools does not work well with bun,
so you will need to use npm for that~~
everything you need to do is available as a script in the `package.json` file
use a live server, firebase emulator, or something similar
use `bun run ...` or `npm run ...` to run the scripts
| script | description | bun | npm |
|--------|-------------|-----|-----|
| `css` | build css from tailwind | ✅ | ✅ |
| `build` | buiild css and bundle the website | ✅ | ❌ |
| `dev-css` | build css and watch | ✅ | ✅ |
| `dev-build` | bundle and watch (this does not watch build css on filesystem change, please run `dev-css` separately) | ✅ | ❌ |
| `deploy-live` | deploy to firebase hosting | ✅ | ✅ |
| `deploy-staging` | deploy to firebase hosting on a staging channel | ✅ | ✅ |
| `deploy` | `build` + `deploy-live` | ✅ | ❌ |
everything is in the `public/` folder, and works as-is
### Cloud Development Setup

File diff suppressed because one or more lines are too long

View file

@ -1,9 +1,10 @@
export const firebaseConfig = {
apiKey: "AIzaSyARm-eSymd2Q3AyxJXiAiiUzsXGmc6T72I",
authDomain: "echoesbehindcloseddoors.firebaseapp.com",
databaseURL: "https://echoesbehindcloseddoors-default-rtdb.asia-southeast1.firebasedatabase.app",
projectId: "echoesbehindcloseddoors",
storageBucket: "echoesbehindcloseddoors.firebasestorage.app",
messagingSenderId: "905742343227",
appId: "1:905742343227:web:19d04f77371ef1952c901e"
};
apiKey: "AIzaSyARm-eSymd2Q3AyxJXiAiiUzsXGmc6T72I",
authDomain: "echoesbehindcloseddoors.firebaseapp.com",
databaseURL:
"https://echoesbehindcloseddoors-default-rtdb.asia-southeast1.firebasedatabase.app",
projectId: "echoesbehindcloseddoors",
storageBucket: "echoesbehindcloseddoors.firebasestorage.app",
messagingSenderId: "905742343227",
appId: "1:905742343227:web:19d04f77371ef1952c901e",
};

11
Web/public/404.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
404
</body>
</html>

View file

@ -0,0 +1,61 @@
<div class="uk-card">
<div class="uk-card-header space-y-2">
<h3 class="uk-h3">Create an account</h3>
<p class="text-muted-foreground">
Enter your email below to create your account
</p>
</div>
<div class="uk-card-body space-y-4">
<div class="grid grid-cols-2 gap-6">
<button class="uk-btn uk-btn-default">
<svg viewBox="0 0 438.549 438.549" class="mr-2 h-4 w-4">
<path
fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
></path>
</svg>
Github
</button>
<button class="uk-btn uk-btn-default">
<svg role="img" viewBox="0 0 24 24" class="mr-2 h-4 w-4">
<path
fill="currentColor"
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
></path>
</svg>
Google
</button>
</div>
<div class="relative">
<div class="absolute inset-0 flex items-center">
<span class="w-full border-t border-border"></span>
</div>
<div class="relative flex justify-center text-xs uppercase">
<span class="bg-background px-2 text-muted-foreground"
>Or continue with</span
>
</div>
</div>
<div class="space-y-2">
<label class="uk-form-label" for="email">Email</label>
<input
class="uk-input"
id="email"
type="text"
placeholder="m@example.com"
/>
</div>
<div class="space-y-2">
<label class="uk-form-label" for="password">Password</label>
<input
class="uk-input"
id="password"
type="password"
placeholder="Password"
/>
</div>
</div>
<div class="uk-card-footer">
<button class="uk-btn uk-btn-primary w-full">Create account</button>
</div>
</div>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,142 @@
<body
class="bg-background font-geist-sans text-sm text-foreground"
data-sveltekit-preload-data="hover"
>
<div style="display: contents">
<!--[--><!--[--><!----><!---->
<div class="hidden h-screen grid-cols-2 xl:grid">
<div
class="col-span-1 hidden flex-col justify-between bg-zinc-900 p-8 text-white lg:flex"
>
<div class="flex items-center text-lg font-medium">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2 h-6 w-6"
>
<path
d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3"
></path>
</svg>
Acme Inc
</div>
<blockquote class="space-y-2">
<p class="text-lg">
"This library has saved me countless hours of work and helped me
deliver stunning designs to my clients faster than ever before."
</p>
<footer class="text-sm">Sofia Davis</footer>
</blockquote>
</div>
<div class="col-span-2 flex flex-col p-8 lg:col-span-1">
<div class="flex flex-none justify-end">
<button class="uk-btn uk-btn-ghost">Login</button>
</div>
<div class="flex flex-1 items-center justify-center">
<div class="w-80 space-y-6">
<div class="flex flex-col space-y-2 text-center">
<h1 class="uk-h3">Create an account</h1>
<p class="text-sm text-muted-foreground">
Enter your email below to create your account
</p>
</div>
<div class="space-y-2">
<input
class="uk-input"
placeholder="name@example.com"
type="text"
/>
<button class="uk-btn uk-btn-primary w-full">
<!--[!--><!--]-->
Sign in with Email
</button>
</div>
<div class="relative">
<div class="absolute inset-0 flex items-center">
<span class="w-full border-t border-border"></span>
</div>
<div class="relative flex justify-center text-xs uppercase">
<span class="bg-background px-2 text-muted-foreground"
>Or continue with</span
>
</div>
</div>
<button class="uk-btn uk-btn-default w-full">
<svg viewBox="0 0 438.549 438.549" class="mr-2 h-4 w-4">
<path
fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
></path>
</svg>
Github
</button>
<p class="px-8 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our
<a
class="underline underline-offset-4 hover:text-primary"
href="#"
>Terms of Service</a
>
and
<a
class="underline underline-offset-4 hover:text-primary"
href="#"
>Privacy Policy</a
>.
</p>
</div>
</div>
</div>
</div>
<!----><!----><!----><!--]-->
<!--[!-->
<div
id="svelte-announcer"
aria-live="assertive"
aria-atomic="true"
style="
position: absolute;
left: 0;
top: 0;
clip: rect(0 0 0 0);
clip-path: inset(50%);
overflow: hidden;
white-space: nowrap;
width: 1px;
height: 1px;
"
>
<!---->
</div>
<!--]--><!--]-->
<script>
{
__sveltekit_ceme51 = {
base: new URL("..", location).pathname.slice(0, -1),
};
const element = document.currentScript.parentElement;
const data = [null, null];
Promise.all([
import("../_app/immutable/entry/start.Vst7ySbz.js"),
import("../_app/immutable/entry/app.D3_5gIyu.js"),
]).then(([kit, app]) => {
kit.start(app, element, {
node_ids: [0, 3],
data,
form: null,
error: null,
});
});
}
</script>
</div>
</body>

View file

@ -0,0 +1,68 @@
/* @import url("https://unpkg.com/franken-ui@2.0.0-internal.42/dist/css/core.min.css"); */
@import "./franken-core.css";
@import "tailwindcss";
/* latin-ext */
@font-face {
font-family: "Geist";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/geist/v1/gyByhwUxId8gMEwSGFWNPoTcZY7pVQ.woff2)
format("woff2");
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Geist";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/geist/v1/gyByhwUxId8gMEwcGFWNPoTcZY4.woff2)
format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
font-family: "Geist Mono";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/geistmono/v1/or3nQ6H-1_WfwkMZI_qYFrkdmhHijks9bNn0.woff2)
format("woff2");
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Geist Mono";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/geistmono/v1/or3nQ6H-1_WfwkMZI_qYFrcdmhHijks9bA.woff2)
format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
@theme {
--font-sans: "Geist", sans-serif;
--font-mono: "'Geist Mono'", monospace;
}
:root {
font-family: Geist, sans-serif;
font-feature-settings: "liga" 1, "calt" 1; /* fix for Chrome */
}
@media (max-width: 768px) {
.uk-subnav > * > :first-child {
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
}
}

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,606 +0,0 @@
// js/nayuki-qrcodegen-v1.8.0.js
var qrcodegen;
(function(qrcodegen2) {
class QrCode {
constructor(version, errorCorrectionLevel, dataCodewords, msk) {
this.version = version;
this.errorCorrectionLevel = errorCorrectionLevel;
this.modules = [];
this.isFunction = [];
if (version < QrCode.MIN_VERSION || version > QrCode.MAX_VERSION)
throw new RangeError("Version value out of range");
if (msk < -1 || msk > 7)
throw new RangeError("Mask value out of range");
this.size = version * 4 + 17;
let row = [];
for (let i = 0;i < this.size; i++)
row.push(false);
for (let i = 0;i < this.size; i++) {
this.modules.push(row.slice());
this.isFunction.push(row.slice());
}
this.drawFunctionPatterns();
const allCodewords = this.addEccAndInterleave(dataCodewords);
this.drawCodewords(allCodewords);
if (msk == -1) {
let minPenalty = 1e9;
for (let i = 0;i < 8; i++) {
this.applyMask(i);
this.drawFormatBits(i);
const penalty = this.getPenaltyScore();
if (penalty < minPenalty) {
msk = i;
minPenalty = penalty;
}
this.applyMask(i);
}
}
assert(0 <= msk && msk <= 7);
this.mask = msk;
this.applyMask(msk);
this.drawFormatBits(msk);
this.isFunction = [];
}
static encodeText(text, ecl) {
const segs = qrcodegen2.QrSegment.makeSegments(text);
return QrCode.encodeSegments(segs, ecl);
}
static encodeBinary(data, ecl) {
const seg = qrcodegen2.QrSegment.makeBytes(data);
return QrCode.encodeSegments([seg], ecl);
}
static encodeSegments(segs, ecl, minVersion = 1, maxVersion = 40, mask = -1, boostEcl = true) {
if (!(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION) || mask < -1 || mask > 7)
throw new RangeError("Invalid value");
let version;
let dataUsedBits;
for (version = minVersion;; version++) {
const dataCapacityBits2 = QrCode.getNumDataCodewords(version, ecl) * 8;
const usedBits = QrSegment.getTotalBits(segs, version);
if (usedBits <= dataCapacityBits2) {
dataUsedBits = usedBits;
break;
}
if (version >= maxVersion)
throw new RangeError("Data too long");
}
for (const newEcl of [QrCode.Ecc.MEDIUM, QrCode.Ecc.QUARTILE, QrCode.Ecc.HIGH]) {
if (boostEcl && dataUsedBits <= QrCode.getNumDataCodewords(version, newEcl) * 8)
ecl = newEcl;
}
let bb = [];
for (const seg of segs) {
appendBits(seg.mode.modeBits, 4, bb);
appendBits(seg.numChars, seg.mode.numCharCountBits(version), bb);
for (const b of seg.getData())
bb.push(b);
}
assert(bb.length == dataUsedBits);
const dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8;
assert(bb.length <= dataCapacityBits);
appendBits(0, Math.min(4, dataCapacityBits - bb.length), bb);
appendBits(0, (8 - bb.length % 8) % 8, bb);
assert(bb.length % 8 == 0);
for (let padByte = 236;bb.length < dataCapacityBits; padByte ^= 236 ^ 17)
appendBits(padByte, 8, bb);
let dataCodewords = [];
while (dataCodewords.length * 8 < bb.length)
dataCodewords.push(0);
bb.forEach((b, i) => dataCodewords[i >>> 3] |= b << 7 - (i & 7));
return new QrCode(version, ecl, dataCodewords, mask);
}
getModule(x, y) {
return 0 <= x && x < this.size && 0 <= y && y < this.size && this.modules[y][x];
}
drawFunctionPatterns() {
for (let i = 0;i < this.size; i++) {
this.setFunctionModule(6, i, i % 2 == 0);
this.setFunctionModule(i, 6, i % 2 == 0);
}
this.drawFinderPattern(3, 3);
this.drawFinderPattern(this.size - 4, 3);
this.drawFinderPattern(3, this.size - 4);
const alignPatPos = this.getAlignmentPatternPositions();
const numAlign = alignPatPos.length;
for (let i = 0;i < numAlign; i++) {
for (let j = 0;j < numAlign; j++) {
if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0))
this.drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
}
}
this.drawFormatBits(0);
this.drawVersion();
}
drawFormatBits(mask) {
const data = this.errorCorrectionLevel.formatBits << 3 | mask;
let rem = data;
for (let i = 0;i < 10; i++)
rem = rem << 1 ^ (rem >>> 9) * 1335;
const bits = (data << 10 | rem) ^ 21522;
assert(bits >>> 15 == 0);
for (let i = 0;i <= 5; i++)
this.setFunctionModule(8, i, getBit(bits, i));
this.setFunctionModule(8, 7, getBit(bits, 6));
this.setFunctionModule(8, 8, getBit(bits, 7));
this.setFunctionModule(7, 8, getBit(bits, 8));
for (let i = 9;i < 15; i++)
this.setFunctionModule(14 - i, 8, getBit(bits, i));
for (let i = 0;i < 8; i++)
this.setFunctionModule(this.size - 1 - i, 8, getBit(bits, i));
for (let i = 8;i < 15; i++)
this.setFunctionModule(8, this.size - 15 + i, getBit(bits, i));
this.setFunctionModule(8, this.size - 8, true);
}
drawVersion() {
if (this.version < 7)
return;
let rem = this.version;
for (let i = 0;i < 12; i++)
rem = rem << 1 ^ (rem >>> 11) * 7973;
const bits = this.version << 12 | rem;
assert(bits >>> 18 == 0);
for (let i = 0;i < 18; i++) {
const color = getBit(bits, i);
const a = this.size - 11 + i % 3;
const b = Math.floor(i / 3);
this.setFunctionModule(a, b, color);
this.setFunctionModule(b, a, color);
}
}
drawFinderPattern(x, y) {
for (let dy = -4;dy <= 4; dy++) {
for (let dx = -4;dx <= 4; dx++) {
const dist = Math.max(Math.abs(dx), Math.abs(dy));
const xx = x + dx;
const yy = y + dy;
if (0 <= xx && xx < this.size && 0 <= yy && yy < this.size)
this.setFunctionModule(xx, yy, dist != 2 && dist != 4);
}
}
}
drawAlignmentPattern(x, y) {
for (let dy = -2;dy <= 2; dy++) {
for (let dx = -2;dx <= 2; dx++)
this.setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) != 1);
}
}
setFunctionModule(x, y, isDark) {
this.modules[y][x] = isDark;
this.isFunction[y][x] = true;
}
addEccAndInterleave(data) {
const ver = this.version;
const ecl = this.errorCorrectionLevel;
if (data.length != QrCode.getNumDataCodewords(ver, ecl))
throw new RangeError("Invalid argument");
const numBlocks = QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
const blockEccLen = QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver];
const rawCodewords = Math.floor(QrCode.getNumRawDataModules(ver) / 8);
const numShortBlocks = numBlocks - rawCodewords % numBlocks;
const shortBlockLen = Math.floor(rawCodewords / numBlocks);
let blocks = [];
const rsDiv = QrCode.reedSolomonComputeDivisor(blockEccLen);
for (let i = 0, k = 0;i < numBlocks; i++) {
let dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
k += dat.length;
const ecc = QrCode.reedSolomonComputeRemainder(dat, rsDiv);
if (i < numShortBlocks)
dat.push(0);
blocks.push(dat.concat(ecc));
}
let result = [];
for (let i = 0;i < blocks[0].length; i++) {
blocks.forEach((block, j) => {
if (i != shortBlockLen - blockEccLen || j >= numShortBlocks)
result.push(block[i]);
});
}
assert(result.length == rawCodewords);
return result;
}
drawCodewords(data) {
if (data.length != Math.floor(QrCode.getNumRawDataModules(this.version) / 8))
throw new RangeError("Invalid argument");
let i = 0;
for (let right = this.size - 1;right >= 1; right -= 2) {
if (right == 6)
right = 5;
for (let vert = 0;vert < this.size; vert++) {
for (let j = 0;j < 2; j++) {
const x = right - j;
const upward = (right + 1 & 2) == 0;
const y = upward ? this.size - 1 - vert : vert;
if (!this.isFunction[y][x] && i < data.length * 8) {
this.modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));
i++;
}
}
}
}
assert(i == data.length * 8);
}
applyMask(mask) {
if (mask < 0 || mask > 7)
throw new RangeError("Mask value out of range");
for (let y = 0;y < this.size; y++) {
for (let x = 0;x < this.size; x++) {
let invert;
switch (mask) {
case 0:
invert = (x + y) % 2 == 0;
break;
case 1:
invert = y % 2 == 0;
break;
case 2:
invert = x % 3 == 0;
break;
case 3:
invert = (x + y) % 3 == 0;
break;
case 4:
invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 == 0;
break;
case 5:
invert = x * y % 2 + x * y % 3 == 0;
break;
case 6:
invert = (x * y % 2 + x * y % 3) % 2 == 0;
break;
case 7:
invert = ((x + y) % 2 + x * y % 3) % 2 == 0;
break;
default:
throw new Error("Unreachable");
}
if (!this.isFunction[y][x] && invert)
this.modules[y][x] = !this.modules[y][x];
}
}
}
getPenaltyScore() {
let result = 0;
for (let y = 0;y < this.size; y++) {
let runColor = false;
let runX = 0;
let runHistory = [0, 0, 0, 0, 0, 0, 0];
for (let x = 0;x < this.size; x++) {
if (this.modules[y][x] == runColor) {
runX++;
if (runX == 5)
result += QrCode.PENALTY_N1;
else if (runX > 5)
result++;
} else {
this.finderPenaltyAddHistory(runX, runHistory);
if (!runColor)
result += this.finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3;
runColor = this.modules[y][x];
runX = 1;
}
}
result += this.finderPenaltyTerminateAndCount(runColor, runX, runHistory) * QrCode.PENALTY_N3;
}
for (let x = 0;x < this.size; x++) {
let runColor = false;
let runY = 0;
let runHistory = [0, 0, 0, 0, 0, 0, 0];
for (let y = 0;y < this.size; y++) {
if (this.modules[y][x] == runColor) {
runY++;
if (runY == 5)
result += QrCode.PENALTY_N1;
else if (runY > 5)
result++;
} else {
this.finderPenaltyAddHistory(runY, runHistory);
if (!runColor)
result += this.finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3;
runColor = this.modules[y][x];
runY = 1;
}
}
result += this.finderPenaltyTerminateAndCount(runColor, runY, runHistory) * QrCode.PENALTY_N3;
}
for (let y = 0;y < this.size - 1; y++) {
for (let x = 0;x < this.size - 1; x++) {
const color = this.modules[y][x];
if (color == this.modules[y][x + 1] && color == this.modules[y + 1][x] && color == this.modules[y + 1][x + 1])
result += QrCode.PENALTY_N2;
}
}
let dark = 0;
for (const row of this.modules)
dark = row.reduce((sum, color) => sum + (color ? 1 : 0), dark);
const total = this.size * this.size;
const k = Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1;
assert(0 <= k && k <= 9);
result += k * QrCode.PENALTY_N4;
assert(0 <= result && result <= 2568888);
return result;
}
getAlignmentPatternPositions() {
if (this.version == 1)
return [];
else {
const numAlign = Math.floor(this.version / 7) + 2;
const step = this.version == 32 ? 26 : Math.ceil((this.version * 4 + 4) / (numAlign * 2 - 2)) * 2;
let result = [6];
for (let pos = this.size - 7;result.length < numAlign; pos -= step)
result.splice(1, 0, pos);
return result;
}
}
static getNumRawDataModules(ver) {
if (ver < QrCode.MIN_VERSION || ver > QrCode.MAX_VERSION)
throw new RangeError("Version number out of range");
let result = (16 * ver + 128) * ver + 64;
if (ver >= 2) {
const numAlign = Math.floor(ver / 7) + 2;
result -= (25 * numAlign - 10) * numAlign - 55;
if (ver >= 7)
result -= 36;
}
assert(208 <= result && result <= 29648);
return result;
}
static getNumDataCodewords(ver, ecl) {
return Math.floor(QrCode.getNumRawDataModules(ver) / 8) - QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver] * QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
}
static reedSolomonComputeDivisor(degree) {
if (degree < 1 || degree > 255)
throw new RangeError("Degree out of range");
let result = [];
for (let i = 0;i < degree - 1; i++)
result.push(0);
result.push(1);
let root = 1;
for (let i = 0;i < degree; i++) {
for (let j = 0;j < result.length; j++) {
result[j] = QrCode.reedSolomonMultiply(result[j], root);
if (j + 1 < result.length)
result[j] ^= result[j + 1];
}
root = QrCode.reedSolomonMultiply(root, 2);
}
return result;
}
static reedSolomonComputeRemainder(data, divisor) {
let result = divisor.map((_) => 0);
for (const b of data) {
const factor = b ^ result.shift();
result.push(0);
divisor.forEach((coef, i) => result[i] ^= QrCode.reedSolomonMultiply(coef, factor));
}
return result;
}
static reedSolomonMultiply(x, y) {
if (x >>> 8 != 0 || y >>> 8 != 0)
throw new RangeError("Byte out of range");
let z = 0;
for (let i = 7;i >= 0; i--) {
z = z << 1 ^ (z >>> 7) * 285;
z ^= (y >>> i & 1) * x;
}
assert(z >>> 8 == 0);
return z;
}
finderPenaltyCountPatterns(runHistory) {
const n = runHistory[1];
assert(n <= this.size * 3);
const core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n;
return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0);
}
finderPenaltyTerminateAndCount(currentRunColor, currentRunLength, runHistory) {
if (currentRunColor) {
this.finderPenaltyAddHistory(currentRunLength, runHistory);
currentRunLength = 0;
}
currentRunLength += this.size;
this.finderPenaltyAddHistory(currentRunLength, runHistory);
return this.finderPenaltyCountPatterns(runHistory);
}
finderPenaltyAddHistory(currentRunLength, runHistory) {
if (runHistory[0] == 0)
currentRunLength += this.size;
runHistory.pop();
runHistory.unshift(currentRunLength);
}
}
QrCode.MIN_VERSION = 1;
QrCode.MAX_VERSION = 40;
QrCode.PENALTY_N1 = 3;
QrCode.PENALTY_N2 = 3;
QrCode.PENALTY_N3 = 40;
QrCode.PENALTY_N4 = 10;
QrCode.ECC_CODEWORDS_PER_BLOCK = [
[-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
[-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28],
[-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
[-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30]
];
QrCode.NUM_ERROR_CORRECTION_BLOCKS = [
[-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25],
[-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49],
[-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68],
[-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81]
];
qrcodegen2.QrCode = QrCode;
function appendBits(val, len, bb) {
if (len < 0 || len > 31 || val >>> len != 0)
throw new RangeError("Value out of range");
for (let i = len - 1;i >= 0; i--)
bb.push(val >>> i & 1);
}
function getBit(x, i) {
return (x >>> i & 1) != 0;
}
function assert(cond) {
if (!cond)
throw new Error("Assertion error");
}
class QrSegment {
constructor(mode, numChars, bitData) {
this.mode = mode;
this.numChars = numChars;
this.bitData = bitData;
if (numChars < 0)
throw new RangeError("Invalid argument");
this.bitData = bitData.slice();
}
static makeBytes(data) {
let bb = [];
for (const b of data)
appendBits(b, 8, bb);
return new QrSegment(QrSegment.Mode.BYTE, data.length, bb);
}
static makeNumeric(digits) {
if (!QrSegment.isNumeric(digits))
throw new RangeError("String contains non-numeric characters");
let bb = [];
for (let i = 0;i < digits.length; ) {
const n = Math.min(digits.length - i, 3);
appendBits(parseInt(digits.substr(i, n), 10), n * 3 + 1, bb);
i += n;
}
return new QrSegment(QrSegment.Mode.NUMERIC, digits.length, bb);
}
static makeAlphanumeric(text) {
if (!QrSegment.isAlphanumeric(text))
throw new RangeError("String contains unencodable characters in alphanumeric mode");
let bb = [];
let i;
for (i = 0;i + 2 <= text.length; i += 2) {
let temp = QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
appendBits(temp, 11, bb);
}
if (i < text.length)
appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6, bb);
return new QrSegment(QrSegment.Mode.ALPHANUMERIC, text.length, bb);
}
static makeSegments(text) {
if (text == "")
return [];
else if (QrSegment.isNumeric(text))
return [QrSegment.makeNumeric(text)];
else if (QrSegment.isAlphanumeric(text))
return [QrSegment.makeAlphanumeric(text)];
else
return [QrSegment.makeBytes(QrSegment.toUtf8ByteArray(text))];
}
static makeEci(assignVal) {
let bb = [];
if (assignVal < 0)
throw new RangeError("ECI assignment value out of range");
else if (assignVal < 1 << 7)
appendBits(assignVal, 8, bb);
else if (assignVal < 1 << 14) {
appendBits(2, 2, bb);
appendBits(assignVal, 14, bb);
} else if (assignVal < 1e6) {
appendBits(6, 3, bb);
appendBits(assignVal, 21, bb);
} else
throw new RangeError("ECI assignment value out of range");
return new QrSegment(QrSegment.Mode.ECI, 0, bb);
}
static isNumeric(text) {
return QrSegment.NUMERIC_REGEX.test(text);
}
static isAlphanumeric(text) {
return QrSegment.ALPHANUMERIC_REGEX.test(text);
}
getData() {
return this.bitData.slice();
}
static getTotalBits(segs, version) {
let result = 0;
for (const seg of segs) {
const ccbits = seg.mode.numCharCountBits(version);
if (seg.numChars >= 1 << ccbits)
return Infinity;
result += 4 + ccbits + seg.bitData.length;
}
return result;
}
static toUtf8ByteArray(str) {
str = encodeURI(str);
let result = [];
for (let i = 0;i < str.length; i++) {
if (str.charAt(i) != "%")
result.push(str.charCodeAt(i));
else {
result.push(parseInt(str.substr(i + 1, 2), 16));
i += 2;
}
}
return result;
}
}
QrSegment.NUMERIC_REGEX = /^[0-9]*$/;
QrSegment.ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+.\/:-]*$/;
QrSegment.ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
qrcodegen2.QrSegment = QrSegment;
})(qrcodegen || (qrcodegen = {}));
(function(qrcodegen2) {
var QrCode;
(function(QrCode2) {
class Ecc {
constructor(ordinal, formatBits) {
this.ordinal = ordinal;
this.formatBits = formatBits;
}
}
Ecc.LOW = new Ecc(0, 1);
Ecc.MEDIUM = new Ecc(1, 0);
Ecc.QUARTILE = new Ecc(2, 3);
Ecc.HIGH = new Ecc(3, 2);
QrCode2.Ecc = Ecc;
})(QrCode = qrcodegen2.QrCode || (qrcodegen2.QrCode = {}));
})(qrcodegen || (qrcodegen = {}));
(function(qrcodegen2) {
var QrSegment;
(function(QrSegment2) {
class Mode {
constructor(modeBits, numBitsCharCount) {
this.modeBits = modeBits;
this.numBitsCharCount = numBitsCharCount;
}
numCharCountBits(ver) {
return this.numBitsCharCount[Math.floor((ver + 7) / 17)];
}
}
Mode.NUMERIC = new Mode(1, [10, 12, 14]);
Mode.ALPHANUMERIC = new Mode(2, [9, 11, 13]);
Mode.BYTE = new Mode(4, [8, 16, 16]);
Mode.KANJI = new Mode(8, [8, 10, 12]);
Mode.ECI = new Mode(7, [0, 0, 0]);
QrSegment2.Mode = Mode;
})(QrSegment = qrcodegen2.QrSegment || (qrcodegen2.QrSegment = {}));
})(qrcodegen || (qrcodegen = {}));
// js/landing-qrcode.js
function toSvgString(qr, border, lightColor, darkColor) {
if (border < 0)
throw new RangeError("Border must be non-negative");
let parts = [];
for (let y = 0;y < qr.size; y++) {
for (let x = 0;x < qr.size; x++) {
if (qr.getModule(x, y))
parts.push(`M${x + border},${y + border}h1v1h-1z`);
}
}
return `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 ${qr.size + border * 2} ${qr.size + border * 2}" stroke="none" style="width: 100%;" class="object-fit"><rect width="100%" height="100%" fill="${lightColor}"/><path d="${parts.join(" ")}" fill="${darkColor}"/></svg>
`;
}
function generateQrCode(text) {
return toSvgString(qrcodegen.QrCode.encodeText(text, qrcodegen.QrCode.Ecc.MEDIUM), 3, "#ffffff", "#000000");
}
var svg = generateQrCode(window.location.origin + "/signup.html");
document.getElementById("hero-svg").innerHTML = svg;
document.getElementById("hero-svg").classList.remove("uk-placeholder");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -6,15 +6,31 @@
<title>Echoes Behind Closed Doors</title>
<!-- favicons -->
<link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon-xhq3hzfq.png" />
<link rel="icon" type="image/x-icon" href="./favicon-4nk2rrtb.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32-sqpfcyy0.png" />
<link rel="icon" type="image/png" sizes="16x16" href="./favicon-16x16-6gdgm8kp.png" />
<link rel="manifest" href="./site-h99v0pws.webmanifest" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/png/apple-touch-icon.png"
/>
<link rel="icon" type="image/x-icon" href="/ico/favicon.ico" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/png/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/png/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<!-- TODO: social share -->
<!-- TODO: temporary ui bundling fix (https://github.com/oven-sh/bun/issues/17243) -->
<!-- ui stuff -->
<!-- temporary ui bundling fix (https://github.com/oven-sh/bun/issues/17243) -->
<script type="module" src="/js/ebcd-common-franken-ui.js"></script>
<link
rel="stylesheet"
href="https://unpkg.com/franken-ui@2.0.0-internal.42/dist/css/core.min.css"
@ -71,7 +87,6 @@
U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@theme {
--font-sans: "Geist", sans-serif;
--font-mono: "'Geist Mono'", monospace;
@ -80,7 +95,6 @@
font-family: Geist, sans-serif;
font-feature-settings: "liga" 1, "calt" 1; /* fix for Chrome */
}
@media (max-width: 768px) {
.uk-subnav > * > :first-child {
padding-left: 0.5rem !important;
@ -88,12 +102,7 @@
}
}
</style>
<!-- franken-ui -->
<!-- TODO: temporary ui bundling fix (see above) -->
<!-- <link rel="stylesheet" href="/css/styles.css" /> -->
<script type="module" crossorigin src="./index-ra0608an.js"></script></head>
</head>
<body class="bg-background text-foreground">
<main>
<section
@ -120,8 +129,12 @@
data-uk-sticky="position:top"
>
<div class="flex flex-row gap-2">
<img src="./logo-icon-1yfzf2ex.png" alt="wirm logo" class="!h-[2.5rem]" />
<img src="./logo-wordmark-blue-x0tvrafq.png" alt="wirm wordmark" class="!h-[2.5rem]" />
<img src="/png/logo-icon.png" alt="wirm logo" class="!h-[2.5rem]" />
<img
src="/png/logo-wordmark-blue.png"
alt="wirm wordmark"
class="!h-[2.5rem]"
/>
</div>
<nav class="flex">
@ -135,21 +148,21 @@
<div data-uk-dropdown="mode: click">
<ul class="uk-dropdown-nav uk-nav">
<li id="nav-auth-logIn">
<a href="/login">Log In</a>
<a href="/login.html">Log In</a>
</li>
<li id="nav-auth-signUp">
<a href="/signup">Sign Up</a>
</li>
<li id="nav-auth-logOut">
<a href="/profile?logOut=true">Log Out</a>
<a href="/signup.html">Sign Up</a>
</li>
<li id="nav-auth-profile">
<a href="/profile">Profile</a>
<a href="/profile.html">Profile</a>
</li>
<li id="nav-auth-logOut">
<a href="/profile.html?logOut=true">Log Out</a>
</li>
</ul>
</div>
</li>
<li><a href="/booth">Manage Booth</a></li>
<li><a href="/booth.html">Manage Booth</a></li>
</ul>
</nav>
</header>
@ -274,6 +287,7 @@
</section>
</main>
<script type="module" src="/js/ebcd-landing-qrcode.js"></script>
<script type="module" src="/js/ebcd-common-navbar.js"></script>
</body>
</html>

0
Web/public/js/auth.js Normal file
View file

View file

@ -0,0 +1,17 @@
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.3.0/firebase-app.js";
import { getDatabase } from "https://www.gstatic.com/firebasejs/11.3.0/firebase-database.js";
import { getAuth } from "https://www.gstatic.com/firebasejs/11.3.0/firebase-auth.js";
const firebaseConfig = {
apiKey: "AIzaSyARm-eSymd2Q3AyxJXiAiiUzsXGmc6T72I",
authDomain: "echoesbehindcloseddoors.firebaseapp.com",
databaseURL: "https://echoesbehindcloseddoors-default-rtdb.asia-southeast1.firebasedatabase.app",
projectId: "echoesbehindcloseddoors",
storageBucket: "echoesbehindcloseddoors.firebasestorage.app",
messagingSenderId: "905742343227",
appId: "1:905742343227:web:19d04f77371ef1952c901e"
};
export const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const database = getDatabase(app);

View file

@ -0,0 +1,23 @@
import "https://unpkg.com/franken-ui@2.0.0-internal.42/dist/js/core.iife.js";
import "https://unpkg.com/franken-ui@2.0.0-internal.42/dist/js/icon.iife.js";
const htmlElement = document.documentElement;
const __FRANKEN__ = JSON.parse(
localStorage.getItem("__FRANKEN__") || "{}"
);
if (
__FRANKEN__.mode === "dark" ||
(!__FRANKEN__.mode &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
htmlElement.classList.add("dark");
} else {
htmlElement.classList.remove("dark");
}
htmlElement.classList.add(__FRANKEN__.theme || "uk-theme-blue");
htmlElement.classList.add(__FRANKEN__.radii || "uk-radii-md");
htmlElement.classList.add(__FRANKEN__.shadows || "uk-shadows-sm");
htmlElement.classList.add(__FRANKEN__.font || "uk-font-sm");

View file

@ -0,0 +1,20 @@
import { app, auth } from "./ebcd-common-firebase.js";
// the navbar has the following li elements in a dropdown:
// - nav-auth-logIn
// - nav-auth-signUp
// - nav-auth-profile
// - nav-auth-logOut
// are we logged in?
auth.onAuthStateChanged((user) => {
if (user) {
// signed in, only show the profile and log out links
document.getElementById("nav-auth-logIn").style.display = "none";
document.getElementById("nav-auth-signUp").style.display = "none";
} else {
// not signed in, only show the log in and sign up links
document.getElementById("nav-auth-profile").style.display = "none";
document.getElementById("nav-auth-logOut").style.display = "none";
}
});

View file

@ -0,0 +1,56 @@
/*
* modified from the typscript QR Code generator input demo
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
import { qrcodegen } from "./nayuki-qrcodegen-v1.8.0.js";
function toSvgString(qr, border, lightColor, darkColor) {
if (border < 0) throw new RangeError("Border must be non-negative");
let parts = [];
for (let y = 0; y < qr.size; y++) {
for (let x = 0; x < qr.size; x++) {
if (qr.getModule(x, y))
parts.push(`M${x + border},${y + border}h1v1h-1z`);
}
}
return `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 ${
qr.size + border * 2
} ${
qr.size + border * 2
}" stroke="none" style="width: 100%;" class="object-fit"><rect width="100%" height="100%" fill="${lightColor}"/><path d="${parts.join(
" "
)}" fill="${darkColor}"/></svg>
`;
}
function generateQrCode(text) {
return toSvgString(
qrcodegen.QrCode.encodeText(text, qrcodegen.QrCode.Ecc.MEDIUM),
3,
"#ffffff", // light
"#000000" // dark
);
}
const svg = generateQrCode(window.location.origin + "/signup.html");
document.getElementById("hero-svg").innerHTML = svg;
document.getElementById("hero-svg").classList.remove("uk-placeholder");

View file

@ -0,0 +1,837 @@
/*
* QR Code generator library (compiled from TypeScript)
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
"use strict";
export var qrcodegen;
(function (qrcodegen) {
/*---- QR Code symbol class ----*/
/*
* A QR Code symbol, which is a type of two-dimension barcode.
* Invented by Denso Wave and described in the ISO/IEC 18004 standard.
* Instances of this class represent an immutable square grid of dark and light cells.
* The class provides static factory functions to create a QR Code from text or binary data.
* The class covers the QR Code Model 2 specification, supporting all versions (sizes)
* from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
*
* Ways to create a QR Code object:
* - High level: Take the payload data and call QrCode.encodeText() or QrCode.encodeBinary().
* - Mid level: Custom-make the list of segments and call QrCode.encodeSegments().
* - Low level: Custom-make the array of data codeword bytes (including
* segment headers and final padding, excluding error correction codewords),
* supply the appropriate version number, and call the QrCode() constructor.
* (Note that all ways require supplying the desired error correction level.)
*/
class QrCode {
/*-- Constructor (low level) and fields --*/
// Creates a new QR Code with the given version number,
// error correction level, data codeword bytes, and mask number.
// This is a low-level API that most users should not use directly.
// A mid-level API is the encodeSegments() function.
constructor(
// The version number of this QR Code, which is between 1 and 40 (inclusive).
// This determines the size of this barcode.
version,
// The error correction level used in this QR Code.
errorCorrectionLevel, dataCodewords, msk) {
this.version = version;
this.errorCorrectionLevel = errorCorrectionLevel;
// The modules of this QR Code (false = light, true = dark).
// Immutable after constructor finishes. Accessed through getModule().
this.modules = [];
// Indicates function modules that are not subjected to masking. Discarded when constructor finishes.
this.isFunction = [];
// Check scalar arguments
if (version < QrCode.MIN_VERSION || version > QrCode.MAX_VERSION)
throw new RangeError("Version value out of range");
if (msk < -1 || msk > 7)
throw new RangeError("Mask value out of range");
this.size = version * 4 + 17;
// Initialize both grids to be size*size arrays of Boolean false
let row = [];
for (let i = 0; i < this.size; i++)
row.push(false);
for (let i = 0; i < this.size; i++) {
this.modules.push(row.slice()); // Initially all light
this.isFunction.push(row.slice());
}
// Compute ECC, draw modules
this.drawFunctionPatterns();
const allCodewords = this.addEccAndInterleave(dataCodewords);
this.drawCodewords(allCodewords);
// Do masking
if (msk == -1) { // Automatically choose best mask
let minPenalty = 1000000000;
for (let i = 0; i < 8; i++) {
this.applyMask(i);
this.drawFormatBits(i);
const penalty = this.getPenaltyScore();
if (penalty < minPenalty) {
msk = i;
minPenalty = penalty;
}
this.applyMask(i); // Undoes the mask due to XOR
}
}
assert(0 <= msk && msk <= 7);
this.mask = msk;
this.applyMask(msk); // Apply the final choice of mask
this.drawFormatBits(msk); // Overwrite old format bits
this.isFunction = [];
}
/*-- Static factory functions (high level) --*/
// Returns a QR Code representing the given Unicode text string at the given error correction level.
// As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer
// Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible
// QR Code version is automatically chosen for the output. The ECC level of the result may be higher than the
// ecl argument if it can be done without increasing the version.
static encodeText(text, ecl) {
const segs = qrcodegen.QrSegment.makeSegments(text);
return QrCode.encodeSegments(segs, ecl);
}
// Returns a QR Code representing the given binary data at the given error correction level.
// This function always encodes using the binary segment mode, not any text mode. The maximum number of
// bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
// The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.
static encodeBinary(data, ecl) {
const seg = qrcodegen.QrSegment.makeBytes(data);
return QrCode.encodeSegments([seg], ecl);
}
/*-- Static factory functions (mid level) --*/
// Returns a QR Code representing the given segments with the given encoding parameters.
// The smallest possible QR Code version within the given range is automatically
// chosen for the output. Iff boostEcl is true, then the ECC level of the result
// may be higher than the ecl argument if it can be done without increasing the
// version. The mask number is either between 0 to 7 (inclusive) to force that
// mask, or -1 to automatically choose an appropriate mask (which may be slow).
// This function allows the user to create a custom sequence of segments that switches
// between modes (such as alphanumeric and byte) to encode text in less space.
// This is a mid-level API; the high-level API is encodeText() and encodeBinary().
static encodeSegments(segs, ecl, minVersion = 1, maxVersion = 40, mask = -1, boostEcl = true) {
if (!(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION)
|| mask < -1 || mask > 7)
throw new RangeError("Invalid value");
// Find the minimal version number to use
let version;
let dataUsedBits;
for (version = minVersion;; version++) {
const dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; // Number of data bits available
const usedBits = QrSegment.getTotalBits(segs, version);
if (usedBits <= dataCapacityBits) {
dataUsedBits = usedBits;
break; // This version number is found to be suitable
}
if (version >= maxVersion) // All versions in the range could not fit the given data
throw new RangeError("Data too long");
}
// Increase the error correction level while the data still fits in the current version number
for (const newEcl of [QrCode.Ecc.MEDIUM, QrCode.Ecc.QUARTILE, QrCode.Ecc.HIGH]) { // From low to high
if (boostEcl && dataUsedBits <= QrCode.getNumDataCodewords(version, newEcl) * 8)
ecl = newEcl;
}
// Concatenate all segments to create the data bit string
let bb = [];
for (const seg of segs) {
appendBits(seg.mode.modeBits, 4, bb);
appendBits(seg.numChars, seg.mode.numCharCountBits(version), bb);
for (const b of seg.getData())
bb.push(b);
}
assert(bb.length == dataUsedBits);
// Add terminator and pad up to a byte if applicable
const dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8;
assert(bb.length <= dataCapacityBits);
appendBits(0, Math.min(4, dataCapacityBits - bb.length), bb);
appendBits(0, (8 - bb.length % 8) % 8, bb);
assert(bb.length % 8 == 0);
// Pad with alternating bytes until data capacity is reached
for (let padByte = 0xEC; bb.length < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
appendBits(padByte, 8, bb);
// Pack bits into bytes in big endian
let dataCodewords = [];
while (dataCodewords.length * 8 < bb.length)
dataCodewords.push(0);
bb.forEach((b, i) => dataCodewords[i >>> 3] |= b << (7 - (i & 7)));
// Create the QR Code object
return new QrCode(version, ecl, dataCodewords, mask);
}
/*-- Accessor methods --*/
// Returns the color of the module (pixel) at the given coordinates, which is false
// for light or true for dark. The top left corner has the coordinates (x=0, y=0).
// If the given coordinates are out of bounds, then false (light) is returned.
getModule(x, y) {
return 0 <= x && x < this.size && 0 <= y && y < this.size && this.modules[y][x];
}
/*-- Private helper methods for constructor: Drawing function modules --*/
// Reads this object's version field, and draws and marks all function modules.
drawFunctionPatterns() {
// Draw horizontal and vertical timing patterns
for (let i = 0; i < this.size; i++) {
this.setFunctionModule(6, i, i % 2 == 0);
this.setFunctionModule(i, 6, i % 2 == 0);
}
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
this.drawFinderPattern(3, 3);
this.drawFinderPattern(this.size - 4, 3);
this.drawFinderPattern(3, this.size - 4);
// Draw numerous alignment patterns
const alignPatPos = this.getAlignmentPatternPositions();
const numAlign = alignPatPos.length;
for (let i = 0; i < numAlign; i++) {
for (let j = 0; j < numAlign; j++) {
// Don't draw on the three finder corners
if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0))
this.drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
}
}
// Draw configuration data
this.drawFormatBits(0); // Dummy mask value; overwritten later in the constructor
this.drawVersion();
}
// Draws two copies of the format bits (with its own error correction code)
// based on the given mask and this object's error correction level field.
drawFormatBits(mask) {
// Calculate error correction code and pack bits
const data = this.errorCorrectionLevel.formatBits << 3 | mask; // errCorrLvl is uint2, mask is uint3
let rem = data;
for (let i = 0; i < 10; i++)
rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
const bits = (data << 10 | rem) ^ 0x5412; // uint15
assert(bits >>> 15 == 0);
// Draw first copy
for (let i = 0; i <= 5; i++)
this.setFunctionModule(8, i, getBit(bits, i));
this.setFunctionModule(8, 7, getBit(bits, 6));
this.setFunctionModule(8, 8, getBit(bits, 7));
this.setFunctionModule(7, 8, getBit(bits, 8));
for (let i = 9; i < 15; i++)
this.setFunctionModule(14 - i, 8, getBit(bits, i));
// Draw second copy
for (let i = 0; i < 8; i++)
this.setFunctionModule(this.size - 1 - i, 8, getBit(bits, i));
for (let i = 8; i < 15; i++)
this.setFunctionModule(8, this.size - 15 + i, getBit(bits, i));
this.setFunctionModule(8, this.size - 8, true); // Always dark
}
// Draws two copies of the version bits (with its own error correction code),
// based on this object's version field, iff 7 <= version <= 40.
drawVersion() {
if (this.version < 7)
return;
// Calculate error correction code and pack bits
let rem = this.version; // version is uint6, in the range [7, 40]
for (let i = 0; i < 12; i++)
rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25);
const bits = this.version << 12 | rem; // uint18
assert(bits >>> 18 == 0);
// Draw two copies
for (let i = 0; i < 18; i++) {
const color = getBit(bits, i);
const a = this.size - 11 + i % 3;
const b = Math.floor(i / 3);
this.setFunctionModule(a, b, color);
this.setFunctionModule(b, a, color);
}
}
// Draws a 9*9 finder pattern including the border separator,
// with the center module at (x, y). Modules can be out of bounds.
drawFinderPattern(x, y) {
for (let dy = -4; dy <= 4; dy++) {
for (let dx = -4; dx <= 4; dx++) {
const dist = Math.max(Math.abs(dx), Math.abs(dy)); // Chebyshev/infinity norm
const xx = x + dx;
const yy = y + dy;
if (0 <= xx && xx < this.size && 0 <= yy && yy < this.size)
this.setFunctionModule(xx, yy, dist != 2 && dist != 4);
}
}
}
// Draws a 5*5 alignment pattern, with the center module
// at (x, y). All modules must be in bounds.
drawAlignmentPattern(x, y) {
for (let dy = -2; dy <= 2; dy++) {
for (let dx = -2; dx <= 2; dx++)
this.setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) != 1);
}
}
// Sets the color of a module and marks it as a function module.
// Only used by the constructor. Coordinates must be in bounds.
setFunctionModule(x, y, isDark) {
this.modules[y][x] = isDark;
this.isFunction[y][x] = true;
}
/*-- Private helper methods for constructor: Codewords and masking --*/
// Returns a new byte string representing the given data with the appropriate error correction
// codewords appended to it, based on this object's version and error correction level.
addEccAndInterleave(data) {
const ver = this.version;
const ecl = this.errorCorrectionLevel;
if (data.length != QrCode.getNumDataCodewords(ver, ecl))
throw new RangeError("Invalid argument");
// Calculate parameter numbers
const numBlocks = QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
const blockEccLen = QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver];
const rawCodewords = Math.floor(QrCode.getNumRawDataModules(ver) / 8);
const numShortBlocks = numBlocks - rawCodewords % numBlocks;
const shortBlockLen = Math.floor(rawCodewords / numBlocks);
// Split data into blocks and append ECC to each block
let blocks = [];
const rsDiv = QrCode.reedSolomonComputeDivisor(blockEccLen);
for (let i = 0, k = 0; i < numBlocks; i++) {
let dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
k += dat.length;
const ecc = QrCode.reedSolomonComputeRemainder(dat, rsDiv);
if (i < numShortBlocks)
dat.push(0);
blocks.push(dat.concat(ecc));
}
// Interleave (not concatenate) the bytes from every block into a single sequence
let result = [];
for (let i = 0; i < blocks[0].length; i++) {
blocks.forEach((block, j) => {
// Skip the padding byte in short blocks
if (i != shortBlockLen - blockEccLen || j >= numShortBlocks)
result.push(block[i]);
});
}
assert(result.length == rawCodewords);
return result;
}
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
// data area of this QR Code. Function modules need to be marked off before this is called.
drawCodewords(data) {
if (data.length != Math.floor(QrCode.getNumRawDataModules(this.version) / 8))
throw new RangeError("Invalid argument");
let i = 0; // Bit index into the data
// Do the funny zigzag scan
for (let right = this.size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
if (right == 6)
right = 5;
for (let vert = 0; vert < this.size; vert++) { // Vertical counter
for (let j = 0; j < 2; j++) {
const x = right - j; // Actual x coordinate
const upward = ((right + 1) & 2) == 0;
const y = upward ? this.size - 1 - vert : vert; // Actual y coordinate
if (!this.isFunction[y][x] && i < data.length * 8) {
this.modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));
i++;
}
// If this QR Code has any remainder bits (0 to 7), they were assigned as
// 0/false/light by the constructor and are left unchanged by this method
}
}
}
assert(i == data.length * 8);
}
// XORs the codeword modules in this QR Code with the given mask pattern.
// The function modules must be marked and the codeword bits must be drawn
// before masking. Due to the arithmetic of XOR, calling applyMask() with
// the same mask value a second time will undo the mask. A final well-formed
// QR Code needs exactly one (not zero, two, etc.) mask applied.
applyMask(mask) {
if (mask < 0 || mask > 7)
throw new RangeError("Mask value out of range");
for (let y = 0; y < this.size; y++) {
for (let x = 0; x < this.size; x++) {
let invert;
switch (mask) {
case 0:
invert = (x + y) % 2 == 0;
break;
case 1:
invert = y % 2 == 0;
break;
case 2:
invert = x % 3 == 0;
break;
case 3:
invert = (x + y) % 3 == 0;
break;
case 4:
invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 == 0;
break;
case 5:
invert = x * y % 2 + x * y % 3 == 0;
break;
case 6:
invert = (x * y % 2 + x * y % 3) % 2 == 0;
break;
case 7:
invert = ((x + y) % 2 + x * y % 3) % 2 == 0;
break;
default: throw new Error("Unreachable");
}
if (!this.isFunction[y][x] && invert)
this.modules[y][x] = !this.modules[y][x];
}
}
}
// Calculates and returns the penalty score based on state of this QR Code's current modules.
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
getPenaltyScore() {
let result = 0;
// Adjacent modules in row having same color, and finder-like patterns
for (let y = 0; y < this.size; y++) {
let runColor = false;
let runX = 0;
let runHistory = [0, 0, 0, 0, 0, 0, 0];
for (let x = 0; x < this.size; x++) {
if (this.modules[y][x] == runColor) {
runX++;
if (runX == 5)
result += QrCode.PENALTY_N1;
else if (runX > 5)
result++;
}
else {
this.finderPenaltyAddHistory(runX, runHistory);
if (!runColor)
result += this.finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3;
runColor = this.modules[y][x];
runX = 1;
}
}
result += this.finderPenaltyTerminateAndCount(runColor, runX, runHistory) * QrCode.PENALTY_N3;
}
// Adjacent modules in column having same color, and finder-like patterns
for (let x = 0; x < this.size; x++) {
let runColor = false;
let runY = 0;
let runHistory = [0, 0, 0, 0, 0, 0, 0];
for (let y = 0; y < this.size; y++) {
if (this.modules[y][x] == runColor) {
runY++;
if (runY == 5)
result += QrCode.PENALTY_N1;
else if (runY > 5)
result++;
}
else {
this.finderPenaltyAddHistory(runY, runHistory);
if (!runColor)
result += this.finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3;
runColor = this.modules[y][x];
runY = 1;
}
}
result += this.finderPenaltyTerminateAndCount(runColor, runY, runHistory) * QrCode.PENALTY_N3;
}
// 2*2 blocks of modules having same color
for (let y = 0; y < this.size - 1; y++) {
for (let x = 0; x < this.size - 1; x++) {
const color = this.modules[y][x];
if (color == this.modules[y][x + 1] &&
color == this.modules[y + 1][x] &&
color == this.modules[y + 1][x + 1])
result += QrCode.PENALTY_N2;
}
}
// Balance of dark and light modules
let dark = 0;
for (const row of this.modules)
dark = row.reduce((sum, color) => sum + (color ? 1 : 0), dark);
const total = this.size * this.size; // Note that size is odd, so dark/total != 1/2
// Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)%
const k = Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1;
assert(0 <= k && k <= 9);
result += k * QrCode.PENALTY_N4;
assert(0 <= result && result <= 2568888); // Non-tight upper bound based on default values of PENALTY_N1, ..., N4
return result;
}
/*-- Private helper functions --*/
// Returns an ascending list of positions of alignment patterns for this version number.
// Each position is in the range [0,177), and are used on both the x and y axes.
// This could be implemented as lookup table of 40 variable-length lists of integers.
getAlignmentPatternPositions() {
if (this.version == 1)
return [];
else {
const numAlign = Math.floor(this.version / 7) + 2;
const step = (this.version == 32) ? 26 :
Math.ceil((this.version * 4 + 4) / (numAlign * 2 - 2)) * 2;
let result = [6];
for (let pos = this.size - 7; result.length < numAlign; pos -= step)
result.splice(1, 0, pos);
return result;
}
}
// Returns the number of data bits that can be stored in a QR Code of the given version number, after
// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
static getNumRawDataModules(ver) {
if (ver < QrCode.MIN_VERSION || ver > QrCode.MAX_VERSION)
throw new RangeError("Version number out of range");
let result = (16 * ver + 128) * ver + 64;
if (ver >= 2) {
const numAlign = Math.floor(ver / 7) + 2;
result -= (25 * numAlign - 10) * numAlign - 55;
if (ver >= 7)
result -= 36;
}
assert(208 <= result && result <= 29648);
return result;
}
// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
// QR Code of the given version number and error correction level, with remainder bits discarded.
// This stateless pure function could be implemented as a (40*4)-cell lookup table.
static getNumDataCodewords(ver, ecl) {
return Math.floor(QrCode.getNumRawDataModules(ver) / 8) -
QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver] *
QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
}
// Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be
// implemented as a lookup table over all possible parameter values, instead of as an algorithm.
static reedSolomonComputeDivisor(degree) {
if (degree < 1 || degree > 255)
throw new RangeError("Degree out of range");
// Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1.
// For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array [255, 8, 93].
let result = [];
for (let i = 0; i < degree - 1; i++)
result.push(0);
result.push(1); // Start off with the monomial x^0
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
// and drop the highest monomial term which is always 1x^degree.
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
let root = 1;
for (let i = 0; i < degree; i++) {
// Multiply the current product by (x - r^i)
for (let j = 0; j < result.length; j++) {
result[j] = QrCode.reedSolomonMultiply(result[j], root);
if (j + 1 < result.length)
result[j] ^= result[j + 1];
}
root = QrCode.reedSolomonMultiply(root, 0x02);
}
return result;
}
// Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials.
static reedSolomonComputeRemainder(data, divisor) {
let result = divisor.map(_ => 0);
for (const b of data) { // Polynomial division
const factor = b ^ result.shift();
result.push(0);
divisor.forEach((coef, i) => result[i] ^= QrCode.reedSolomonMultiply(coef, factor));
}
return result;
}
// Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result
// are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8.
static reedSolomonMultiply(x, y) {
if (x >>> 8 != 0 || y >>> 8 != 0)
throw new RangeError("Byte out of range");
// Russian peasant multiplication
let z = 0;
for (let i = 7; i >= 0; i--) {
z = (z << 1) ^ ((z >>> 7) * 0x11D);
z ^= ((y >>> i) & 1) * x;
}
assert(z >>> 8 == 0);
return z;
}
// Can only be called immediately after a light run is added, and
// returns either 0, 1, or 2. A helper function for getPenaltyScore().
finderPenaltyCountPatterns(runHistory) {
const n = runHistory[1];
assert(n <= this.size * 3);
const core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n;
return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0)
+ (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0);
}
// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore().
finderPenaltyTerminateAndCount(currentRunColor, currentRunLength, runHistory) {
if (currentRunColor) { // Terminate dark run
this.finderPenaltyAddHistory(currentRunLength, runHistory);
currentRunLength = 0;
}
currentRunLength += this.size; // Add light border to final run
this.finderPenaltyAddHistory(currentRunLength, runHistory);
return this.finderPenaltyCountPatterns(runHistory);
}
// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore().
finderPenaltyAddHistory(currentRunLength, runHistory) {
if (runHistory[0] == 0)
currentRunLength += this.size; // Add light border to initial run
runHistory.pop();
runHistory.unshift(currentRunLength);
}
}
/*-- Constants and tables --*/
// The minimum version number supported in the QR Code Model 2 standard.
QrCode.MIN_VERSION = 1;
// The maximum version number supported in the QR Code Model 2 standard.
QrCode.MAX_VERSION = 40;
// For use in getPenaltyScore(), when evaluating which mask is best.
QrCode.PENALTY_N1 = 3;
QrCode.PENALTY_N2 = 3;
QrCode.PENALTY_N3 = 40;
QrCode.PENALTY_N4 = 10;
QrCode.ECC_CODEWORDS_PER_BLOCK = [
// Version: (note that index 0 is for padding, and is set to an illegal value)
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
[-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
[-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28],
[-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
[-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // High
];
QrCode.NUM_ERROR_CORRECTION_BLOCKS = [
// Version: (note that index 0 is for padding, and is set to an illegal value)
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
[-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25],
[-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49],
[-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68],
[-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81], // High
];
qrcodegen.QrCode = QrCode;
// Appends the given number of low-order bits of the given value
// to the given buffer. Requires 0 <= len <= 31 and 0 <= val < 2^len.
function appendBits(val, len, bb) {
if (len < 0 || len > 31 || val >>> len != 0)
throw new RangeError("Value out of range");
for (let i = len - 1; i >= 0; i--) // Append bit by bit
bb.push((val >>> i) & 1);
}
// Returns true iff the i'th bit of x is set to 1.
function getBit(x, i) {
return ((x >>> i) & 1) != 0;
}
// Throws an exception if the given condition is false.
function assert(cond) {
if (!cond)
throw new Error("Assertion error");
}
/*---- Data segment class ----*/
/*
* A segment of character/binary/control data in a QR Code symbol.
* Instances of this class are immutable.
* The mid-level way to create a segment is to take the payload data
* and call a static factory function such as QrSegment.makeNumeric().
* The low-level way to create a segment is to custom-make the bit buffer
* and call the QrSegment() constructor with appropriate values.
* This segment class imposes no length restrictions, but QR Codes have restrictions.
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
*/
class QrSegment {
/*-- Constructor (low level) and fields --*/
// Creates a new QR Code segment with the given attributes and data.
// The character count (numChars) must agree with the mode and the bit buffer length,
// but the constraint isn't checked. The given bit buffer is cloned and stored.
constructor(
// The mode indicator of this segment.
mode,
// The length of this segment's unencoded data. Measured in characters for
// numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
// Always zero or positive. Not the same as the data's bit length.
numChars,
// The data bits of this segment. Accessed through getData().
bitData) {
this.mode = mode;
this.numChars = numChars;
this.bitData = bitData;
if (numChars < 0)
throw new RangeError("Invalid argument");
this.bitData = bitData.slice(); // Make defensive copy
}
/*-- Static factory functions (mid level) --*/
// Returns a segment representing the given binary data encoded in
// byte mode. All input byte arrays are acceptable. Any text string
// can be converted to UTF-8 bytes and encoded as a byte mode segment.
static makeBytes(data) {
let bb = [];
for (const b of data)
appendBits(b, 8, bb);
return new QrSegment(QrSegment.Mode.BYTE, data.length, bb);
}
// Returns a segment representing the given string of decimal digits encoded in numeric mode.
static makeNumeric(digits) {
if (!QrSegment.isNumeric(digits))
throw new RangeError("String contains non-numeric characters");
let bb = [];
for (let i = 0; i < digits.length;) { // Consume up to 3 digits per iteration
const n = Math.min(digits.length - i, 3);
appendBits(parseInt(digits.substr(i, n), 10), n * 3 + 1, bb);
i += n;
}
return new QrSegment(QrSegment.Mode.NUMERIC, digits.length, bb);
}
// Returns a segment representing the given text string encoded in alphanumeric mode.
// The characters allowed are: 0 to 9, A to Z (uppercase only), space,
// dollar, percent, asterisk, plus, hyphen, period, slash, colon.
static makeAlphanumeric(text) {
if (!QrSegment.isAlphanumeric(text))
throw new RangeError("String contains unencodable characters in alphanumeric mode");
let bb = [];
let i;
for (i = 0; i + 2 <= text.length; i += 2) { // Process groups of 2
let temp = QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
appendBits(temp, 11, bb);
}
if (i < text.length) // 1 character remaining
appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6, bb);
return new QrSegment(QrSegment.Mode.ALPHANUMERIC, text.length, bb);
}
// Returns a new mutable list of zero or more segments to represent the given Unicode text string.
// The result may use various segment modes and switch modes to optimize the length of the bit stream.
static makeSegments(text) {
// Select the most efficient segment encoding automatically
if (text == "")
return [];
else if (QrSegment.isNumeric(text))
return [QrSegment.makeNumeric(text)];
else if (QrSegment.isAlphanumeric(text))
return [QrSegment.makeAlphanumeric(text)];
else
return [QrSegment.makeBytes(QrSegment.toUtf8ByteArray(text))];
}
// Returns a segment representing an Extended Channel Interpretation
// (ECI) designator with the given assignment value.
static makeEci(assignVal) {
let bb = [];
if (assignVal < 0)
throw new RangeError("ECI assignment value out of range");
else if (assignVal < (1 << 7))
appendBits(assignVal, 8, bb);
else if (assignVal < (1 << 14)) {
appendBits(0b10, 2, bb);
appendBits(assignVal, 14, bb);
}
else if (assignVal < 1000000) {
appendBits(0b110, 3, bb);
appendBits(assignVal, 21, bb);
}
else
throw new RangeError("ECI assignment value out of range");
return new QrSegment(QrSegment.Mode.ECI, 0, bb);
}
// Tests whether the given string can be encoded as a segment in numeric mode.
// A string is encodable iff each character is in the range 0 to 9.
static isNumeric(text) {
return QrSegment.NUMERIC_REGEX.test(text);
}
// Tests whether the given string can be encoded as a segment in alphanumeric mode.
// A string is encodable iff each character is in the following set: 0 to 9, A to Z
// (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
static isAlphanumeric(text) {
return QrSegment.ALPHANUMERIC_REGEX.test(text);
}
/*-- Methods --*/
// Returns a new copy of the data bits of this segment.
getData() {
return this.bitData.slice(); // Make defensive copy
}
// (Package-private) Calculates and returns the number of bits needed to encode the given segments at
// the given version. The result is infinity if a segment has too many characters to fit its length field.
static getTotalBits(segs, version) {
let result = 0;
for (const seg of segs) {
const ccbits = seg.mode.numCharCountBits(version);
if (seg.numChars >= (1 << ccbits))
return Infinity; // The segment's length doesn't fit the field's bit width
result += 4 + ccbits + seg.bitData.length;
}
return result;
}
// Returns a new array of bytes representing the given string encoded in UTF-8.
static toUtf8ByteArray(str) {
str = encodeURI(str);
let result = [];
for (let i = 0; i < str.length; i++) {
if (str.charAt(i) != "%")
result.push(str.charCodeAt(i));
else {
result.push(parseInt(str.substr(i + 1, 2), 16));
i += 2;
}
}
return result;
}
}
/*-- Constants --*/
// Describes precisely all strings that are encodable in numeric mode.
QrSegment.NUMERIC_REGEX = /^[0-9]*$/;
// Describes precisely all strings that are encodable in alphanumeric mode.
QrSegment.ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+.\/:-]*$/;
// The set of all legal characters in alphanumeric mode,
// where each character value maps to the index in the string.
QrSegment.ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
qrcodegen.QrSegment = QrSegment;
})(qrcodegen || (qrcodegen = {}));
/*---- Public helper enumeration ----*/
(function (qrcodegen) {
var QrCode;
(function (QrCode) {
/*
* The error correction level in a QR Code symbol. Immutable.
*/
class Ecc {
/*-- Constructor and fields --*/
constructor(
// In the range 0 to 3 (unsigned 2-bit integer).
ordinal,
// (Package-private) In the range 0 to 3 (unsigned 2-bit integer).
formatBits) {
this.ordinal = ordinal;
this.formatBits = formatBits;
}
}
/*-- Constants --*/
Ecc.LOW = new Ecc(0, 1); // The QR Code can tolerate about 7% erroneous codewords
Ecc.MEDIUM = new Ecc(1, 0); // The QR Code can tolerate about 15% erroneous codewords
Ecc.QUARTILE = new Ecc(2, 3); // The QR Code can tolerate about 25% erroneous codewords
Ecc.HIGH = new Ecc(3, 2); // The QR Code can tolerate about 30% erroneous codewords
QrCode.Ecc = Ecc;
})(QrCode = qrcodegen.QrCode || (qrcodegen.QrCode = {}));
})(qrcodegen || (qrcodegen = {}));
/*---- Public helper enumeration ----*/
(function (qrcodegen) {
var QrSegment;
(function (QrSegment) {
/*
* Describes how a segment's data bits are interpreted. Immutable.
*/
class Mode {
/*-- Constructor and fields --*/
constructor(
// The mode indicator bits, which is a uint4 value (range 0 to 15).
modeBits,
// Number of character count bits for three different version ranges.
numBitsCharCount) {
this.modeBits = modeBits;
this.numBitsCharCount = numBitsCharCount;
}
/*-- Method --*/
// (Package-private) Returns the bit width of the character count field for a segment in
// this mode in a QR Code at the given version number. The result is in the range [0, 16].
numCharCountBits(ver) {
return this.numBitsCharCount[Math.floor((ver + 7) / 17)];
}
}
/*-- Constants --*/
Mode.NUMERIC = new Mode(0x1, [10, 12, 14]);
Mode.ALPHANUMERIC = new Mode(0x2, [9, 11, 13]);
Mode.BYTE = new Mode(0x4, [8, 16, 16]);
Mode.KANJI = new Mode(0x8, [8, 10, 12]);
Mode.ECI = new Mode(0x7, [0, 0, 0]);
QrSegment.Mode = Mode;
})(QrSegment = qrcodegen.QrSegment || (qrcodegen.QrSegment = {}));
})(qrcodegen || (qrcodegen = {}));

44
Web/public/login.html Normal file
View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Echoes Behind Closed Doors</title>
<!-- favicons -->
<link
rel="apple-touch-icon"
sizes="180x180"
href="/png/apple-touch-icon.png"
/>
<link rel="icon" type="image/x-icon" href="/ico/favicon.ico" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/png/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/png/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<!-- TODO: social share -->
<!-- franken-ui -->
<link rel="stylesheet" href="/css/styles.min.css" />
<script
type="module"
src="/js/franken-ui.js"
></script>
</head>
<body class="bg-background text-foreground">
<main>
hi bro
</main>
<script type="application/javascript" src="/js/auth.js"></script>
</body>
</html>

BIN
Web/public/png/android-chrome-192x192.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Web/public/png/android-chrome-512x512.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Web/public/png/logo-wordmark.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -1,951 +0,0 @@
/*! tailwindcss v4.0.3 | MIT License | https://tailwindcss.com */
@import url("https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&display=swap");
@layer theme, base, components, utilities;
@layer theme {
:root, :host {
--font-sans: "Geist", sans-serif;
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: "'Geist Mono'", monospace;
--color-red-50: oklch(0.971 0.013 17.38);
--color-red-100: oklch(0.936 0.032 17.717);
--color-red-200: oklch(0.885 0.062 18.334);
--color-red-300: oklch(0.808 0.114 19.571);
--color-red-400: oklch(0.704 0.191 22.216);
--color-red-500: oklch(0.637 0.237 25.331);
--color-red-600: oklch(0.577 0.245 27.325);
--color-red-700: oklch(0.505 0.213 27.518);
--color-red-800: oklch(0.444 0.177 26.899);
--color-red-900: oklch(0.396 0.141 25.723);
--color-red-950: oklch(0.258 0.092 26.042);
--color-orange-50: oklch(0.98 0.016 73.684);
--color-orange-100: oklch(0.954 0.038 75.164);
--color-orange-200: oklch(0.901 0.076 70.697);
--color-orange-300: oklch(0.837 0.128 66.29);
--color-orange-400: oklch(0.75 0.183 55.934);
--color-orange-500: oklch(0.705 0.213 47.604);
--color-orange-600: oklch(0.646 0.222 41.116);
--color-orange-700: oklch(0.553 0.195 38.402);
--color-orange-800: oklch(0.47 0.157 37.304);
--color-orange-900: oklch(0.408 0.123 38.172);
--color-orange-950: oklch(0.266 0.079 36.259);
--color-amber-50: oklch(0.987 0.022 95.277);
--color-amber-100: oklch(0.962 0.059 95.617);
--color-amber-200: oklch(0.924 0.12 95.746);
--color-amber-300: oklch(0.879 0.169 91.605);
--color-amber-400: oklch(0.828 0.189 84.429);
--color-amber-500: oklch(0.769 0.188 70.08);
--color-amber-600: oklch(0.666 0.179 58.318);
--color-amber-700: oklch(0.555 0.163 48.998);
--color-amber-800: oklch(0.473 0.137 46.201);
--color-amber-900: oklch(0.414 0.112 45.904);
--color-amber-950: oklch(0.279 0.077 45.635);
--color-yellow-50: oklch(0.987 0.026 102.212);
--color-yellow-100: oklch(0.973 0.071 103.193);
--color-yellow-200: oklch(0.945 0.129 101.54);
--color-yellow-300: oklch(0.905 0.182 98.111);
--color-yellow-400: oklch(0.852 0.199 91.936);
--color-yellow-500: oklch(0.795 0.184 86.047);
--color-yellow-600: oklch(0.681 0.162 75.834);
--color-yellow-700: oklch(0.554 0.135 66.442);
--color-yellow-800: oklch(0.476 0.114 61.907);
--color-yellow-900: oklch(0.421 0.095 57.708);
--color-yellow-950: oklch(0.286 0.066 53.813);
--color-lime-50: oklch(0.986 0.031 120.757);
--color-lime-100: oklch(0.967 0.067 122.328);
--color-lime-200: oklch(0.938 0.127 124.321);
--color-lime-300: oklch(0.897 0.196 126.665);
--color-lime-400: oklch(0.841 0.238 128.85);
--color-lime-500: oklch(0.768 0.233 130.85);
--color-lime-600: oklch(0.648 0.2 131.684);
--color-lime-700: oklch(0.532 0.157 131.589);
--color-lime-800: oklch(0.453 0.124 130.933);
--color-lime-900: oklch(0.405 0.101 131.063);
--color-lime-950: oklch(0.274 0.072 132.109);
--color-green-50: oklch(0.982 0.018 155.826);
--color-green-100: oklch(0.962 0.044 156.743);
--color-green-200: oklch(0.925 0.084 155.995);
--color-green-300: oklch(0.871 0.15 154.449);
--color-green-400: oklch(0.792 0.209 151.711);
--color-green-500: oklch(0.723 0.219 149.579);
--color-green-600: oklch(0.627 0.194 149.214);
--color-green-700: oklch(0.527 0.154 150.069);
--color-green-800: oklch(0.448 0.119 151.328);
--color-green-900: oklch(0.393 0.095 152.535);
--color-green-950: oklch(0.266 0.065 152.934);
--color-emerald-50: oklch(0.979 0.021 166.113);
--color-emerald-100: oklch(0.95 0.052 163.051);
--color-emerald-200: oklch(0.905 0.093 164.15);
--color-emerald-300: oklch(0.845 0.143 164.978);
--color-emerald-400: oklch(0.765 0.177 163.223);
--color-emerald-500: oklch(0.696 0.17 162.48);
--color-emerald-600: oklch(0.596 0.145 163.225);
--color-emerald-700: oklch(0.508 0.118 165.612);
--color-emerald-800: oklch(0.432 0.095 166.913);
--color-emerald-900: oklch(0.378 0.077 168.94);
--color-emerald-950: oklch(0.262 0.051 172.552);
--color-teal-50: oklch(0.984 0.014 180.72);
--color-teal-100: oklch(0.953 0.051 180.801);
--color-teal-200: oklch(0.91 0.096 180.426);
--color-teal-300: oklch(0.855 0.138 181.071);
--color-teal-400: oklch(0.777 0.152 181.912);
--color-teal-500: oklch(0.704 0.14 182.503);
--color-teal-600: oklch(0.6 0.118 184.704);
--color-teal-700: oklch(0.511 0.096 186.391);
--color-teal-800: oklch(0.437 0.078 188.216);
--color-teal-900: oklch(0.386 0.063 188.416);
--color-teal-950: oklch(0.277 0.046 192.524);
--color-cyan-50: oklch(0.984 0.019 200.873);
--color-cyan-100: oklch(0.956 0.045 203.388);
--color-cyan-200: oklch(0.917 0.08 205.041);
--color-cyan-300: oklch(0.865 0.127 207.078);
--color-cyan-400: oklch(0.789 0.154 211.53);
--color-cyan-500: oklch(0.715 0.143 215.221);
--color-cyan-600: oklch(0.609 0.126 221.723);
--color-cyan-700: oklch(0.52 0.105 223.128);
--color-cyan-800: oklch(0.45 0.085 224.283);
--color-cyan-900: oklch(0.398 0.07 227.392);
--color-cyan-950: oklch(0.302 0.056 229.695);
--color-sky-50: oklch(0.977 0.013 236.62);
--color-sky-100: oklch(0.951 0.026 236.824);
--color-sky-200: oklch(0.901 0.058 230.902);
--color-sky-300: oklch(0.828 0.111 230.318);
--color-sky-400: oklch(0.746 0.16 232.661);
--color-sky-500: oklch(0.685 0.169 237.323);
--color-sky-600: oklch(0.588 0.158 241.966);
--color-sky-700: oklch(0.5 0.134 242.749);
--color-sky-800: oklch(0.443 0.11 240.79);
--color-sky-900: oklch(0.391 0.09 240.876);
--color-sky-950: oklch(0.293 0.066 243.157);
--color-blue-50: oklch(0.97 0.014 254.604);
--color-blue-100: oklch(0.932 0.032 255.585);
--color-blue-200: oklch(0.882 0.059 254.128);
--color-blue-300: oklch(0.809 0.105 251.813);
--color-blue-400: oklch(0.707 0.165 254.624);
--color-blue-500: oklch(0.623 0.214 259.815);
--color-blue-600: oklch(0.546 0.245 262.881);
--color-blue-700: oklch(0.488 0.243 264.376);
--color-blue-800: oklch(0.424 0.199 265.638);
--color-blue-900: oklch(0.379 0.146 265.522);
--color-blue-950: oklch(0.282 0.091 267.935);
--color-indigo-50: oklch(0.962 0.018 272.314);
--color-indigo-100: oklch(0.93 0.034 272.788);
--color-indigo-200: oklch(0.87 0.065 274.039);
--color-indigo-300: oklch(0.785 0.115 274.713);
--color-indigo-400: oklch(0.673 0.182 276.935);
--color-indigo-500: oklch(0.585 0.233 277.117);
--color-indigo-600: oklch(0.511 0.262 276.966);
--color-indigo-700: oklch(0.457 0.24 277.023);
--color-indigo-800: oklch(0.398 0.195 277.366);
--color-indigo-900: oklch(0.359 0.144 278.697);
--color-indigo-950: oklch(0.257 0.09 281.288);
--color-violet-50: oklch(0.969 0.016 293.756);
--color-violet-100: oklch(0.943 0.029 294.588);
--color-violet-200: oklch(0.894 0.057 293.283);
--color-violet-300: oklch(0.811 0.111 293.571);
--color-violet-400: oklch(0.702 0.183 293.541);
--color-violet-500: oklch(0.606 0.25 292.717);
--color-violet-600: oklch(0.541 0.281 293.009);
--color-violet-700: oklch(0.491 0.27 292.581);
--color-violet-800: oklch(0.432 0.232 292.759);
--color-violet-900: oklch(0.38 0.189 293.745);
--color-violet-950: oklch(0.283 0.141 291.089);
--color-purple-50: oklch(0.977 0.014 308.299);
--color-purple-100: oklch(0.946 0.033 307.174);
--color-purple-200: oklch(0.902 0.063 306.703);
--color-purple-300: oklch(0.827 0.119 306.383);
--color-purple-400: oklch(0.714 0.203 305.504);
--color-purple-500: oklch(0.627 0.265 303.9);
--color-purple-600: oklch(0.558 0.288 302.321);
--color-purple-700: oklch(0.496 0.265 301.924);
--color-purple-800: oklch(0.438 0.218 303.724);
--color-purple-900: oklch(0.381 0.176 304.987);
--color-purple-950: oklch(0.291 0.149 302.717);
--color-fuchsia-50: oklch(0.977 0.017 320.058);
--color-fuchsia-100: oklch(0.952 0.037 318.852);
--color-fuchsia-200: oklch(0.903 0.076 319.62);
--color-fuchsia-300: oklch(0.833 0.145 321.434);
--color-fuchsia-400: oklch(0.74 0.238 322.16);
--color-fuchsia-500: oklch(0.667 0.295 322.15);
--color-fuchsia-600: oklch(0.591 0.293 322.896);
--color-fuchsia-700: oklch(0.518 0.253 323.949);
--color-fuchsia-800: oklch(0.452 0.211 324.591);
--color-fuchsia-900: oklch(0.401 0.17 325.612);
--color-fuchsia-950: oklch(0.293 0.136 325.661);
--color-pink-50: oklch(0.971 0.014 343.198);
--color-pink-100: oklch(0.948 0.028 342.258);
--color-pink-200: oklch(0.899 0.061 343.231);
--color-pink-300: oklch(0.823 0.12 346.018);
--color-pink-400: oklch(0.718 0.202 349.761);
--color-pink-500: oklch(0.656 0.241 354.308);
--color-pink-600: oklch(0.592 0.249 0.584);
--color-pink-700: oklch(0.525 0.223 3.958);
--color-pink-800: oklch(0.459 0.187 3.815);
--color-pink-900: oklch(0.408 0.153 2.432);
--color-pink-950: oklch(0.284 0.109 3.907);
--color-rose-50: oklch(0.969 0.015 12.422);
--color-rose-100: oklch(0.941 0.03 12.58);
--color-rose-200: oklch(0.892 0.058 10.001);
--color-rose-300: oklch(0.81 0.117 11.638);
--color-rose-400: oklch(0.712 0.194 13.428);
--color-rose-500: oklch(0.645 0.246 16.439);
--color-rose-600: oklch(0.586 0.253 17.585);
--color-rose-700: oklch(0.514 0.222 16.935);
--color-rose-800: oklch(0.455 0.188 13.697);
--color-rose-900: oklch(0.41 0.159 10.272);
--color-rose-950: oklch(0.271 0.105 12.094);
--color-slate-50: oklch(0.984 0.003 247.858);
--color-slate-100: oklch(0.968 0.007 247.896);
--color-slate-200: oklch(0.929 0.013 255.508);
--color-slate-300: oklch(0.869 0.022 252.894);
--color-slate-400: oklch(0.704 0.04 256.788);
--color-slate-500: oklch(0.554 0.046 257.417);
--color-slate-600: oklch(0.446 0.043 257.281);
--color-slate-700: oklch(0.372 0.044 257.287);
--color-slate-800: oklch(0.279 0.041 260.031);
--color-slate-900: oklch(0.208 0.042 265.755);
--color-slate-950: oklch(0.129 0.042 264.695);
--color-gray-50: oklch(0.985 0.002 247.839);
--color-gray-100: oklch(0.967 0.003 264.542);
--color-gray-200: oklch(0.928 0.006 264.531);
--color-gray-300: oklch(0.872 0.01 258.338);
--color-gray-400: oklch(0.707 0.022 261.325);
--color-gray-500: oklch(0.551 0.027 264.364);
--color-gray-600: oklch(0.446 0.03 256.802);
--color-gray-700: oklch(0.373 0.034 259.733);
--color-gray-800: oklch(0.278 0.033 256.848);
--color-gray-900: oklch(0.21 0.034 264.665);
--color-gray-950: oklch(0.13 0.028 261.692);
--color-zinc-50: oklch(0.985 0 0);
--color-zinc-100: oklch(0.967 0.001 286.375);
--color-zinc-200: oklch(0.92 0.004 286.32);
--color-zinc-300: oklch(0.871 0.006 286.286);
--color-zinc-400: oklch(0.705 0.015 286.067);
--color-zinc-500: oklch(0.552 0.016 285.938);
--color-zinc-600: oklch(0.442 0.017 285.786);
--color-zinc-700: oklch(0.37 0.013 285.805);
--color-zinc-800: oklch(0.274 0.006 286.033);
--color-zinc-900: oklch(0.21 0.006 285.885);
--color-zinc-950: oklch(0.141 0.005 285.823);
--color-neutral-50: oklch(0.985 0 0);
--color-neutral-100: oklch(0.97 0 0);
--color-neutral-200: oklch(0.922 0 0);
--color-neutral-300: oklch(0.87 0 0);
--color-neutral-400: oklch(0.708 0 0);
--color-neutral-500: oklch(0.556 0 0);
--color-neutral-600: oklch(0.439 0 0);
--color-neutral-700: oklch(0.371 0 0);
--color-neutral-800: oklch(0.269 0 0);
--color-neutral-900: oklch(0.205 0 0);
--color-neutral-950: oklch(0.145 0 0);
--color-stone-50: oklch(0.985 0.001 106.423);
--color-stone-100: oklch(0.97 0.001 106.424);
--color-stone-200: oklch(0.923 0.003 48.717);
--color-stone-300: oklch(0.869 0.005 56.366);
--color-stone-400: oklch(0.709 0.01 56.259);
--color-stone-500: oklch(0.553 0.013 58.071);
--color-stone-600: oklch(0.444 0.011 73.639);
--color-stone-700: oklch(0.374 0.01 67.558);
--color-stone-800: oklch(0.268 0.007 34.298);
--color-stone-900: oklch(0.216 0.006 56.043);
--color-stone-950: oklch(0.147 0.004 49.25);
--color-black: #000;
--color-white: #fff;
--spacing: 0.25rem;
--breakpoint-sm: 40rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 64rem;
--breakpoint-xl: 80rem;
--breakpoint-2xl: 96rem;
--container-3xs: 16rem;
--container-2xs: 18rem;
--container-xs: 20rem;
--container-sm: 24rem;
--container-md: 28rem;
--container-lg: 32rem;
--container-xl: 36rem;
--container-2xl: 42rem;
--container-3xl: 48rem;
--container-4xl: 56rem;
--container-5xl: 64rem;
--container-6xl: 72rem;
--container-7xl: 80rem;
--text-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.75);
--text-sm: 0.875rem;
--text-sm--line-height: calc(1.25 / 0.875);
--text-base: 1rem;
--text-base--line-height: calc(1.5 / 1);
--text-lg: 1.125rem;
--text-lg--line-height: calc(1.75 / 1.125);
--text-xl: 1.25rem;
--text-xl--line-height: calc(1.75 / 1.25);
--text-2xl: 1.5rem;
--text-2xl--line-height: calc(2 / 1.5);
--text-3xl: 1.875rem;
--text-3xl--line-height: calc(2.25 / 1.875);
--text-4xl: 2.25rem;
--text-4xl--line-height: calc(2.5 / 2.25);
--text-5xl: 3rem;
--text-5xl--line-height: 1;
--text-6xl: 3.75rem;
--text-6xl--line-height: 1;
--text-7xl: 4.5rem;
--text-7xl--line-height: 1;
--text-8xl: 6rem;
--text-8xl--line-height: 1;
--text-9xl: 8rem;
--text-9xl--line-height: 1;
--font-weight-thin: 100;
--font-weight-extralight: 200;
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--font-weight-extrabold: 800;
--font-weight-black: 900;
--tracking-tighter: -0.05em;
--tracking-tight: -0.025em;
--tracking-normal: 0em;
--tracking-wide: 0.025em;
--tracking-wider: 0.05em;
--tracking-widest: 0.1em;
--leading-tight: 1.25;
--leading-snug: 1.375;
--leading-normal: 1.5;
--leading-relaxed: 1.625;
--leading-loose: 2;
--radius-xs: 0.125rem;
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--radius-xl: 0.75rem;
--radius-2xl: 1rem;
--radius-3xl: 1.5rem;
--radius-4xl: 2rem;
--shadow-2xs: 0 1px rgb(0 0 0 / 0.05);
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1),
0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1),
0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1),
0 8px 10px -6px rgb(0 0 0 / 0.1);
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
--inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05);
--inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05);
--inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05);
--drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05);
--drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15);
--drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12);
--drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15);
--drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1);
--drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--animate-spin: spin 1s linear infinite;
--animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
--animate-bounce: bounce 1s infinite;
--blur-xs: 4px;
--blur-sm: 8px;
--blur-md: 12px;
--blur-lg: 16px;
--blur-xl: 24px;
--blur-2xl: 40px;
--blur-3xl: 64px;
--perspective-dramatic: 100px;
--perspective-near: 300px;
--perspective-normal: 500px;
--perspective-midrange: 800px;
--perspective-distant: 1200px;
--aspect-video: 16 / 9;
--default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
--default-font-family: var(--font-sans);
--default-font-feature-settings: var(--font-sans--font-feature-settings);
--default-font-variation-settings: var(
--font-sans--font-variation-settings
);
--default-mono-font-family: var(--font-mono);
--default-mono-font-feature-settings: var(
--font-mono--font-feature-settings
);
--default-mono-font-variation-settings: var(
--font-mono--font-variation-settings
);
}
}
@layer base {
*, ::after, ::before, ::backdrop, ::file-selector-button {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0 solid;
}
html, :host {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
tab-size: 4;
font-family: var( --default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" );
font-feature-settings: var(--default-font-feature-settings, normal);
font-variation-settings: var( --default-font-variation-settings, normal );
-webkit-tap-highlight-color: transparent;
}
body {
line-height: inherit;
}
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
b, strong {
font-weight: bolder;
}
code, kbd, samp, pre {
font-family: var( --default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace );
font-feature-settings: var( --default-mono-font-feature-settings, normal );
font-variation-settings: var( --default-mono-font-variation-settings, normal );
font-size: 1em;
}
small {
font-size: 80%;
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
:-moz-focusring {
outline: auto;
}
progress {
vertical-align: baseline;
}
summary {
display: list-item;
}
ol, ul, menu {
list-style: none;
}
img, svg, video, canvas, audio, iframe, embed, object {
display: block;
vertical-align: middle;
}
img, video {
max-width: 100%;
height: auto;
}
button, input, select, optgroup, textarea, ::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
border-radius: 0;
background-color: transparent;
opacity: 1;
}
:where(select:is([multiple], [size])) optgroup {
font-weight: bolder;
}
:where(select:is([multiple], [size])) optgroup option {
padding-inline-start: 20px;
}
::file-selector-button {
margin-inline-end: 4px;
}
::placeholder {
opacity: 1;
color: color-mix(in oklab, currentColor 50%, transparent);
}
textarea {
resize: vertical;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-date-and-time-value {
min-height: 1lh;
text-align: inherit;
}
::-webkit-datetime-edit {
display: inline-flex;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
padding-block: 0;
}
:-moz-ui-invalid {
box-shadow: none;
}
button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
appearance: button;
}
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
height: auto;
}
[hidden]:where(:not([hidden="until-found"])) {
display: none !important;
}
}
@layer utilities {
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.static {
position: static;
}
.inset-0 {
inset: calc(var(--spacing) * 0);
}
.col-span-1 {
grid-column: span 1 / span 1;
}
.col-span-2 {
grid-column: span 2 / span 2;
}
.my-20 {
margin-block: calc(var(--spacing) * 20);
}
.mt-2 {
margin-top: calc(var(--spacing) * 2);
}
.mt-4 {
margin-top: calc(var(--spacing) * 4);
}
.mr-2 {
margin-right: calc(var(--spacing) * 2);
}
.block {
display: block;
}
.flex {
display: flex;
}
.grid {
display: grid;
}
.hidden {
display: none;
}
.table {
display: table;
}
.aspect-square {
aspect-ratio: 1 / 1;
}
.\!h-\[2\.5rem\] {
height: 2.5rem !important;
}
.\!h-\[90svh\] {
height: 90svh !important;
}
.\!h-\[90vh\] {
height: 90vh !important;
}
.h-4 {
height: calc(var(--spacing) * 4);
}
.h-6 {
height: calc(var(--spacing) * 6);
}
.h-\[31svw\] {
height: 31svw;
}
.h-\[31vw\] {
height: 31vw;
}
.h-\[90svh\] {
height: 90svh;
}
.h-\[90vh\] {
height: 90vh;
}
.h-auto {
height: auto;
}
.h-screen {
height: 100vh;
}
.w-4 {
width: calc(var(--spacing) * 4);
}
.w-6 {
width: calc(var(--spacing) * 6);
}
.w-80 {
width: calc(var(--spacing) * 80);
}
.w-\[25svw\] {
width: 25svw;
}
.w-\[25vw\] {
width: 25vw;
}
.w-full {
width: 100%;
}
.w-screen {
width: 100vw;
}
.min-w-fit {
min-width: fit-content;
}
.flex-1 {
flex: 1;
}
.flex-none {
flex: none;
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.flex-col {
flex-direction: column;
}
.flex-row {
flex-direction: row;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.justify-center {
justify-content: center;
}
.justify-end {
justify-content: flex-end;
}
.gap-2 {
gap: calc(var(--spacing) * 2);
}
.space-y-2 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-6 {}
.overflow-hidden {
overflow: hidden;
}
.\!rounded-md {
border-radius: var(--radius-md) !important;
}
.rounded-2xl {
border-radius: var(--radius-2xl);
}
.border {
border-style: var(--tw-border-style);
border-width: 1px;
}
.border-t {
border-top-style: var(--tw-border-style);
border-top-width: 1px;
}
.\!bg-\[hsl\(var\(--muted\)\)\]\/75 {
background-color: color-mix(in oklab, hsl(var(--muted)) 75%, transparent) !important;
}
.bg-\[hsl\(var\(--foreground\)\)\]\/70 {
background-color: color-mix(in oklab, hsl(var(--foreground)) 70%, transparent);
}
.bg-zinc-900 {
background-color: var(--color-zinc-900);
}
.object-cover {
object-fit: cover;
}
.p-4 {
padding: calc(var(--spacing) * 4);
}
.p-8 {
padding: calc(var(--spacing) * 8);
}
.px-2 {
padding-inline: calc(var(--spacing) * 2);
}
.px-8 {
padding-inline: calc(var(--spacing) * 8);
}
.text-center {
text-align: center;
}
.\!text-6xl {
font-size: var(--text-6xl) !important;
line-height: var(--tw-leading, var(--text-6xl--line-height)) !important;
}
.text-lg {
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));
}
.text-sm {
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
.text-xl {
font-size: var(--text-xl);
line-height: var(--tw-leading, var(--text-xl--line-height));
}
.text-xs {
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
.font-bold {
--tw-font-weight: var(--font-weight-bold);
font-weight: var(--font-weight-bold);
}
.font-medium {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
.text-\[hsl\(var\(--background\)\)\] {
color: hsl(var(--background));
}
.text-white {
color: var(--color-white);
}
.uppercase {
text-transform: uppercase;
}
.ordinal {
--tw-ordinal: ordinal;
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
}
.underline {
text-decoration-line: underline;
}
.underline-offset-4 {
text-underline-offset: 4px;
}
.opacity-50 {
opacity: 50%;
}
.invert {
--tw-invert: invert(100%);
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
.\!backdrop-blur-lg {
--tw-backdrop-blur: blur(var(--blur-lg)) !important;
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,) !important;
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,) !important;
}
.\!backdrop-blur-md {
--tw-backdrop-blur: blur(var(--blur-md)) !important;
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,) !important;
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,) !important;
}
.max-md\:hidden {
@media (width < 48rem) {
display: none;
}
}
.md\:w-\[30vw\] {
@media (width >= 48rem) {
width: 30vw;
}
}
.lg\:col-span-1 {
@media (width >= 64rem) {
grid-column: span 1 / span 1;
}
}
.lg\:flex {
@media (width >= 64rem) {
display: flex;
}
}
.xl\:grid {
@media (width >= 80rem) {
display: grid;
}
}
.dark\:bg-\[hsl\(var\(--background\)\)\]\/60 {
@media (prefers-color-scheme: dark) {
background-color: color-mix(in oklab, hsl(var(--background)) 60%, transparent);
}
}
.dark\:text-\[hsl\(var\(--foreground\)\)\] {
@media (prefers-color-scheme: dark) {
color: hsl(var(--foreground));
}
}
}
:root {
font-family: Geist, sans-serif;
font-feature-settings: "liga" 1, "calt" 1;
}
@media (max-width: 768px) {
.uk-subnav > * > :first-child {
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes ping {
75%, 100% {
transform: scale(2);
opacity: 0;
}
}
@keyframes pulse {
50% {
opacity: 0.5;
}
}
@keyframes bounce {
0%, 100% {
transform: translateY(-25%);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
50% {
transform: none;
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
}
@property --tw-space-y-reverse {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-border-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-font-weight {
syntax: "*";
inherits: false;
}
@property --tw-ordinal {
syntax: "*";
inherits: false;
}
@property --tw-slashed-zero {
syntax: "*";
inherits: false;
}
@property --tw-numeric-figure {
syntax: "*";
inherits: false;
}
@property --tw-numeric-spacing {
syntax: "*";
inherits: false;
}
@property --tw-numeric-fraction {
syntax: "*";
inherits: false;
}
@property --tw-blur {
syntax: "*";
inherits: false;
}
@property --tw-brightness {
syntax: "*";
inherits: false;
}
@property --tw-contrast {
syntax: "*";
inherits: false;
}
@property --tw-grayscale {
syntax: "*";
inherits: false;
}
@property --tw-hue-rotate {
syntax: "*";
inherits: false;
}
@property --tw-invert {
syntax: "*";
inherits: false;
}
@property --tw-opacity {
syntax: "*";
inherits: false;
}
@property --tw-saturate {
syntax: "*";
inherits: false;
}
@property --tw-sepia {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-blur {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-brightness {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-contrast {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-grayscale {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-hue-rotate {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-invert {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-opacity {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-saturate {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-sepia {
syntax: "*";
inherits: false;
}