This commit is contained in:
zyl 2024-11-04 17:57:51 -08:00
parent 00c90a12d7
commit d8cb447e00
Signed by: zyl
SSH key fingerprint: SHA256:uxxbSXbdroP/OnKBGnEDk5q7EKB2razvstC/KmzdXXs
47 changed files with 53 additions and 5099 deletions

22
.vscode/tasks.json vendored
View file

@ -1,22 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "watch",
"args": ["-x", "run -- serve"],
"problemMatcher": ["$rustc"],
"label": "dev serve site",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "cargo",
"command": "run",
"problemMatcher": ["$rustc"],
"label": "build site"
}
]
}

56
Cargo.lock generated
View file

@ -2707,6 +2707,34 @@ version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]]
name = "webdog"
version = "0.1.0"
dependencies = [
"color-eyre",
"extract-frontmatter",
"eyre",
"fs_extra",
"futures",
"grass",
"hotwatch",
"itertools",
"lol_html",
"minifier",
"percent-encoding",
"pulldown-cmark",
"rayon",
"rss",
"serde",
"serde_yml",
"tera",
"time",
"tokio",
"url",
"walkdir",
"warp",
]
[[package]]
name = "winapi-util"
version = "0.1.9"
@ -2972,31 +3000,3 @@ dependencies = [
"quote",
"syn 2.0.87",
]
[[package]]
name = "zyl-site"
version = "0.1.0"
dependencies = [
"color-eyre",
"extract-frontmatter",
"eyre",
"fs_extra",
"futures",
"grass",
"hotwatch",
"itertools",
"lol_html",
"minifier",
"percent-encoding",
"pulldown-cmark",
"rayon",
"rss",
"serde",
"serde_yml",
"tera",
"time",
"tokio",
"url",
"walkdir",
"warp",
]

View file

@ -1,6 +1,6 @@
[package]
edition = "2018"
name = "zyl-site"
edition = "2021"
name = "webdog"
version = "0.1.0"
[dependencies]

View file

@ -1,5 +1,3 @@
# zyl.gay
# webdog
Source for my website (located at [zyl.gay](https://zyl.gay)) and the custom static site generator I've built alongside it.
Feel free to make or suggest changes.
Source for webdog, the static site generator fit for a dog. See our website at https://webdog.zyl.gay for more details.

View file

@ -1,12 +0,0 @@
# http://editorconfig.org
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.yml]
indent_style = space

172
cf/.gitignore vendored
View file

@ -1,172 +0,0 @@
# Logs
logs
_.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
# Runtime data
pids
_.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
# 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.\*
# wrangler project
.dev.vars
.wrangler/

View file

@ -1,6 +0,0 @@
{
"printWidth": 140,
"singleQuote": true,
"semi": true,
"useTabs": true
}

3157
cf/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,22 +0,0 @@
{
"name": "cf",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"test": "vitest",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.4.5",
"@cloudflare/workers-types": "^4.20240620.0",
"typescript": "^5.4.5",
"vitest": "1.5.0",
"wrangler": "^3.60.3"
},
"dependencies": {
"hono": "^4.4.8"
}
}

View file

@ -1,3 +0,0 @@
CREATE TABLE pets (count INTEGER NOT NULL DEFAULT 0);
INSERT INTO pets (count) VALUES (0);

View file

@ -1,33 +0,0 @@
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run `npm run dev` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `npm run deploy` to publish your worker
*
* Bind resources to your worker in `wrangler.toml`. After adding bindings, a type definition for the
* `Env` object can be regenerated with `npm run cf-typegen`.
*
* Learn more at https://developers.cloudflare.com/workers/
*/
import { Hono } from 'hono';
import { cors } from 'hono/cors';
const app = new Hono<{ Bindings: Env }>();
app.use('/api/*', cors());
app.get('/api/pet', async (c) => {
return c.json({
count: (await c.env.DB.prepare('SELECT count FROM pets').first())!.count,
});
});
app.post('/api/pet', async (c) => {
return c.json({
count: (await c.env.DB.prepare('UPDATE pets SET count = count + 1 RETURNING count').first())!.count,
});
});
export default app satisfies ExportedHandler<Env>;

View file

@ -1,104 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": ["es2021"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
"jsx": "react-jsx" /* Specify what JSX code is generated. */,
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "es2022" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
"types": [
"@cloudflare/workers-types/2023-07-01"
] /* Specify type package names to be included without being referenced in a source file. */,
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
"resolveJsonModule": true /* Enable importing .json files */,
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */,
"checkJs": false /* Enable error reporting in type-checked JavaScript files. */,
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
"noEmit": true /* Disable emitting files from a compilation. */,
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
"isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
"allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */,
// "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"exclude": ["test"]
}

View file

@ -1,11 +0,0 @@
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.toml' },
},
},
},
});

View file

@ -1,6 +0,0 @@
// Generated by Wrangler on Tue Jun 25 2024 15:10:11 GMT-0700 (Pacific Daylight Time)
// by running `wrangler types`
type Env = {
DB: D1Database;
};

View file

@ -1,108 +0,0 @@
#:schema node_modules/wrangler/config-schema.json
compatibility_date = "2023-06-20"
compatibility_flags = ["nodejs_compat"]
main = "src/index.ts"
name = "cf"
# Automatically place your workloads in an optimal location to minimize latency.
# If you are running back-end logic in a Worker, running it closer to your back-end infrastructure
# rather than the end user may result in better performance.
# Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
# [placement]
# mode = "smart"
# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
# Docs:
# - https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
# Note: Use secrets to store sensitive data.
# - https://developers.cloudflare.com/workers/configuration/secrets/
# [vars]
# MY_VARIABLE = "production_value"
# Bind the Workers AI model catalog. Run machine learning models, powered by serverless GPUs, on Cloudflares global network
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#workers-ai
# [ai]
# binding = "AI"
# Bind an Analytics Engine dataset. Use Analytics Engine to write analytics within your Pages Function.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#analytics-engine-datasets
# [[analytics_engine_datasets]]
# binding = "MY_DATASET"
# Bind a headless browser instance running on Cloudflare's global network.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#browser-rendering
# [browser]
# binding = "MY_BROWSER"
# Bind a D1 database. D1 is Cloudflares native serverless SQL database.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#d1-databases
[[d1_databases]]
binding = "DB"
database_id = "1df6a81f-149f-4c48-b01c-1bf9af04b264"
database_name = "zylgay"
# Bind a dispatch namespace. Use Workers for Platforms to deploy serverless functions programmatically on behalf of your customers.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#dispatch-namespace-bindings-workers-for-platforms
# [[dispatch_namespaces]]
# binding = "MY_DISPATCHER"
# namespace = "my-namespace"
# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model.
# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#durable-objects
# [[durable_objects.bindings]]
# name = "MY_DURABLE_OBJECT"
# class_name = "MyDurableObject"
# Durable Object migrations.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#migrations
# [[migrations]]
# tag = "v1"
# new_classes = ["MyDurableObject"]
# Bind a Hyperdrive configuration. Use to accelerate access to your existing databases from Cloudflare Workers.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#hyperdrive
# [[hyperdrive]]
# binding = "MY_HYPERDRIVE"
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#kv-namespaces
# [[kv_namespaces]]
# binding = "MY_KV_NAMESPACE"
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Bind an mTLS certificate. Use to present a client certificate when communicating with another service.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#mtls-certificates
# [[mtls_certificates]]
# binding = "MY_CERTIFICATE"
# certificate_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#queues
# [[queues.producers]]
# binding = "MY_QUEUE"
# queue = "my-queue"
# Bind a Queue consumer. Queue Consumers can retrieve tasks scheduled by Producers to act on them.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#queues
# [[queues.consumers]]
# queue = "my-queue"
# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#r2-buckets
# [[r2_buckets]]
# binding = "MY_BUCKET"
# bucket_name = "my-bucket"
# Bind another Worker service. Use this binding to call another Worker without network overhead.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
# [[services]]
# binding = "MY_SERVICE"
# service = "my-service"
# Bind a Vectorize index. Use to store and query vector embeddings for semantic search, classification and other vector search use-cases.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#vectorize-indexes
# [[vectorize]]
# binding = "MY_INDEX"
# index_name = "my-index"

View file

@ -1,7 +1,7 @@
base_url: "https://zyl.gay"
title: zyl is gay
description: "zyl's website."
sass_styles: [index.scss, pet.scss, click.scss]
base_url: "https://webdog.zyl.gay"
title: webdog
description: "static site builder for dogs"
sass_styles: [index.scss]
cdn_url: "https://i.zyl.gay"
resources:
@ -13,23 +13,9 @@ resources:
resource_list_template: blog-list.tera
tag_list_template: basic-link-list.tera
rss_template: rss/blog-post.tera
rss_title: zyl's blog
rss_description: feed of recent blog posts on zyl's website.
rss_title: webdog blog
rss_description: feed of recent webdog blog posts.
list_title: blog
tag_list_title: blog tags
resource_name_plural: blog posts
resources_per_page: 20
images:
source_path: images
output_path_short: i
output_path_long: images
resource_template: image.tera
resource_list_template: images.tera
tag_list_template: basic-link-list.tera
rss_template: rss/image.tera
rss_title: zyl's images
rss_description: feed of newly uploaded images from zyl's website.
list_title: images
tag_list_title: image tags
resource_name_plural: images
resources_per_page: 10

View file

@ -1,12 +0,0 @@
---
title: click
scripts: ["js/click.js"]
styles: ["click.css"]
embed:
title: click
site_name: zyl.gay
description: click click click
extra:
name: basic
template: extras/click.tera
---

View file

@ -1,11 +0,0 @@
---
title: games!
---
# games
little games i've made on here :3
<h2><a href="/pet">pet game</a></h2>
<h2><a href="/click">clicker game</a></h2>

View file

@ -6,16 +6,12 @@ extra:
count: 3
---
# zyl is gay
# webdog
hi! i'm zyl, a trans doggirlthing <abbr title="therian">ΘΔ</abbr> in the pnw.
welcome to webdog, the static site generator fit for a dog :3
if you're interested, check out [my youtube](me$https://youtube.com/@zylpup) to see what i've got going on there
if you're an adult i have a couple socials floating around:
- [federated (sharkey)](me$https://fed.zyl.gay/@zyl)
- [instagram?](me$https://www.instagram.com/zylbarker/)
- [tumblr](me$https://www.tumblr.com/zyllian)
if you like what i do or just wanna help me out [check out my payment info](/pay-me) :)
```
git clone https://zyllian/webdog
cd webdog
cargo install .
```

View file

@ -1,19 +0,0 @@
---
title: pay me!!
embed:
title: pay me!!
site_name: test
description: send trans people money. like me.
---
# pay me!!
you should send trans people money. here's how you can send _me_ money:
(preferred methods are feeless, so i'll receive the full amount you send)
- **[\$zylpup](https://cash.app/$zylpup)** on cashapp **(preferred)**
- **z@zyl.gay** on apple cash **(preferred)**
- [github sponsors](https://github.com/sponsors/zyllian)
- [ko-fi](https://ko-fi.com/zylpup)
- [buy me a coffee](https://buymeacoffee.com/zylpup)

View file

@ -1,98 +0,0 @@
---
title: pet
scripts: ["js/pet.js"]
styles: ["pet.css"]
embed:
title: raise a pet
site_name: test
description: come raise a cool pet! you could get a square. or a circle. or a triangle. who knows?
---
<div id="pet">
<noscript><h1>javascript is required for the pet game!!</h1></noscript>
<div id="pet-display">
<h2 class="pet-name"></h2>
<div class="the-pet"></div>
<div class="status">
<p name="hungry" class="hidden"><span class="pet-name"></span> looks hungry..</p>
<p name="starving" class="hidden"><span class="pet-name"></span> is starving!! you need to feed them!!</p>
<p name="unhappy" class="hidden"><span class="pet-name"></span> looks at you with wide eyes..</p>
<p name="messy-1" class="hidden"><span class="pet-name"></span> has left a bit of a mess! time to clean!</p>
<p name="messy-2" class="hidden">there's even more mess in here.. shouldn't you clean it for <span class="pet-name"></span>?</p>
<p name="messy-3" class="hidden">what a mess!! <span class="pet-name"></span> can't be happy.. you've gotta clean in here</p>
</div>
</div>
<div id="egg">
<p>whoa! you just found a weird egg! maybe you should watch it and see what happens..</p>
</div>
<div id="adult-info" class="hidden">
<p><span class="pet-name"></span> has grown up to be an adult!! what will they do with their life now....</p>
<button class="advance">okay!</button>
</div>
<div id="elder-info" class="hidden">
<p>oh? <span class="pet-name"></span> has aged and is now an elder creature! they may not have much left in their life.... hopefully it's been a good life!</p>
<button class="advance">hopefully!!</button>
</div>
<div id="passed-away-info" class="hidden">
<p>oh... <span class="pet-name"></span> has finally gone and kicked the bucket. its story comes to an end....</p>
<button>but what's this egg lying here about..?</button>
</div>
<form id="pet-setup" class="hidden">
<p>whoa! your egg just hatched into a new creature! what should you name it?</p>
<input type="text" name="pet-name" min-length="3" max-length="50">
<button type="submit">name it!</button>
</form>
<div id="pet-actions">
<div name="hatched-actions" class="hidden">
<button name="feed">feed</button>
<button name="pet">pet</button>
<button name="clean">clean</button>
</div>
<button name="pause">pause</button>
</div>
<div id="debug-section" class="hidden">
<button id="force-update">force update</button> <button id="reset">reset</button>
<p>LS: <span name="ls"></span> A: <span name="a"></span> F: <span name="f"></span> B: <span name="b"></span> P: <span name="p"></span> MC: <span name="mc"></span> H: <span name="h"></span></p>
</div>
</div>
<details>
<summary>tips!!</summary>
<ul>
<li>pets need to be fed about once every eight hours!</li>
<li>your pet still exists while the page is unloaded or your computer is off! pause if you need to leave them be for a while!</li>
<li>make sure to keep your pet clean!!</li>
<li>if your pet is turning grey, make sure you're giving them the attention they need!! pet's deserve happiness too :(</li>
<li>if you take good enough care of your pet they'll stop going potty on the floor!</li>
</ul>
</details>
<details open>
<summary>changelog</summary>
<details>
<summary>07/08/2024</summary>
<ul>
<li>slow pet food/happiness decay</li>
</ul>
</details>
<details>
<summary>07/04/2024</summary>
<ul>
<li>oops pets couldn't age past puppies..</li>
</ul>
</details>
<details>
<summary>06/25/2024</summary>
<ul>
<li>pets now are simulated even if the page is unloaded</li>
<li>when pets are unhappy &lt;redacted&gt;<!-- their hidden behavior stat drops --></li>
</p>
</details>
</details>

View file

@ -1,23 +0,0 @@
---
title: my projects
---
# projects
a simple list of projects i maintain:
<h2><a href="https://github.com/zyllian/zyllian.github.io">this very website</a></h2>
the website you're on right now :3
<h2><a href="https://github.com/zyllian/classics">my minecraft classic server</a></h2>
a simple minecraft classic server implementing some classic protocol extensions. not feasible for a public server but otherwise functions well :\)
<h2><a href="https://pipefall.com">pipefall.com</a></h2>
pipefall
<h2><a href="https://fed.zyl.gay">sharkey instance</a> (invite only)</h2>
sharkey instance i run for myself/friends/etc

View file

@ -1,7 +0,0 @@
---
title: secret!!
---
# you found the secret page! 🎉🎉
that's it.

View file

@ -1,63 +0,0 @@
---
title: estradiol delivery methods
timestamp: 2023-11-12T20:55:00.00Z
tags: [trans, hrt, estradiol]
desc: a comparison of the four forms of estradiol i've taken since starting hrt
cdn_file: 2023/11/syringe.jpeg
header_image_alt: A photograph of a syringe for instramuscular injection with needle still in its wrapper.
---
i started estradiol injections a few months ago and felt it might be useful to write out my experiences with each of them for posterity
## patches
my endo started me on estradiol patches. these definitely work, but only if you put them in the right spots and also if they stay on
i had trouble with both of those and found it pretty annoying
aside from that, i could feel them starting to wear off 12-24 hours before it was time to replace the patches. definitely not ideal
additionally, insurance sucks and regularly caused me to be a couple days late on replacing a patch because they wouldn't cover it in time
## sublingual pills
these are pretty neat. easiest and cheapest of the bunch
unfortunately, i could feel _these_ wearing off too, at about 5-6 hours after a dose. apparently this isn't common, but it affected me sooo
i've also had problems with getting accurate estradiol level measurements since it flucuates so much. definitely my preferred method if they worked better for me
## swallowed pills
before switching to injections my endo had me try out swallowing my estradiol pills instead of dissolving them. the results for me were that i couldn't feel them wearing off anymore, BUT i couldn't get my levels back up either. not an ideal solution
## injections
these are _great_
i was super nervous before starting them but turns out it's actually not that hard to inject yourself if it's your only option
cannot feel these wearing off at all between doses _and_ i get good estradiol levels
my E levels are so consistently good now that i don't need a testosterone blocker which is _very_ nice
however!
## insurance _sucks_
my insurance filled my first three-month prescription of estradiol valerate for around $120. this is absurd.
then they failed to fill my prescription when i was out. when i called to ask for a fill, they told me they couldn't. doing the math from what they told me i found they expected me to around 16 and half doses out of a vial, which would be an extra 3-4 weeks over what the prescription from my endo actually said
so instead i went out of pocket and through discount programs get my estradiol for cheaper than with insurance and (importantly) _on time_
loooooove insurance.
## conclusions
- injections rock if you can get insurance to cover them
- sublingual's good too and works great for most people but didn't for me
- avoid swallowing them
- patches are ehhh
that is all.

View file

@ -1,20 +0,0 @@
---
title: i built a minecraft classic server
timestamp: 2024-07-08T10:37:00.00Z
tags: [minecraft, minecraft classic, dev]
desc: i got bored and built a minecraft classic server
cdn_file: 2024/07/classic2.png
header_image_alt: Screenshot of my Minecraft classic server showing two players standing in front of a stack of blocks.
---
a while ago i was looking for something to do and my brain decided i should.. build a complete reimplementation of the minecraft server, naturally. i knew i'd never get anywhere close to finishing that project before losing steam so instead, i redirected this inspiration into [a minecraft classic server](https://github.com/zyllian/classics)!
<md class="float-left w50" type="blog-image" src="cdn$2024/07/classic1.png" content="at one point while changing my world format, worlds started getting sent to clients diagonally! don't remember what caused this, though."></md>
minecraft classic has [fairly simple networking](https://wiki.vg/Classic_Protocol) as it turns out, so building a server which vanilla classic clients can connect to isn't too bad if you know what you're doing, and with no need to stay true to the vanilla map format, you can simplify things even further!
minecraft classic actually still has a decently large community around it, and what i believe are the most popular clients/servers all implement [an extended networking protocol](https://wiki.vg/Classic_Protocol_Extension) to add extra features that vanilla classic doesn't support.
my implementation lacks basically any kind of anti-cheat and is lacking most of the community protocol extensions, but it's still pretty cool to have built it! i love working on random projects and it's nice to see one which is actually useable, even if there's [a lot to add](https://zyllian/classics/issues)!
<md type="blog-image" src="cdn$2024/07/classic3.png" content="screenshot of me stress testing how many players i could connect to the server :3"></md>

View file

@ -1,12 +0,0 @@
---
title: send trans people money.
timestamp: 2024-06-21T14:05:00.00Z
tags: [trans, money]
desc: you should send trans people money. i'm trans btw
cdn_file: 2024/06/gamecube.jpeg
header_image_alt: A photo of me holding a Cipon-branded Gamecube controller in my lap.
---
this is a pretty short post. i'm just here to say you should send trans people ([like me](/pay-me)) money. spare a few bucks and send it! sure you can find [one or two trans people](/pay-me) out there to send money to. surely.
the picture is a gamecube controller i've been using recently since i started playing melee some :3

View file

@ -1,24 +0,0 @@
---
title: so now i have a blog
timestamp: 2023-06-09T22:20:00.00Z
tags: [meta, blog]
desc: I added a blog to my site. Here's a picture of some smoke.
cdn_file: 2023/06/smoke.jpeg
header_image_alt: Photo of smoke engulfing an interstate from a highway across a river.
---
So now I have a blog on my site. I don't really have any plans to post here regularly, but idk maybe that'll change in the future.
That's pretty much it for now.
<!-- That's pretty much it as far as the non-technical side of things goes.
## The Technical Side of Things
I haven't really written anything about how my site works before, so this is also going to contain some general information about the site as a whole.
`zyl.gay` is a static website built with a custom static site builder I built for it. It started by taking Markdown pages and rendering them on top of the appropriate template.
When I added the [images section](/images/) to the site I added the first abstraction on top of this: YAML files with the relevant metadata for the image (including a short but unstyled description) which then get rendered not only into pages for the individual pages, but also a paginated display for all the images _and_ a method to view images by tag.
To get blogs working I modified the image page code to be generic over provided resource types, so really the images and the blog posts are rendered the same way, just with different configurations. -->

View file

@ -1,8 +1,8 @@
---
title: being gay is so cool
timestamp: 2023-11-14T23:09:00.00Z
tags: [gay, lesbiab, trans]
desc: holy shit being gay is cool y'all
title: test post
timestamp: 2024-11-04T17:29:00.00Z
tags: [first, post, test, webdog]
desc: the first post wowie
cdn_file: 2023/11/pfp.png
header_image_alt: A dead Among Us bean layered on top of transgender, lesbian, and bisexual pride flags.
---

View file

@ -1,10 +0,0 @@
---
title: among us 😱
timestamp: 2022-12-14T00:00:00.00Z
alt: Screenshot of Pokémon White on an evolution screen. Text reads "Congratulations! Your amogus evolved into Amoonguss!"
desc: aaahhhh
cdn_file: amogus.png
tags: [pokémon, sussy]
---
aaahhhh

View file

@ -1,7 +0,0 @@
---
title: cat 3
timestamp: 2022-12-14T00:00:00.00Z
alt: Picture of my cat sleeping in a box barely large enough for them. Their head is resting on one edge of the box.
cdn_file: boxtop.jpeg
tags: [cat]
---

View file

@ -1,7 +0,0 @@
---
title: cat
timestamp: 2022-12-14T00:00:00.00Z
alt: Picture of my cat sleeping curled up on top of some pillows.
cdn_file: cat.jpeg
tags: [cat]
---

View file

@ -1,7 +0,0 @@
---
title: cat 2
timestamp: 2022-12-14T00:00:00.00Z
alt: Close up picture of my cat laying on a shelf while staring not quite at the camera.
cdn_file: cat2.jpeg
tags: [cat]
---

View file

@ -1,9 +0,0 @@
---
title: shorts to dresses
timestamp: 2022-12-14T00:00:00.00Z
alt: Screenshot from Pokémon Black 2 of an NPC saying "This dress is comfy and easy to wear..."
cdn_file: trans-comfy.png
tags: [pokémon, trans]
---
yooo they're turning the comfy shorts kid trans

View file

@ -1 +1 @@
zyl.gay
webdog.zyl.gay

Binary file not shown.

Before

Width:  |  Height:  |  Size: 823 B

View file

@ -1,222 +0,0 @@
(function () {
"use strict";
const click = document.querySelector("#click");
const petsCounter = click.querySelector("#pets");
const petsPerSecondCounter = click.querySelector("#pets-per-second");
const barksCounter = click.querySelector("#barks");
const barksPerSecondCounter = click.querySelector("#barks-per-second");
const kissesCounter = click.querySelector("#kisses");
const kissesPerSecondCounter = click.querySelector("#kisses-per-second");
const barker = click.querySelector("#barker");
const toolsEl = click.querySelector(".tools");
const toolPriceFactor = 0.1;
const upgradePriceFactor = 0.2;
const upgradeProductionFactor = 1.1;
const toolData = {
hand: {
priceIn: "barks",
basePrice: 10,
petsPerSecond: 0.5,
},
puppy: {
priceIn: "pets",
basePrice: 5,
barksPerSecond: 0.5,
},
foodBowl: {
priceIn: "barks",
basePrice: 50,
barksPerSecond: 1.3,
},
kisser: {
priceIn: "pets",
basePrice: 500,
kissesPerSecond: 0.25,
},
};
let barks = 0;
let pets = 0;
let kisses = 0;
let tools = {};
let petsPerSecond = 0;
let barksPerSecond = 0;
let kissesPerSecond = 0;
function calcPrice(base, count) {
return Math.floor(base ** (1 + toolPriceFactor * count));
}
function calcUpgradePrice(base, count) {
return Math.floor((base * 2) ** (1 + upgradePriceFactor * count));
}
const getValue = (name) => {
if (name === "pets") {
return pets;
} else if (name === "barks") {
return barks;
} else if (name === "kisses") {
return kisses;
} else if (name === "petsPerSecond") {
return petsPerSecond;
} else if (name === "barksPerSecond") {
return barksPerSecond;
} else if (name === "kissesPerSecond") {
return kissesPerSecond;
}
};
const setValue = (name, value) => {
if (name === "pets") {
pets = value;
} else if (name === "barks") {
barks = value;
} else if (name === "kisses") {
kisses = value;
} else if (name === "petsPerSecond") {
petsPerSecond = value;
} else if (name === "barksPerSecond") {
barksPerSecond = value;
} else if (name === "kissesPerSecond") {
kissesPerSecond = value;
}
};
const updatePerSecondValues = () => {
let pets = 0;
let barks = 0;
let kisses = 0;
for (const [id, tool] of Object.entries(tools)) {
pets +=
(toolData[id].petsPerSecond || 0) *
tool.count *
tool.upgrades *
upgradeProductionFactor;
barks +=
(toolData[id].barksPerSecond || 0) *
tool.count *
tool.upgrades *
upgradeProductionFactor;
kisses +=
(toolData[id].kissesPerSecond || 0) *
tool.count *
tool.upgrades *
upgradeProductionFactor;
}
petsPerSecond = pets;
barksPerSecond = barks;
kissesPerSecond = kisses;
};
const updateDisplay = () => {
petsCounter.innerText = pets;
petsPerSecondCounter.innerText = petsPerSecond.toFixed(2);
barksCounter.innerText = barks;
barksPerSecondCounter.innerText = barksPerSecond.toFixed(2);
kissesCounter.innerText = kisses;
kissesPerSecondCounter.innerText = kissesPerSecond.toFixed(2);
};
for (const el of toolsEl.querySelectorAll(".tool")) {
const id = el.getAttribute("data-tool");
if (id) {
const data = toolData[id];
if (data) {
const toolInfo = {
count: 0,
upgrades: 1,
};
tools[id] = toolInfo;
const count = el.querySelector(".count");
const level = el.querySelector(".level");
const buy = el.querySelector(".buy");
const upgrade = el.querySelector(".upgrade");
const updateText = () => {
count.innerText = toolInfo.count;
level.innerText = toolInfo.upgrades;
const price = calcPrice(data.basePrice, toolInfo.count);
const upgradePrice = calcUpgradePrice(
data.basePrice,
toolInfo.upgrades
);
buy.innerText = `buy - ${price} ${data.priceIn}`;
upgrade.innerText = `upgrade - ${upgradePrice} kisses`;
};
updateText();
buy.addEventListener("click", () => {
const price = calcPrice(data.basePrice, toolInfo.count);
const v = getValue(data.priceIn);
if (v >= price) {
setValue(data.priceIn, v - price);
toolInfo.count += 1;
updatePerSecondValues();
updateText();
updateDisplay();
}
});
upgrade.addEventListener("click", () => {
const price = calcUpgradePrice(data.basePrice, toolInfo.upgrades);
if (kisses >= price) {
kisses -= price;
toolInfo.upgrades += 1;
updatePerSecondValues();
updateText();
updateDisplay();
}
});
}
}
}
barker.addEventListener("click", () => {
barks += 1;
updateDisplay();
});
let lastUpdate = 0;
let petsQueued = 0;
let barksQueued = 0;
let kissesQueued = 0;
const checkQueue = (name, queued) => {
const perSecond = getValue(`${name}PerSecond`);
if (perSecond > 0) {
const amount = 1000 / perSecond;
const toAdd = Math.floor(queued / amount);
setValue(name, getValue(name) + toAdd);
updateDisplay();
queued -= toAdd * amount;
} else {
queued = 0;
}
return queued;
};
const update = (ts) => {
requestAnimationFrame(update);
const diff = ts - lastUpdate;
petsQueued += diff;
barksQueued += diff;
kissesQueued += diff;
petsQueued = checkQueue("pets", petsQueued);
barksQueued = checkQueue("barks", barksQueued);
kissesQueued = checkQueue("kisses", kissesQueued);
lastUpdate = ts;
};
requestAnimationFrame(update);
})();

View file

@ -1,29 +0,0 @@
(function () {
"use strict";
const url = document.body.classList.contains("debug")
? "http://127.0.0.1:8787/api/pet"
: "https://cf.zyllian.workers.dev/api/pet";
const petCounter = document.querySelector("#pet-counter");
const internal = petCounter.querySelector(".internal");
const count = petCounter.querySelector(".count");
const petButton = petCounter.querySelector("button");
(async function () {
const r = await (await fetch(url)).json();
count.innerText = r.count;
})();
petButton.addEventListener("click", async () => {
petButton.disabled = true;
petButton.outerHTML = "| thanks! &lt;3";
count.innerText = Number.parseInt(count.innerText) + 1;
const r = await (await fetch(url, { method: "post" })).json();
if (r.count) {
count.innerText = r.count;
}
});
internal.style.display = "block";
})();

View file

@ -1,565 +0,0 @@
(function () {
"use strict";
const UPDATES_PER_MINUTE = 2;
const UPDATES_PER_HOUR = UPDATES_PER_MINUTE * 60;
const UPDATES_PER_DAY = UPDATES_PER_HOUR * 24;
/** the current pet version.. */
const CURRENT_PET_VERSION = 1;
/** the max food a pet will eat */
const MAX_FOOD = 100;
/** the amount of time it takes for a pet to have to GO */
const POTTY_TIME = 100;
/** how fast a pet's food value decays */
const FOOD_DECAY = MAX_FOOD / (UPDATES_PER_HOUR * 12); // to stay on top should be fed roughly once every 8 hours?
/** the rate at which a pet ages */
const AGING_RATE = 1;
/** how fast a pet's potty need decays */
const POTTY_DECAY = FOOD_DECAY / 2; // roughly every 4 hours?
/** how much mess can be in a pet's space at once */
const MAX_MESS = 5;
/** how fast a pet's happiness decays */
const HAPPINESS_DECAY = FOOD_DECAY / 1.5;
/** a pet's maximum happiness */
const MAX_HAPPINESS = 100;
/** how quickly a pet's happiness will be reduced by when hungry */
const HAPPINESS_EMPTY_STOMACH_MODIFIER = -10 / UPDATES_PER_HOUR;
/** how quickly a pet's happiness will be reduced by when their space is messy, per piece of mess */
const HAPPINESS_MESS_MODIFIER = -5 / UPDATES_PER_HOUR;
/** how quickly a pet's behavior drops when unhappy */
const BEHAVIOR_UNHAPPY_MODIFIER = -5 / UPDATES_PER_HOUR;
/** the amount of happiness gained when the pet is fed (excluding when the pet doesn't yet need food) */
const FEED_HAPPINESS = 5;
/** the amount of happiness gained when the pet is pet */
const PET_HAPPINESS = 20;
/** the amount of happiness gained when the pet's space is cleaned */
const CLEAN_HAPPINESS = 1;
/** the minimum amount of time between feedings */
const FEED_TIMER = 1000 * 60 * 60;
/** the minimum amount of time between pets */
const PET_TIMER = 60000;
/** the minimum amount of time between cleans */
const CLEAN_TIMER = 1000 * 60 * 60;
const PET_SAVE_KEY = "pet-game";
/** life stage for an egg */
const LIFE_STAGE_EGG = 1;
/** life stage for a pup */
const LIFE_STAGE_PUP = 2;
/** life stage for an adult */
const LIFE_STAGE_ADULT = 3;
/** life stage for an elder pet */
const LIFE_STAGE_ELDER = 4;
/** the time it takes for a pet to grow past the egg phase */
const EGG_TIME = UPDATES_PER_MINUTE;
/** the time it takes for a pet to grow past the pup phase */
const PUP_TIME = UPDATES_PER_DAY * 7;
/** the time it takes for a pet to grow past the adult phase */
const ADULT_TIME = UPDATES_PER_DAY * 15;
/** the time it takes for a pet to grow past the elder phase */
const ELDER_TIME = UPDATES_PER_DAY * 7;
const WIDTH_PUP = 150;
const HEIGHT_PUP = 150;
const WIDTH_ADULT = 250;
const HEIGHT_ADULT = 250;
const WIDTH_ELDER = 210;
const HEIGHT_ELDER = 210;
/** the different types of pets available */
const PET_TYPES = ["circle", "square", "triangle"];
const petDisplay = document.querySelector("#pet-display");
const thePet = petDisplay.querySelector(".the-pet");
const status = petDisplay.querySelector(".status");
const statusHungry = status.querySelector("[name=hungry]");
const statusStarving = status.querySelector("[name=starving]");
const statusUnhappy = status.querySelector("[name=unhappy]");
const statusMessy1 = status.querySelector("[name=messy-1]");
const statusMessy2 = status.querySelector("[name=messy-2]");
const statusMessy3 = status.querySelector("[name=messy-3]");
const petName = document.querySelectorAll(".pet-name");
const eggDiv = document.querySelector("div#egg");
const petSetup = document.querySelector("#pet-setup");
const adultInfo = document.querySelector("div#adult-info");
const elderInfo = document.querySelector("div#elder-info");
const passedAwayInfo = document.querySelector("div#passed-away-info");
const name = petSetup.querySelector("input[name=pet-name]");
const petActions = document.querySelector("div#pet-actions");
const pauseButton = petActions.querySelector("button[name=pause]");
const hatchedActions = petActions.querySelector("div[name=hatched-actions]");
const feedButton = hatchedActions.querySelector("button[name=feed]");
const petButton = hatchedActions.querySelector("button[name=pet]");
const cleanButton = hatchedActions.querySelector("button[name=clean]");
const debug = document.querySelector("div#debug-section");
const debugLifeStage = debug.querySelector("span[name=ls]");
const debugAge = debug.querySelector("span[name=a]");
const debugFood = debug.querySelector("span[name=f]");
const debugBehavior = debug.querySelector("span[name=b]");
const debugPotty = debug.querySelector("span[name=p]");
const debugMessCounter = debug.querySelector("span[name=mc]");
const debugHappiness = debug.querySelector("span[name=h]");
const forceUpdateButton = debug.querySelector("button#force-update");
const resetButton = debug.querySelector("button#reset");
let canFeed = true;
let canPet = true;
let canClean = true;
/**
* generates a random number within the given range
* @param {number} min the minimum number for the random generation
* @param {number} max the maximum number for the random generation
* @returns the generated number
*/
function rand(min, max) {
return Math.random() * (max - min) + min;
}
/**
* class containing information about a pet
*/
class Pet {
/** current pet version */
version = CURRENT_PET_VERSION;
/** whether the pet can die or not */
canDie = false;
/** whether the pet is alive or dead */
alive = true;
/** whether the pet simulation is paused */
paused = false;
/** whether the pet simulation needs an interactive advancement */
needsAdvancement = false;
/** the pet's current life stage */
lifeStage = LIFE_STAGE_EGG;
/** the pet's name */
name = "";
/** how much food the pet has stored */
food = MAX_FOOD;
/** the pet's age */
age = 0;
/** the pet's behavior score */
behavior = 0;
/** how long until the pet needs to go potty */
pottyTimer = POTTY_TIME;
/** how much mess the pet has made */
messCounter = 0;
/** the pet's current happiness */
_happiness = MAX_HAPPINESS;
/** the time the pet was last updated */
lastUpdate = Date.now();
/** the time the egg was found */
eggFound = Date.now();
/** the time the egg hatched */
hatched = Date.now();
/** the pet's type */
type = PET_TYPES[Math.floor(rand(0, PET_TYPES.length))];
/** the pet's color */
color = `rgb(${rand(0, 255)}, ${rand(0, 255)}, ${rand(0, 255)})`;
/** the pet's scaled width */
scaleWidth = rand(0.6, 1.4);
/** the pet's scaled height */
scaleHeight = rand(0.6, 1.4);
/**
* updates a pet
*/
update() {
if (!this.alive || this.paused || this.needsAdvancement) {
return;
}
console.log("update");
this.lastUpdate = Date.now();
this.age += AGING_RATE;
if (this.lifeStage !== LIFE_STAGE_EGG) {
this.food -= FOOD_DECAY;
this.pottyTimer -= POTTY_DECAY;
this.happiness -= HAPPINESS_DECAY;
if (this.food < 0) {
this.happiness += HAPPINESS_EMPTY_STOMACH_MODIFIER;
this.food = 0;
if (this.canDie) {
// TODO: pet dies
}
}
if (this.happiness <= 0) {
this.behavior -= BEHAVIOR_UNHAPPY_MODIFIER;
}
if (this.pottyTimer < 0) {
this.goPotty();
}
for (let i = 0; i < this.messCounter; i++) {
this.happiness += HAPPINESS_MESS_MODIFIER;
}
}
if (this.lifeStage === LIFE_STAGE_EGG && this.age >= EGG_TIME) {
this.needsAdvancement = true;
this.lifeStage = LIFE_STAGE_PUP;
this.age = 0;
} else if (this.lifeStage === LIFE_STAGE_PUP && this.age >= PUP_TIME) {
this.needsAdvancement = true;
this.lifeStage = LIFE_STAGE_ADULT;
this.age = 0;
} else if (
this.lifeStage === LIFE_STAGE_ADULT &&
this.age >= ADULT_TIME
) {
this.needsAdvancement = true;
this.lifeStage = LIFE_STAGE_ELDER;
this.age = 0;
} else if (
this.lifeStage === LIFE_STAGE_ELDER &&
this.age >= ELDER_TIME
) {
this.needsAdvancement = true;
this.alive = false;
// TODO: DEATH
}
this.updateDom();
}
/**
* updates the html dom
*/
updateDom() {
eggDiv.classList.add("hidden");
petSetup.classList.add("hidden");
hatchedActions.classList.remove("hidden");
thePet.classList.remove("egg");
thePet.classList.remove("pup");
thePet.classList.remove("adult");
thePet.classList.remove("elder");
thePet.classList.remove("dead");
statusHungry.classList.add("hidden");
statusStarving.classList.add("hidden");
statusUnhappy.classList.add("hidden");
statusMessy1.classList.add("hidden");
statusMessy2.classList.add("hidden");
statusMessy3.classList.add("hidden");
adultInfo.classList.add("hidden");
elderInfo.classList.add("hidden");
let width = 0;
let height = 0;
if (this.lifeStage === LIFE_STAGE_EGG) {
eggDiv.classList.remove("hidden");
hatchedActions.classList.add("hidden");
thePet.classList.add("egg");
} else if (this.lifeStage === LIFE_STAGE_PUP) {
if (this.needsAdvancement) {
petSetup.classList.remove("hidden");
}
thePet.classList.add("pup");
width = WIDTH_PUP;
height = HEIGHT_PUP;
} else if (this.lifeStage === LIFE_STAGE_ADULT) {
if (this.needsAdvancement) {
adultInfo.classList.remove("hidden");
}
thePet.classList.add("adult");
width = WIDTH_ADULT;
height = HEIGHT_ADULT;
} else if (this.lifeStage === LIFE_STAGE_ELDER) {
if (this.needsAdvancement) {
if (this.alive) {
elderInfo.classList.remove("hidden");
} else {
passedAwayInfo.classList.remove("hidden");
thePet.classList.add("elder");
width = WIDTH_ELDER;
height = HEIGHT_ELDER;
}
}
}
width *= this.scaleWidth;
height *= this.scaleHeight;
thePet.style.setProperty("--width", `${width}px`);
thePet.style.setProperty("--height", `${height}px`);
thePet.style.setProperty("--color", this.color);
let happinessFilter = 1 - this.happiness / MAX_HAPPINESS;
if (happinessFilter < 0.6) {
happinessFilter = 0;
}
happinessFilter = (happinessFilter - 0.6) * 2.5;
if (happinessFilter < 0) {
happinessFilter = 0;
}
thePet.style.setProperty(
"filter",
`grayscale(${happinessFilter * 100}%)`
);
if (!this.alive) {
thePet.classList.add("dead");
} else if (this.lifeStage !== LIFE_STAGE_EGG) {
thePet.classList.add(this.type);
if (this.food <= MAX_FOOD / 10) {
statusStarving.classList.remove("hidden");
} else if (this.food <= MAX_FOOD / 2) {
statusHungry.classList.remove("hidden");
}
if (this.happiness <= MAX_HAPPINESS / 3) {
statusUnhappy.classList.remove("hidden");
}
if (this.messCounter >= MAX_MESS) {
statusMessy3.classList.remove("hidden");
} else if (this.messCounter >= MAX_MESS / 2) {
statusMessy2.classList.remove("hidden");
} else if (this.messCounter > 0) {
statusMessy1.classList.remove("hidden");
}
}
if (this.paused) {
pauseButton.innerText = "unpause";
} else {
pauseButton.innerText = "pause";
}
debugLifeStage.innerText = this.lifeStage;
debugAge.innerText = this.age;
debugFood.innerText = this.food;
debugBehavior.innerText = this.behavior;
debugPotty.innerText = this.pottyTimer;
debugMessCounter.innerText = this.messCounter;
debugHappiness.innerText = this.happiness;
this.save();
}
/**
* feeds the pet
* @param {number} amount the amount to feed the pet by
*/
feed(amount) {
if (this.food > MAX_FOOD) {
return;
}
this.food += amount;
if (this.food <= MAX_FOOD) {
this.happiness += FEED_HAPPINESS;
}
this.updateDom();
}
/**
* makes the pet go potty
*/
goPotty() {
if (this.behavior > 45) {
// go potty properly
} else {
this.messCounter += 1;
if (this.messCounter > MAX_MESS) {
this.messCounter = MAX_MESS;
}
}
this.pottyTimer = POTTY_TIME;
pet.updateDom();
}
/**
* pets the pet
*/
pet() {
this.behavior += 0.5;
this.happiness += PET_HAPPINESS;
pet.updateDom();
}
/**
* cleans the pet's space
*/
clean() {
if (this.messCounter > 0) {
this.messCounter -= 1;
this.happiness += CLEAN_HAPPINESS;
} else {
this.behavior += 1;
this.happiness -= CLEAN_HAPPINESS;
}
pet.updateDom();
}
/**
* saves the pet
*/
save() {
localStorage.setItem(PET_SAVE_KEY, JSON.stringify(this));
}
/**
* loads the pet
*/
load() {
const item = localStorage.getItem(PET_SAVE_KEY);
if (item != undefined) {
const loaded = JSON.parse(localStorage.getItem(PET_SAVE_KEY));
for (let k of Object.keys(loaded)) {
this[k] = loaded[k];
}
this.version = CURRENT_PET_VERSION;
this.updateDom();
}
}
/** whether the pet can be updated */
get canUpdate() {
return !this.paused && !this.needsAdvancement;
}
/** the pet's happiness */
get happiness() {
return this._happiness;
}
set happiness(amount) {
if (amount < 0) {
amount = 0;
} else if (amount > MAX_HAPPINESS) {
amount = MAX_HAPPINESS;
}
this._happiness = amount;
}
}
let pet = new Pet();
petSetup.addEventListener("submit", (e) => {
e.preventDefault();
const newName = name.value;
if (newName.trim().length === 0) {
return;
}
pet.name = newName;
for (let name of petName) {
name.innerText = pet.name;
}
pet.hatched = Date.now();
pet.needsAdvancement = false;
pet.updateDom();
});
feedButton.addEventListener("click", () => {
if (!canFeed || !pet.canUpdate) {
return;
}
canFeed = false;
feedButton.disabled = true;
setTimeout(() => {
canFeed = true;
feedButton.disabled = false;
}, FEED_TIMER);
pet.feed(38);
});
petButton.addEventListener("click", () => {
if (!canPet || !pet.canUpdate) {
return;
}
canPet = false;
petButton.disabled = true;
setTimeout(() => {
canPet = true;
petButton.disabled = false;
}, PET_TIMER);
pet.pet();
});
cleanButton.addEventListener("click", () => {
if (!canClean || !pet.canUpdate) {
return;
}
canClean = false;
cleanButton.disabled = true;
setTimeout(() => {
canClean = true;
cleanButton.disabled = false;
}, CLEAN_TIMER);
pet.clean();
});
pauseButton.addEventListener("click", () => {
pet.paused = !pet.paused;
pet.updateDom();
});
const advance = () => {
console.log(pet);
pet.needsAdvancement = false;
pet.updateDom();
console.log(pet);
};
for (let btn of document.querySelectorAll("button.advance")) {
btn.addEventListener("click", advance);
}
passedAwayInfo.querySelector("button").addEventListener("click", () => {
pet = new Pet();
pet.updateDom();
});
const update = () => {
pet.update();
};
setInterval(update, 60000 / UPDATES_PER_MINUTE);
forceUpdateButton.addEventListener("click", update);
resetButton.addEventListener("click", () => {
thePet.classList.remove(pet.type);
pet = new Pet();
pet.updateDom();
});
pet.load();
for (let name of petName) {
name.innerText = pet.name;
}
if (document.body.classList.contains("debug")) {
debug.classList.remove("hidden");
document.pet = pet;
}
const updates = Math.floor(
(Date.now() - pet.lastUpdate) / (60000 / UPDATES_PER_MINUTE)
);
for (let i = 0; i < updates; i++) {
pet.update();
}
pet.updateDom();
console.log(pet);
})();

View file

@ -1,27 +0,0 @@
#click {
.resources {
display: grid;
grid-template-columns: repeat(3, 0fr);
grid-auto-flow: row;
& > span {
margin-right: 5px;
width: max-content;
&.resource {
font-weight: bold;
}
}
}
#barker {
font-size: 2rem;
padding: 8px;
}
.tools {
.name {
font-weight: bold;
}
}
}

View file

@ -59,48 +59,6 @@ abbr {
cursor: help;
}
.images-list {
display: flex;
flex-wrap: wrap;
.image {
position: relative;
padding: 4px;
height: auto;
.image-actual {
display: block;
width: 300px;
height: 100%;
object-fit: cover;
}
.title {
position: absolute;
bottom: 0;
left: 0;
padding: 4px;
margin: 4px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
}
}
}
.image-full {
.title,
.tags-title {
margin-bottom: 0;
}
.image-actual {
width: 100%;
max-height: 80vh;
object-fit: contain;
background-color: rgba(0, 0, 0, 0.3);
}
}
.blog-post-list {
.post {
p {
@ -208,13 +166,3 @@ abbr {
.flex-spacer {
flex-grow: 1;
}
#pet-counter {
position: fixed;
bottom: 4px;
right: 4px;
.internal {
display: none;
}
}

View file

@ -1,50 +0,0 @@
#pet {
#pet-display {
.the-pet {
--color: red;
--width: 250px;
--height: 250px;
background-color: var(--color);
width: var(--width);
height: var(--height);
margin-bottom: 8px;
&.egg {
background-color: white;
// egg shape from https://css-tricks.com/the-shapes-of-css/
width: 126px;
height: 180px;
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
}
&.square {
border-radius: 2px;
}
&.circle {
border-radius: 50%;
}
&.triangle {
clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
}
.status {
font-style: italic;
}
}
#debug-section [name] {
font-weight: bold;
}
button {
margin-bottom: 4px;
}
.hidden {
display: none;
}
}

View file

@ -1,7 +1,3 @@
{% macro badge(badge, url, alt) %}
<a href="{{url}}"><img src="/badges/{{badge}}" alt="{{alt}}"></a>
{% endmacro badge %}
<!DOCTYPE html>
<html lang="en">
@ -10,7 +6,6 @@
<meta name="referrer" content="no-referrer">
<link rel="stylesheet" href="/styles/index.css">
<title>{{ title }}</title>
<script type="text/javascript" src="/js/pet-me.js" defer></script>
{{ head | safe }}
{% for script in scripts %}
<script type="text/javascript" src="{{script}}" defer></script>
@ -23,40 +18,17 @@
<body>
<header class="main-header">
<span>
<a class="name" href="/">zyl's website</a> |
<span class="pronouns">it/puppy(/she)</span>
<a class="name" href="/">webdog</a>
</span>
<span class="spacer"></span>
<a href="/games">games</a> |
<a href="/projects">my projects</a> |
<a href="/blog/">blog</a> |
<a href="/images/">images</a> |
<a href="/pay-me">pay me!</a> |
<a href="https://github.com/zyllian/zyllian.github.io" rel="noopener noreferrer">source</a>
<a href="https://github.com/zyllian/webdog" rel="noopener noreferrer">github</a>
</header>
<div id="content">
<main class="page">
{% block content %}{{ page | safe }}{% endblock content %}
</main>
</div>
<div class="flex-spacer"></div>
<hr />
<footer id="footer">
bark bark awruff :3
<div class="badges">
{{ self::badge(badge="transbian.png", url="https://badge.les.bi", alt="transgender and lesbian flags") }}
</div>
</footer>
<div id="pet-counter">
<noscript>enable js to pet me :3</noscript>
<div class="internal">
<span class="count">???</span> pets
<button>pet me :3</button>
</div>
</div>
</body>
</html>

View file

@ -1,31 +0,0 @@
{% macro resource(id, name) %}
<span class="resource">{{name}}</span><span id={{id}}>0</span> <span>(<span id="{{id}}-per-second">0</span>/s)</span>
{% endmacro resource %}
{% macro tool(id, name, description) %}
<div class="tool" data-tool={{id}}>
<p class="name">{{name}} (<span class="count">0</span>, lvl <span class="level">1</span>)</p>
<p class="description">{{description}}</p>
<button class="buy">buy</button> <button class="upgrade">upgrade</button>
</div>
{% endmacro tool %}
<div id="click">
<p>WARNING: no save mechanic is implemented yet!!</p>
<h1>click</h1>
<noscript>
<h1>javascript is required for the clicker game!!</h1>
</noscript>
<div class="resources">
{{ self::resource(id="pets", name="pets") }}
{{ self::resource(id="barks", name="barks") }}
{{ self::resource(id="kisses", name="kisses") }}
</div>
<button id="barker">bark</button>
<div class="tools">
{{ self::tool(id="hand", name="hand", description="don't bite the hand that pets you") }}
{{ self::tool(id="puppy", name="puppy", description="arf arf wruff :3") }}
{{ self::tool(id="foodBowl", name="food bowl", description="more food for more barking") }}
{{ self::tool(id="kisser", name="kisser wow", description="someone to kiss all those poor puppies,,") }}
</div>
</div>

View file

@ -1,13 +0,0 @@
<div class="image-full">
<h1 class="title">{{title}}</h1>
<span class="timestamp">published {{timestamp}}</span>
<img class="image-actual" src="{{cdn_file}}" alt="{{alt}}">
{{ content | safe }}
<p><a href="{{cdn_file}}">view full size image</a></p>
<h3 class="tags-title">tags</h3>
<div class="image-tags">
{% for tag in tags %}
<a class="tag" href="/i/tag/{{tag}}/">{{tag}}</a>{% if not loop.last %},{% endif %}
{% endfor %}
</div>
</div>

View file

@ -1,23 +0,0 @@
{% if tag %}
<h1>images tagged {{tag}}</h1>
<p><a href="/images/">view all images</a></p>
{% else %}
<h1>images</h1>
<p><a href="/i/tags">view image tags</a></p>
<p><a href="rss.xml">rss feed</a></p>
{% endif %}
<h3>page {{page}}/{{page_max}}</h3>
{% if previous %}
<a href="./{{previous}}">previous page</a>
{% endif %}
{% if next %}
<a href="./{{next}}">next page</a>
{% endif %}
<div class="images-list">
{% for resource in resources %}
<a class="image" href="/i/{{resource.id}}">
<img class="image-actual" src="{{resource.cdn_file}}" alt="{{resource.alt}}">
<span class="title">{{resource.title}}</span>
</a>
{% endfor %}
</div>

View file

@ -1 +0,0 @@
<img src="{{cdn_file}}" alt="{{alt}}">

View file

@ -1,6 +1,6 @@
use std::path::Path;
use zyl_site::Site;
use webdog::Site;
#[cfg(feature = "serve")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]