This commit is contained in:
root 2026-05-17 03:44:36 +00:00
commit f5dcf4d107
187 changed files with 7066 additions and 0 deletions

51
.dockerignore Normal file
View file

@ -0,0 +1,51 @@
# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
# Ignore git directory.
/.git/
/.gitignore
# Ignore bundler config.
/.bundle
# Ignore all environment files.
/.env*
# Ignore all default key files.
/config/master.key
/config/credentials/*.key
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/.keep
# Ignore storage (uploaded files in development and any SQLite databases).
/storage/*
!/storage/.keep
/tmp/storage/*
!/tmp/storage/.keep
# Ignore assets.
/node_modules/
/app/assets/builds/*
!/app/assets/builds/.keep
/public/assets
# Ignore CI service files.
/.github
# Ignore Kamal files.
/config/deploy*.yml
/.kamal
# Ignore development files
/.devcontainer
# Ignore Docker-related files
/.dockerignore
/Dockerfile*

9
.gitattributes vendored Normal file
View file

@ -0,0 +1,9 @@
# See https://git-scm.com/docs/gitattributes for more about git attribute files.
# Mark the database schema as having been generated.
db/schema.rb linguist-generated
# Mark any vendored files as having been vendored.
vendor/* linguist-vendored
config/credentials/*.yml.enc diff=rails_credentials
config/credentials.yml.enc diff=rails_credentials

12
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: bundler
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10

124
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,124 @@
name: CI
on:
pull_request:
push:
branches: [ main ]
jobs:
scan_ruby:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Scan for common Rails security vulnerabilities using static analysis
run: bin/brakeman --no-pager
- name: Scan for known security vulnerabilities in gems used
run: bin/bundler-audit
scan_js:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Scan for security vulnerabilities in JavaScript dependencies
run: bin/importmap audit
lint:
runs-on: ubuntu-latest
env:
RUBOCOP_CACHE_ROOT: tmp/rubocop
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Prepare RuboCop cache
uses: actions/cache@v4
env:
DEPENDENCIES_HASH: ${{ hashFiles('.ruby-version', '**/.rubocop.yml', '**/.rubocop_todo.yml', 'Gemfile.lock') }}
with:
path: ${{ env.RUBOCOP_CACHE_ROOT }}
key: rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-${{ github.ref_name == github.event.repository.default_branch && github.run_id || 'default' }}
restore-keys: |
rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-
- name: Lint code for consistent style
run: bin/rubocop -f github
test:
runs-on: ubuntu-latest
# services:
# redis:
# image: valkey/valkey:8
# ports:
# - 6379:6379
# options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Run tests
env:
RAILS_ENV: test
# RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
# REDIS_URL: redis://localhost:6379/0
run: bin/rails db:test:prepare test
system-test:
runs-on: ubuntu-latest
# services:
# redis:
# image: valkey/valkey:8
# ports:
# - 6379:6379
# options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Run System Tests
env:
RAILS_ENV: test
# RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
# REDIS_URL: redis://localhost:6379/0
run: bin/rails db:test:prepare test:system
- name: Keep screenshots from failed system tests
uses: actions/upload-artifact@v4
if: failure()
with:
name: screenshots
path: ${{ github.workspace }}/tmp/screenshots
if-no-files-found: ignore

37
.gitignore vendored Normal file
View file

@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# Temporary files generated by your text editor or operating system
# belong in git's global ignore instead:
# `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore`
# Ignore bundler config.
/.bundle
# Ignore all environment files.
/.env*
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/
!/tmp/pids/.keep
# Ignore storage (uploaded files in development and any SQLite databases).
/storage/*
!/storage/.keep
/tmp/storage/*
!/tmp/storage/
!/tmp/storage/.keep
/storage
storage/
/public/assets
# Ignore key files for decrypting credentials and more.
/config/*.key
/public/assets
db/

View file

@ -0,0 +1,3 @@
#!/bin/sh
echo "Docker set up on $KAMAL_HOSTS..."

View file

@ -0,0 +1,3 @@
#!/bin/sh
echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."

14
.kamal/hooks/post-deploy.sample Executable file
View file

@ -0,0 +1,14 @@
#!/bin/sh
# A sample post-deploy hook
#
# These environment variables are available:
# KAMAL_RECORDED_AT
# KAMAL_PERFORMER
# KAMAL_VERSION
# KAMAL_HOSTS
# KAMAL_ROLES (if set)
# KAMAL_DESTINATION (if set)
# KAMAL_RUNTIME
echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"

View file

@ -0,0 +1,3 @@
#!/bin/sh
echo "Rebooted kamal-proxy on $KAMAL_HOSTS"

View file

@ -0,0 +1,3 @@
#!/bin/sh
echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."

51
.kamal/hooks/pre-build.sample Executable file
View file

@ -0,0 +1,51 @@
#!/bin/sh
# A sample pre-build hook
#
# Checks:
# 1. We have a clean checkout
# 2. A remote is configured
# 3. The branch has been pushed to the remote
# 4. The version we are deploying matches the remote
#
# These environment variables are available:
# KAMAL_RECORDED_AT
# KAMAL_PERFORMER
# KAMAL_VERSION
# KAMAL_HOSTS
# KAMAL_ROLES (if set)
# KAMAL_DESTINATION (if set)
if [ -n "$(git status --porcelain)" ]; then
echo "Git checkout is not clean, aborting..." >&2
git status --porcelain >&2
exit 1
fi
first_remote=$(git remote)
if [ -z "$first_remote" ]; then
echo "No git remote set, aborting..." >&2
exit 1
fi
current_branch=$(git branch --show-current)
if [ -z "$current_branch" ]; then
echo "Not on a git branch, aborting..." >&2
exit 1
fi
remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1)
if [ -z "$remote_head" ]; then
echo "Branch not pushed to remote, aborting..." >&2
exit 1
fi
if [ "$KAMAL_VERSION" != "$remote_head" ]; then
echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2
exit 1
fi
exit 0

47
.kamal/hooks/pre-connect.sample Executable file
View file

@ -0,0 +1,47 @@
#!/usr/bin/env ruby
# A sample pre-connect check
#
# Warms DNS before connecting to hosts in parallel
#
# These environment variables are available:
# KAMAL_RECORDED_AT
# KAMAL_PERFORMER
# KAMAL_VERSION
# KAMAL_HOSTS
# KAMAL_ROLES (if set)
# KAMAL_DESTINATION (if set)
# KAMAL_RUNTIME
hosts = ENV["KAMAL_HOSTS"].split(",")
results = nil
max = 3
elapsed = Benchmark.realtime do
results = hosts.map do |host|
Thread.new do
tries = 1
begin
Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
rescue SocketError
if tries < max
puts "Retrying DNS warmup: #{host}"
tries += 1
sleep rand
retry
else
puts "DNS warmup failed: #{host}"
host
end
end
tries
end
end.map(&:value)
end
retries = results.sum - hosts.size
nopes = results.count { |r| r == max }
puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]

122
.kamal/hooks/pre-deploy.sample Executable file
View file

@ -0,0 +1,122 @@
#!/usr/bin/env ruby
# A sample pre-deploy hook
#
# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
#
# Fails unless the combined status is "success"
#
# These environment variables are available:
# KAMAL_RECORDED_AT
# KAMAL_PERFORMER
# KAMAL_VERSION
# KAMAL_HOSTS
# KAMAL_COMMAND
# KAMAL_SUBCOMMAND
# KAMAL_ROLES (if set)
# KAMAL_DESTINATION (if set)
# Only check the build status for production deployments
if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
exit 0
end
require "bundler/inline"
# true = install gems so this is fast on repeat invocations
gemfile(true, quiet: true) do
source "https://rubygems.org"
gem "octokit"
gem "faraday-retry"
end
MAX_ATTEMPTS = 72
ATTEMPTS_GAP = 10
def exit_with_error(message)
$stderr.puts message
exit 1
end
class GithubStatusChecks
attr_reader :remote_url, :git_sha, :github_client, :combined_status
def initialize
@remote_url = github_repo_from_remote_url
@git_sha = `git rev-parse HEAD`.strip
@github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
refresh!
end
def refresh!
@combined_status = github_client.combined_status(remote_url, git_sha)
end
def state
combined_status[:state]
end
def first_status_url
first_status = combined_status[:statuses].find { |status| status[:state] == state }
first_status && first_status[:target_url]
end
def complete_count
combined_status[:statuses].count { |status| status[:state] != "pending"}
end
def total_count
combined_status[:statuses].count
end
def current_status
if total_count > 0
"Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
else
"Build not started..."
end
end
private
def github_repo_from_remote_url
url = `git config --get remote.origin.url`.strip.delete_suffix(".git")
if url.start_with?("https://github.com/")
url.delete_prefix("https://github.com/")
elsif url.start_with?("git@github.com:")
url.delete_prefix("git@github.com:")
else
url
end
end
end
$stdout.sync = true
begin
puts "Checking build status..."
attempts = 0
checks = GithubStatusChecks.new
loop do
case checks.state
when "success"
puts "Checks passed, see #{checks.first_status_url}"
exit 0
when "failure"
exit_with_error "Checks failed, see #{checks.first_status_url}"
when "pending"
attempts += 1
end
exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
puts checks.current_status
sleep(ATTEMPTS_GAP)
checks.refresh!
end
rescue Octokit::NotFound
exit_with_error "Build status could not be found"
end

View file

@ -0,0 +1,3 @@
#!/bin/sh
echo "Rebooting kamal-proxy on $KAMAL_HOSTS..."

19
.kamal/secrets Normal file
View file

@ -0,0 +1,19 @@
# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.
# Example of extracting secrets from 1password (or another compatible pw manager)
# SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY)
# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS})
# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS})
# Example of extracting secrets from Rails credentials
# KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password)
# Use a GITHUB_TOKEN if private repositories are needed for the image
# GITHUB_TOKEN=$(gh config get -h github.com oauth_token)
# Grab the registry password from ENV
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
# Improve security by using a password manager. Never check config/master.key into git!
RAILS_MASTER_KEY=$(cat config/master.key)

8
.rubocop.yml Normal file
View file

@ -0,0 +1,8 @@
# Omakase Ruby styling for Rails
inherit_gem: { rubocop-rails-omakase: rubocop.yml }
# Overwrite or add rules to create your own house style
#
# # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
# Layout/SpaceInsideArrayLiteralBrackets:
# Enabled: false

1
.ruby-version Normal file
View file

@ -0,0 +1 @@
ruby-3.4.7

76
Dockerfile Normal file
View file

@ -0,0 +1,76 @@
# syntax=docker/dockerfile:1
# check=error=true
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t galaxy .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name galaxy galaxy
# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.4.7
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
# Rails app lives here
WORKDIR /rails
# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \
ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Set production environment variables and enable jemalloc for reduced memory usage and latency.
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development" \
LD_PRELOAD="/usr/local/lib/libjemalloc.so"
# Throw-away build stage to reduce size of final image
FROM base AS build
# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Install application gems
COPY Gemfile Gemfile.lock vendor ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495
bundle exec bootsnap precompile -j 1 --gemfile
# Copy application code
COPY . .
# Precompile bootsnap code for faster boot times.
# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495
RUN bundle exec bootsnap precompile -j 1 app/ lib/
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
# Final stage for app image
FROM base
# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash
USER 1000:1000
# Copy built artifacts: gems, application
COPY --chown=rails:rails --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --chown=rails:rails --from=build /rails /rails
# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start server via Thruster by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/thrust", "./bin/rails", "server"]

68
Gemfile Normal file
View file

@ -0,0 +1,68 @@
source "https://rubygems.org"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 8.1.1"
# The modern asset pipeline for Rails [https://github.com/rails/propshaft]
gem "propshaft"
# Use sqlite3 as the database for Active Record
gem "sqlite3", ">= 2.1"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
gem "bcrypt", "~> 3.1.7"
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]
# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable
gem "solid_cache"
gem "solid_queue"
gem "solid_cable"
gem "rss"
# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false
# Deploy this application anywhere as a Docker container [https://kamal-deploy.org]
gem "kamal", require: false
# Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/]
gem "thruster", require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
gem "image_processing", "~> 1.2"
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
# Audits gems for known security defects (use config/bundler-audit.yml to ignore issues)
gem "bundler-audit", require: false
# Static analysis for security vulnerabilities [https://brakemanscanner.org/]
gem "brakeman", require: false
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
gem "rubocop-rails-omakase", require: false
end
group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"
end
group :test do
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
gem "capybara"
gem "selenium-webdriver"
end

414
Gemfile.lock Normal file
View file

@ -0,0 +1,414 @@
GEM
remote: https://rubygems.org/
specs:
action_text-trix (2.1.15)
railties
actioncable (8.1.1)
actionpack (= 8.1.1)
activesupport (= 8.1.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (8.1.1)
actionpack (= 8.1.1)
activejob (= 8.1.1)
activerecord (= 8.1.1)
activestorage (= 8.1.1)
activesupport (= 8.1.1)
mail (>= 2.8.0)
actionmailer (8.1.1)
actionpack (= 8.1.1)
actionview (= 8.1.1)
activejob (= 8.1.1)
activesupport (= 8.1.1)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (8.1.1)
actionview (= 8.1.1)
activesupport (= 8.1.1)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (8.1.1)
action_text-trix (~> 2.1.15)
actionpack (= 8.1.1)
activerecord (= 8.1.1)
activestorage (= 8.1.1)
activesupport (= 8.1.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (8.1.1)
activesupport (= 8.1.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (8.1.1)
activesupport (= 8.1.1)
globalid (>= 0.3.6)
activemodel (8.1.1)
activesupport (= 8.1.1)
activerecord (8.1.1)
activemodel (= 8.1.1)
activesupport (= 8.1.1)
timeout (>= 0.4.0)
activestorage (8.1.1)
actionpack (= 8.1.1)
activejob (= 8.1.1)
activerecord (= 8.1.1)
activesupport (= 8.1.1)
marcel (~> 1.0)
activesupport (8.1.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
json
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
ast (2.4.3)
base64 (0.3.0)
bcrypt (3.1.20)
bcrypt_pbkdf (1.1.1)
bigdecimal (3.3.1)
bindex (0.8.1)
bootsnap (1.18.6)
msgpack (~> 1.2)
brakeman (7.1.1)
racc
builder (3.3.0)
bundler-audit (0.9.2)
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
capybara (3.40.0)
addressable
matrix
mini_mime (>= 0.1.3)
nokogiri (~> 1.11)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
concurrent-ruby (1.3.5)
connection_pool (2.5.4)
crass (1.0.6)
date (3.5.0)
debug (1.11.0)
irb (~> 1.10)
reline (>= 0.3.8)
dotenv (3.1.8)
drb (2.2.3)
ed25519 (1.4.0)
erb (5.1.3)
erubi (1.13.1)
et-orbi (1.4.0)
tzinfo
ffi (1.17.2-aarch64-linux-gnu)
ffi (1.17.2-aarch64-linux-musl)
ffi (1.17.2-arm-linux-gnu)
ffi (1.17.2-arm-linux-musl)
ffi (1.17.2-x86_64-linux-gnu)
ffi (1.17.2-x86_64-linux-musl)
fugit (1.12.1)
et-orbi (~> 1.4)
raabro (~> 1.4)
globalid (1.3.0)
activesupport (>= 6.1)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
image_processing (1.14.0)
mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.2.2)
actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
railties (>= 6.0.0)
io-console (0.8.1)
irb (1.15.3)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jbuilder (2.14.1)
actionview (>= 7.0.0)
activesupport (>= 7.0.0)
json (2.16.0)
kamal (2.8.2)
activesupport (>= 7.0)
base64 (~> 0.2)
bcrypt_pbkdf (~> 1.0)
concurrent-ruby (~> 1.2)
dotenv (~> 3.1)
ed25519 (~> 1.4)
net-ssh (~> 7.3)
sshkit (>= 1.23.0, < 2.0)
thor (~> 1.3)
zeitwerk (>= 2.6.18, < 3.0)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0)
loofah (2.24.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.9.0)
logger
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
marcel (1.1.0)
matrix (0.4.3)
mini_magick (5.3.1)
logger
mini_mime (1.1.5)
minitest (5.26.0)
msgpack (1.8.0)
net-imap (0.5.12)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-scp (4.1.0)
net-ssh (>= 2.6.5, < 8.0.0)
net-sftp (4.0.0)
net-ssh (>= 5.0.0, < 8.0.0)
net-smtp (0.5.1)
net-protocol
net-ssh (7.3.0)
nio4r (2.7.5)
nokogiri (1.18.10-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.10-aarch64-linux-musl)
racc (~> 1.4)
nokogiri (1.18.10-arm-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.10-arm-linux-musl)
racc (~> 1.4)
nokogiri (1.18.10-x86_64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.10-x86_64-linux-musl)
racc (~> 1.4)
ostruct (0.6.3)
parallel (1.27.0)
parser (3.3.10.0)
ast (~> 2.4.1)
racc
pp (0.6.3)
prettyprint
prettyprint (0.2.0)
prism (1.6.0)
propshaft (1.3.1)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
psych (5.2.6)
date
stringio
public_suffix (6.0.2)
puma (7.1.0)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.8.1)
rack (3.2.4)
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
rackup (2.2.1)
rack (>= 3)
rails (8.1.1)
actioncable (= 8.1.1)
actionmailbox (= 8.1.1)
actionmailer (= 8.1.1)
actionpack (= 8.1.1)
actiontext (= 8.1.1)
actionview (= 8.1.1)
activejob (= 8.1.1)
activemodel (= 8.1.1)
activerecord (= 8.1.1)
activestorage (= 8.1.1)
activesupport (= 8.1.1)
bundler (>= 1.15.0)
railties (= 8.1.1)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.2)
loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (8.1.1)
actionpack (= 8.1.1)
activesupport (= 8.1.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.3.1)
rdoc (6.15.1)
erb
psych (>= 4.0.0)
tsort
regexp_parser (2.11.3)
reline (0.6.2)
io-console (~> 0.5)
rexml (3.4.4)
rss (0.3.1)
rexml
rubocop (1.81.7)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.47.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.47.1)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-performance (1.26.1)
lint_roller (~> 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0)
rubocop-rails (2.33.4)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.44.0, < 2.0)
rubocop-rails-omakase (1.1.0)
rubocop (>= 1.72)
rubocop-performance (>= 1.24)
rubocop-rails (>= 2.30)
ruby-progressbar (1.13.0)
ruby-vips (2.2.5)
ffi (~> 1.12)
logger
rubyzip (3.2.2)
securerandom (0.4.1)
selenium-webdriver (4.38.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 4.0)
websocket (~> 1.0)
solid_cable (3.0.12)
actioncable (>= 7.2)
activejob (>= 7.2)
activerecord (>= 7.2)
railties (>= 7.2)
solid_cache (1.0.9)
activejob (>= 7.2)
activerecord (>= 7.2)
railties (>= 7.2)
solid_queue (1.2.4)
activejob (>= 7.1)
activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1)
fugit (~> 1.11)
railties (>= 7.1)
thor (>= 1.3.1)
sqlite3 (2.8.0-aarch64-linux-gnu)
sqlite3 (2.8.0-aarch64-linux-musl)
sqlite3 (2.8.0-arm-linux-gnu)
sqlite3 (2.8.0-arm-linux-musl)
sqlite3 (2.8.0-x86_64-linux-gnu)
sqlite3 (2.8.0-x86_64-linux-musl)
sshkit (1.24.0)
base64
logger
net-scp (>= 1.1.2)
net-sftp (>= 2.1.2)
net-ssh (>= 2.8.0)
ostruct
stimulus-rails (1.3.4)
railties (>= 6.0.0)
stringio (3.1.7)
thor (1.4.0)
thruster (0.1.16)
thruster (0.1.16-aarch64-linux)
thruster (0.1.16-x86_64-linux)
timeout (0.4.4)
tsort (0.2.0)
turbo-rails (2.0.20)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.1.0)
uri (1.1.1)
useragent (0.16.11)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
websocket (1.2.11)
websocket-driver (0.8.0)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.7.3)
PLATFORMS
aarch64-linux
aarch64-linux-gnu
aarch64-linux-musl
arm-linux-gnu
arm-linux-musl
x86_64-linux
x86_64-linux-gnu
x86_64-linux-musl
DEPENDENCIES
bcrypt (~> 3.1.7)
bootsnap
brakeman
bundler-audit
capybara
debug
image_processing (~> 1.2)
importmap-rails
jbuilder
kamal
propshaft
puma (>= 5.0)
rails (~> 8.1.1)
rss
rubocop-rails-omakase
selenium-webdriver
solid_cable
solid_cache
solid_queue
sqlite3 (>= 2.1)
stimulus-rails
thruster
turbo-rails
tzinfo-data
web-console
BUNDLED WITH
2.6.9

3
README.md Normal file
View file

@ -0,0 +1,3 @@
this is the code for my silly personal playground site. i use it to learn rails stuff on! it's a mess, don't use this code, or do if you want idk i cant stop you
this is accessible at https://lesbian.kissing.computer, currently

6
Rakefile Normal file
View file

@ -0,0 +1,6 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative "config/application"
Rails.application.load_tasks

0
app/assets/images/.keep Normal file
View file

View file

@ -0,0 +1,440 @@
/*
* Default Trix editor styles. See Action Text overwrites below.
*/
trix-editor {
border: 1px solid #bbb;
border-radius: 3px;
margin: 0;
padding: 0.4em 0.6em;
min-height: 5em;
outline: none; }
trix-toolbar * {
box-sizing: border-box; }
trix-toolbar .trix-button-row {
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
overflow-x: auto; }
trix-toolbar .trix-button-group {
display: flex;
margin-bottom: 10px;
border: 1px solid #bbb;
border-top-color: #ccc;
border-bottom-color: #888;
border-radius: 3px; }
trix-toolbar .trix-button-group:not(:first-child) {
margin-left: 1.5vw; }
@media (max-width: 768px) {
trix-toolbar .trix-button-group:not(:first-child) {
margin-left: 0; } }
trix-toolbar .trix-button-group-spacer {
flex-grow: 1; }
@media (max-width: 768px) {
trix-toolbar .trix-button-group-spacer {
display: none; } }
trix-toolbar .trix-button {
position: relative;
float: left;
color: rgba(0, 0, 0, 0.6);
font-size: 0.75em;
font-weight: 600;
white-space: nowrap;
padding: 0 0.5em;
margin: 0;
outline: none;
border: none;
border-bottom: 1px solid #ddd;
border-radius: 0;
background: transparent; }
trix-toolbar .trix-button:not(:first-child) {
border-left: 1px solid #ccc; }
trix-toolbar .trix-button.trix-active {
background: #cbeefa;
color: black; }
trix-toolbar .trix-button:not(:disabled) {
cursor: pointer; }
trix-toolbar .trix-button:disabled {
color: rgba(0, 0, 0, 0.125); }
@media (max-width: 768px) {
trix-toolbar .trix-button {
letter-spacing: -0.01em;
padding: 0 0.3em; } }
trix-toolbar .trix-button--icon {
font-size: inherit;
width: 2.6em;
height: 1.6em;
max-width: calc(0.8em + 4vw);
text-indent: -9999px; }
@media (max-width: 768px) {
trix-toolbar .trix-button--icon {
height: 2em;
max-width: calc(0.8em + 3.5vw); } }
trix-toolbar .trix-button--icon::before {
display: inline-block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0.6;
content: "";
background-position: center;
background-repeat: no-repeat;
background-size: contain; }
@media (max-width: 768px) {
trix-toolbar .trix-button--icon::before {
right: 6%;
left: 6%; } }
trix-toolbar .trix-button--icon.trix-active::before {
opacity: 1; }
trix-toolbar .trix-button--icon:disabled::before {
opacity: 0.125; }
trix-toolbar .trix-button--icon-attach::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10.5%2018V7.5c0-2.25%203-2.25%203%200V18c0%204.125-6%204.125-6%200V7.5c0-6.375%209-6.375%209%200V18%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-miterlimit%3D%2210%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E");
top: 8%;
bottom: 4%; }
trix-toolbar .trix-button--icon-bold::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6.522%2019.242a.5.5%200%200%201-.5-.5V5.35a.5.5%200%200%201%20.5-.5h5.783c1.347%200%202.46.345%203.24.982.783.64%201.216%201.562%201.216%202.683%200%201.13-.587%202.129-1.476%202.71a.35.35%200%200%200%20.049.613c1.259.56%202.101%201.742%202.101%203.22%200%201.282-.483%202.334-1.363%203.063-.876.726-2.132%201.12-3.66%201.12h-5.89ZM9.27%207.347v3.362h1.97c.766%200%201.347-.17%201.733-.464.38-.291.587-.716.587-1.27%200-.53-.183-.928-.513-1.198-.334-.273-.838-.43-1.505-.43H9.27Zm0%205.606v3.791h2.389c.832%200%201.448-.177%201.853-.497.399-.315.614-.786.614-1.423%200-.62-.22-1.077-.63-1.385-.418-.313-1.053-.486-1.905-.486H9.27Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-italic::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M9%205h6.5v2h-2.23l-2.31%2010H13v2H6v-2h2.461l2.306-10H9V5Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-link::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M18.948%205.258a4.337%204.337%200%200%200-6.108%200L11.217%206.87a.993.993%200%200%200%200%201.41c.392.39%201.027.39%201.418%200l1.623-1.613a2.323%202.323%200%200%201%203.271%200%202.29%202.29%200%200%201%200%203.251l-2.393%202.38a3.021%203.021%200%200%201-4.255%200l-.05-.049a1.007%201.007%200%200%200-1.418%200%20.993.993%200%200%200%200%201.41l.05.049a5.036%205.036%200%200%200%207.091%200l2.394-2.38a4.275%204.275%200%200%200%200-6.072Zm-13.683%2013.6a4.337%204.337%200%200%200%206.108%200l1.262-1.255a.993.993%200%200%200%200-1.41%201.007%201.007%200%200%200-1.418%200L9.954%2017.45a2.323%202.323%200%200%201-3.27%200%202.29%202.29%200%200%201%200-3.251l2.344-2.331a2.579%202.579%200%200%201%203.631%200c.392.39%201.027.39%201.419%200a.993.993%200%200%200%200-1.41%204.593%204.593%200%200%200-6.468%200l-2.345%202.33a4.275%204.275%200%200%200%200%206.072Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-strike::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6%2014.986c.088%202.647%202.246%204.258%205.635%204.258%203.496%200%205.713-1.728%205.713-4.463%200-.275-.02-.536-.062-.781h-3.461c.398.293.573.654.573%201.123%200%201.035-1.074%201.787-2.646%201.787-1.563%200-2.773-.762-2.91-1.924H6ZM6.432%2010h3.763c-.632-.314-.914-.715-.914-1.273%200-1.045.977-1.739%202.432-1.739%201.475%200%202.52.723%202.617%201.914h2.764c-.05-2.548-2.11-4.238-5.39-4.238-3.145%200-5.392%201.719-5.392%204.316%200%20.363.04.703.12%201.02ZM4%2011a1%201%200%201%200%200%202h15a1%201%200%201%200%200-2H4Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-quote::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M4.581%208.471c.44-.5%201.056-.834%201.758-.995C8.074%207.17%209.201%207.822%2010%208.752c1.354%201.578%201.33%203.555.394%205.277-.941%201.731-2.788%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.121-.49.16-.764.294-.286.567-.566.791-.835.222-.266.413-.54.524-.815.113-.28.156-.597.026-.908-.128-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.674-2.7c0-.905.283-1.59.72-2.088Zm9.419%200c.44-.5%201.055-.834%201.758-.995%201.734-.306%202.862.346%203.66%201.276%201.355%201.578%201.33%203.555.395%205.277-.941%201.731-2.789%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.122-.49.16-.764.294-.286.567-.566.791-.835.222-.266.412-.54.523-.815.114-.28.157-.597.026-.908-.127-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.672-2.701c0-.905.283-1.59.72-2.088Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-heading-1::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21.5%207.5v-3h-12v3H14v13h3v-13h4.5ZM9%2013.5h3.5v-3h-10v3H6v7h3v-7Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-code::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3.293%2011.293a1%201%200%200%200%200%201.414l4%204a1%201%200%201%200%201.414-1.414L5.414%2012l3.293-3.293a1%201%200%200%200-1.414-1.414l-4%204Zm13.414%205.414%204-4a1%201%200%200%200%200-1.414l-4-4a1%201%200%201%200-1.414%201.414L18.586%2012l-3.293%203.293a1%201%200%200%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-bullet-list::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%207.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203ZM8%206a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-2.5-5a1.5%201.5%200%201%201-3%200%201.5%201.5%200%200%201%203%200ZM5%2019.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-number-list::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%204h2v4H4V5H3V4Zm5%202a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-3.5-7H6v1l-1.5%202H6v1H3v-1l1.667-2H3v-1h2.5ZM3%2017v-1h3v4H3v-1h2v-.5H4v-1h1V17H3Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-undo::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%2014a1%201%200%200%200%201%201h6a1%201%200%201%200%200-2H6.257c2.247-2.764%205.151-3.668%207.579-3.264%202.589.432%204.739%202.356%205.174%205.405a1%201%200%200%200%201.98-.283c-.564-3.95-3.415-6.526-6.825-7.095C11.084%207.25%207.63%208.377%205%2011.39V8a1%201%200%200%200-2%200v6Zm2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-redo::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21%2014a1%201%200%200%201-1%201h-6a1%201%200%201%201%200-2h3.743c-2.247-2.764-5.151-3.668-7.579-3.264-2.589.432-4.739%202.356-5.174%205.405a1%201%200%200%201-1.98-.283c.564-3.95%203.415-6.526%206.826-7.095%203.08-.513%206.534.614%209.164%203.626V8a1%201%200%201%201%202%200v6Zm-2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-decrease-nesting-level::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-3.707-5.707a1%201%200%200%200%200%201.414l2%202a1%201%200%201%200%201.414-1.414L4.414%2012l1.293-1.293a1%201%200%200%200-1.414-1.414l-2%202Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-increase-nesting-level::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-2.293-2.293%202-2a1%201%200%200%200%200-1.414l-2-2a1%201%200%201%200-1.414%201.414L3.586%2012l-1.293%201.293a1%201%200%201%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-dialogs {
position: relative; }
trix-toolbar .trix-dialog {
position: absolute;
top: 0;
left: 0;
right: 0;
font-size: 0.75em;
padding: 15px 10px;
background: #fff;
box-shadow: 0 0.3em 1em #ccc;
border-top: 2px solid #888;
border-radius: 5px;
z-index: 5; }
trix-toolbar .trix-input--dialog {
font-size: inherit;
font-weight: normal;
padding: 0.5em 0.8em;
margin: 0 10px 0 0;
border-radius: 3px;
border: 1px solid #bbb;
background-color: #fff;
box-shadow: none;
outline: none;
-webkit-appearance: none;
-moz-appearance: none; }
trix-toolbar .trix-input--dialog.validate:invalid {
box-shadow: #F00 0px 0px 1.5px 1px; }
trix-toolbar .trix-button--dialog {
font-size: inherit;
padding: 0.5em;
border-bottom: none; }
trix-toolbar .trix-dialog--link {
max-width: 600px; }
trix-toolbar .trix-dialog__link-fields {
display: flex;
align-items: baseline; }
trix-toolbar .trix-dialog__link-fields .trix-input {
flex: 1; }
trix-toolbar .trix-dialog__link-fields .trix-button-group {
flex: 0 0 content;
margin: 0; }
trix-editor [data-trix-mutable]:not(.attachment__caption-editor) {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; }
trix-editor [data-trix-mutable]::-moz-selection,
trix-editor [data-trix-cursor-target]::-moz-selection, trix-editor [data-trix-mutable] ::-moz-selection {
background: none; }
trix-editor [data-trix-mutable]::selection,
trix-editor [data-trix-cursor-target]::selection, trix-editor [data-trix-mutable] ::selection {
background: none; }
trix-editor .attachment__caption-editor:focus[data-trix-mutable]::-moz-selection {
background: highlight; }
trix-editor .attachment__caption-editor:focus[data-trix-mutable]::selection {
background: highlight; }
trix-editor [data-trix-mutable].attachment.attachment--file {
box-shadow: 0 0 0 2px highlight;
border-color: transparent; }
trix-editor [data-trix-mutable].attachment img {
box-shadow: 0 0 0 2px highlight; }
trix-editor .attachment {
position: relative; }
trix-editor .attachment:hover {
cursor: default; }
trix-editor .attachment--preview .attachment__caption:hover {
cursor: text; }
trix-editor .attachment__progress {
position: absolute;
z-index: 1;
height: 20px;
top: calc(50% - 10px);
left: 5%;
width: 90%;
opacity: 0.9;
transition: opacity 200ms ease-in; }
trix-editor .attachment__progress[value="100"] {
opacity: 0; }
trix-editor .attachment__caption-editor {
display: inline-block;
width: 100%;
margin: 0;
padding: 0;
font-size: inherit;
font-family: inherit;
line-height: inherit;
color: inherit;
text-align: center;
vertical-align: top;
border: none;
outline: none;
-webkit-appearance: none;
-moz-appearance: none; }
trix-editor .attachment__toolbar {
position: absolute;
z-index: 1;
top: -0.9em;
left: 0;
width: 100%;
text-align: center; }
trix-editor .trix-button-group {
display: inline-flex; }
trix-editor .trix-button {
position: relative;
float: left;
color: #666;
white-space: nowrap;
font-size: 80%;
padding: 0 0.8em;
margin: 0;
outline: none;
border: none;
border-radius: 0;
background: transparent; }
trix-editor .trix-button:not(:first-child) {
border-left: 1px solid #ccc; }
trix-editor .trix-button.trix-active {
background: #cbeefa; }
trix-editor .trix-button:not(:disabled) {
cursor: pointer; }
trix-editor .trix-button--remove {
text-indent: -9999px;
display: inline-block;
padding: 0;
outline: none;
width: 1.8em;
height: 1.8em;
line-height: 1.8em;
border-radius: 50%;
background-color: #fff;
border: 2px solid highlight;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.25); }
trix-editor .trix-button--remove::before {
display: inline-block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0.7;
content: "";
background-image: url("data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M19%206.41%2017.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%22%2F%3E%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E");
background-position: center;
background-repeat: no-repeat;
background-size: 90%; }
trix-editor .trix-button--remove:hover {
border-color: #333; }
trix-editor .trix-button--remove:hover::before {
opacity: 1; }
trix-editor .attachment__metadata-container {
position: relative; }
trix-editor .attachment__metadata {
position: absolute;
left: 50%;
top: 2em;
transform: translate(-50%, 0);
max-width: 90%;
padding: 0.1em 0.6em;
font-size: 0.8em;
color: #fff;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 3px; }
trix-editor .attachment__metadata .attachment__name {
display: inline-block;
max-width: 100%;
vertical-align: bottom;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
trix-editor .attachment__metadata .attachment__size {
margin-left: 0.2em;
white-space: nowrap; }
.trix-content {
line-height: 1.5;
overflow-wrap: break-word;
word-break: break-word; }
.trix-content * {
box-sizing: border-box;
margin: 0;
padding: 0; }
.trix-content h1 {
font-size: 1.2em;
line-height: 1.2; }
.trix-content blockquote {
border: 0 solid #ccc;
border-left-width: 0.3em;
margin-left: 0.3em;
padding-left: 0.6em; }
.trix-content [dir=rtl] blockquote,
.trix-content blockquote[dir=rtl] {
border-width: 0;
border-right-width: 0.3em;
margin-right: 0.3em;
padding-right: 0.6em; }
.trix-content li {
margin-left: 1em; }
.trix-content [dir=rtl] li {
margin-right: 1em; }
.trix-content pre {
display: inline-block;
width: 100%;
vertical-align: top;
font-family: monospace;
font-size: 0.9em;
padding: 0.5em;
white-space: pre;
background-color: #eee;
overflow-x: auto; }
.trix-content img {
max-width: 100%;
height: auto; }
.trix-content .attachment {
display: inline-block;
position: relative;
max-width: 100%; }
.trix-content .attachment a {
color: inherit;
text-decoration: none; }
.trix-content .attachment a:hover, .trix-content .attachment a:visited:hover {
color: inherit; }
.trix-content .attachment__caption {
text-align: center; }
.trix-content .attachment__caption .attachment__name + .attachment__size::before {
content: ' \2022 '; }
.trix-content .attachment--preview {
width: 100%;
text-align: center; }
.trix-content .attachment--preview .attachment__caption {
color: #666;
font-size: 0.9em;
line-height: 1.2; }
.trix-content .attachment--file {
color: #333;
line-height: 1;
margin: 0 2px 2px 2px;
padding: 0.4em 1em;
border: 1px solid #bbb;
border-radius: 5px; }
.trix-content .attachment-gallery {
display: flex;
flex-wrap: wrap;
position: relative; }
.trix-content .attachment-gallery .attachment {
flex: 1 0 33%;
padding: 0 0.5em;
max-width: 33%; }
.trix-content .attachment-gallery.attachment-gallery--2 .attachment, .trix-content .attachment-gallery.attachment-gallery--4 .attachment {
flex-basis: 50%;
max-width: 50%; }
/*
* We need to override trix.csss image gallery styles to accommodate the
* <action-text-attachment> element we wrap around attachments. Otherwise,
* images in galleries will be squished by the max-width: 33%; rule.
*/
.trix-content .attachment-gallery > action-text-attachment,
.trix-content .attachment-gallery > .attachment {
flex: 1 0 33%;
padding: 0 0.5em;
max-width: 33%;
}
.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,
.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,
.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {
flex-basis: 50%;
max-width: 50%;
}
.trix-content action-text-attachment .attachment {
padding: 0 !important;
max-width: 100% !important;
}

View file

@ -0,0 +1,349 @@
body {
background-image: url("https://file.garden/aJzQmzrHVB4BLKwu/tumblrbg.gif");
font-family: ms gothic;
font-weight: 900;
}
.mainaboutbox {
background-image: url("https://file.garden/aJzQmzrHVB4BLKwu/sky.png");
width: 990px;
height: 550px;
margin: 0 auto;
padding: 2%;
overflow-y: scroll;
border: 8px double white;
box-shadow: 10px 10px 4px #ffeab0;
}
.shelf {
background:url("https://file.garden/aJzQmzrHVB4BLKwu/MYWOOD.png");
line-height: 150px;
text-decoration: underline white 5px;
padding: 0px 20px;
outline: #5d1c27 solid 15px;
border: #863133 solid 5px;
position: relative;
vertical-align: text-bottom;
margin: 30px 0;
image-rendering: pixelated;
}
.postz img {
width: 50px;
}
.mainaboutbox2 {
background-image: url("https://file.garden/aJzQmzrHVB4BLKwu/sky.png");
width: 450px;
overflow: scroll;
height: 550px;
text-align: center;
margin: 0 auto;
font-size:20px;
padding: 2%;
border: 8px double white;
box-shadow: 10px 10px 4px #ffeab0;
}
.header {
padding-left: 6px;
margin-bottom:10px;
font-size: 10px;
}
a {
color: #000;
}
a:visited {
color: #000;
}
#statuscafe {
padding: .5em;
background-image: url('images/xraytech.jpg');
border: 1px solid midnightblue;
}
#statuscafe a {
color:black;
}
#statuscafe-username {
margin-bottom: .5em; color:black;
}
#statuscafe-content {
margin: 0 1em 0.5em 1em;
color:black;
}
.navigate {
background-image: url("https://file.garden/aJzQmzrHVB4BLKwu/sky.png");
border: 2px dashed yellow;
width: 450px;
}
.favoritestuff {
border: 5px double white;
width: 250px;
height: 180px;
position: absolute;
background-image: url("https://file.garden/aJzQmzrHVB4BLKwu/tumblrbg.gif");
overflow-y: scroll;
}
.etc {
border: 5px double white;
width: 250px;
height: 180px;
left:300px;
position: absolute;
background-image: url("https://file.garden/aJzQmzrHVB4BLKwu/tumblrbg.gif");
overflow-y: scroll;
}
.badges {
border: 5px groove white;
width: 290px;
height: 520px;
background:rgba(255, 255, 255, .5);
margin-left:650px;
overflow: scroll;
top:270px;
padding: 0.5%;
position: absolute;
}
.buttons {
color: #000;
background-image: url("https://file.garden/aJzQmzrHVB4BLKwu/tumblrbg.gif");
border: 4px dashed yellow;
padding: 5px;
}
.listening {
border: 5px groove white;
width: 280px;
height: 250px;
background:rgba(255, 255, 255, .5);
margin-top: 10px;
margin-left: 495px;
overflow: scroll;
padding: 0.5%;
position: absolute;
}
#pictureofme {
width: 250px;
position: absolute;
background-size: 105%;
background-repeat: no-repeat;
height: 250px;
margin-left: 5px;
}
.maininfo {
height: 443px;
width: 600px;
border: 5px double white;
overflow-y: scroll;
padding: 7px;
position: relative;
margin-left: 10px;
background:rgba(255, 255, 255, .5);
}
#mainTable{
font-size: 19px;
font-family: 'Times New Roman', serif;
text-align: center;
vertical-align: middle;
width: 410px;
margin-left: auto;
margin-right: auto;
border-collapse: separate;
border-spacing: 10px 5px;
}
#leftField{
width: 120px;
height: 150px;
border: 1px solid #000;
cursor: pointer;
}
middleField{
width: 120px;
height: 70px;
border: 1px solid #000;
cursor: pointer;
}
#rightField{
width: 120px;
height: 150px;
border: 1px solid #000;
cursor: pointer;
}
.scrapbox {
height: 443px;
width: 600px;
border: 5px double white;
overflow: scroll;
padding: 7px;
margin:0 auto;
background:rgba(255, 255, 255, .5);
}
.favorites {
border: 5px groove white;
width: 200px;
height: 250px;
background:rgba(255, 255, 255, .5);
overflow: scroll;
padding: 0.5%;
position: absolute;
margin-top: 10px;
margin-left: 6px;
}
.fronter {
position: absolute;
right: 0px;
top: 0px;
}
.pkfronters--member-list--pronouns,
.pkfronters--member-card--pronouns
{
word-break: break-word;
}
.pkfronters--member-list--pronouns::before,
.pkfronters--member-card--pronouns::before
{
content: '\0020(';
}
.pkfronters--member-list--pronouns::after,
.pkfronters--member-card--pronouns::after
{
content: ')\0020';
}
.pkfronters--member-card-container
{
display: flex;
flex-flow: row wrap;
gap: 1rem;
}
.pkfronters--member-card
{
display: flex;
flex-flow: column wrap;
align-items: center;
max-width: 8em;
background: #fff2b8;
color: #000;
}
.pkfronters--member-card--avatar
{
min-width: 6rem;
width: 6rem;
min-height: 6rem;
height: 6rem;
margin: 0 0 0.5rem 0;
border-radius: 4px;
border: 4px solid transparent;
}
.pkfronters--member-card--name
{
text-align: center;
}
.marquee {
margin:0 auto;
background: #fff;
color: #000;
border: 3px double white;
align-content: center;
}
h1 {
font-size: 22px;
border: 3px dotted white;
}
/*
* This is a manifest file that'll be compiled into application.css.
*
* With Propshaft, assets are served efficiently without preprocessing steps. You can still include
* application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
* cascading order, meaning styles declared later in the document or manifest will override earlier ones,
* depending on specificity.
*
* Consider organizing styles into separate files for maintainability.
*/

View file

@ -0,0 +1,529 @@
/* -------------------------------------------------------- */
/* VARIABLES */
/* -------------------------------------------------------- */
/* Variables are used like this: var(--text-color) */
:root {
/* Background Colors: */
--background-color: #d1eed9;
--content-background-color: #ffffff;
--sidebar-background-color: #ffffff;
/* Text Colors: */
--text-color: #000000;
--sidebar-text-color: #000000;
--link-color: #008127;
--link-color-hover: #10ec10;
/* Text: */
--font: Monaco, monospace;
--heading-font: Lucida Console, monospace;
--font-size: 15px;
/* Other Settings: */
--margin: 10px;
--padding: 20px;
--border: 9px solid #000000;
--round-borders: 0px;
--sidebar-width: 480px;
}
/* -------------------------------------------------------- */
/* BASICS */
/* -------------------------------------------------------- */
* {
box-sizing: border-box;
}
.mainelouisa {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
font-size: var(--font-size);
margin: 0;
padding: var(--margin);
color: var(--text-color);
font-family: var(--font);
line-height: 1.2;
background: var(--background-color);
background-image: url("https://file.garden/aJzQmzrHVB4BLKwu/eyebg.png");
}
::selection {
/* (Text highlighted by the user) */
background: rgba(0, 0, 0, 0.2);
}
mark {
/* Text highlighted by using the <mark> element */
text-shadow: 1px 1px 4px var(--link-color);
background-color: inherit;
color: var(--text-color);
}
/* Links: */
a {
text-decoration: underline;
}
a,
a:visited {
color: var(--link-color);
}
.maiddnelouisa {
margin: 0 auto;
background-image: url("https://file.garden/aJzQmzrHVB4BLKwu/eyebg.png");
}
a:hover,
a:focus {
color: var(--link-color-hover);
text-decoration: none;
}
/* -------------------------------------------------------- */
/* LAYOUT */
/* -------------------------------------------------------- */
.layout {
width: 1000px;
display: grid;
grid-gap: var(--margin);
grid-template: ".headerspiral .headerspiral" auto "leftSidebar .mainspiral" auto "footer footer" auto / var(--sidebar-width) auto;
/* Confused by the grid? Check out my tutorial: https://petrapixel.neocities.org/coding/positioning-tutorial#grid */
}
.mainspiral {
overflow-y: auto;
position: absolute;
background: var(--content-background-color);
border: var(--border);
border-radius: var(--round-borders);
}
/* -------------------------------------------------------- */
/* .headerspiral */
/* -------------------------------------------------------- */
.headerspiral {
grid-area: header;
font-size: 1.2em;
border: var(--border);
border-radius: var(--round-borders);
background: var(--content-background-color);
}
.headerspiral-content {
padding: var(--padding);
}
.headerspiral-title {
font-family: var(--heading-font);
font-size: 1.5em;
font-weight: bold;
}
.headerspiral-image img {
width: 90%;
height: auto;
text-align: center;
}
/* -------------------------------------------------------- */
/* SIDEBARS */
/* -------------------------------------------------------- */
aside {
grid-area: aside;
border: var(--border);
border-radius: var(--round-borders);
overflow: hidden;
background: var(--sidebar-background-color);
padding: var(--padding);
color: var(--sidebar-text-color);
}
.left-sidebar {
grid-area: leftSidebar;
}
.right-sidebar {
grid-area: rightSidebar;
}
.sidebar-title {
font-weight: bold;
font-size: 1.2em;
font-family: var(--heading-font);
}
.sidebar-section:not(:last-child) {
margin-bottom: 3em;
}
.sidebar-section ul,
.sidebar-section ol {
padding-left: 1.5em;
}
.sidebar-section > *:not(p):not(ul):not(ol):not(blockquote) {
margin-top: 10px;
}
/* Sidebar Blockquote: */
.sidebar-section blockquote {
background: rgba(0, 0, 0, 0.1);
padding: 15px;
margin: 1em 0;
border-radius: 10px;
overflow: hidden;
}
.sidebar-section blockquote > *:first-child {
margin-top: 0;
}
.sidebar-section blockquote > *:last-child {
margin-bottom: 0;
}
/* Site Button: */
.site-button {
display: flex;
flex-direction: column;
align-items: center;
}
.site-button textarea {
font-family: monospace;
font-size: 0.7em;
}
/* -------------------------------------------------------- */
/* FOOTER */
/* -------------------------------------------------------- */
.footerspiral {
grid-area: footer;
border: var(--border);
border-radius: var(--round-borders);
overflow: hidden;
font-size: 0.75em;
padding: 15px;
background: var(--content-background-color);
display: flex;
justify-content: center;
}
.footerspiral a,
.footerspiral a:visited {
color: var(--link-color);
}
.footerspiral a:hover,
.footerspiral a:focus {
color: var(--link-color-hover);
}
/* -------------------------------------------------------- */
/* NAVIGATION */
/* -------------------------------------------------------- */
nav {
margin-bottom: 3em;
}
nav .sidebar-title {
margin-bottom: 0.5em;
}
nav ul {
margin: 0 -5px;
padding: 0;
list-style: none;
user-select: none;
}
nav ul li {
margin-bottom: 0;
}
nav > ul li > a,
nav > ul li > strong {
display: inline-block;
}
nav > ul li > a,
nav > ul li > details summary,
nav > ul li > strong {
padding: 5px 10px;
}
nav > ul li > a.active,
nav > ul li > details.active summary {
font-weight: bold;
}
nav ul summary {
cursor: pointer;
}
nav ul ul li > a {
padding-left: 30px;
}
/* NAVIGATION IN .headerspiral */
.headerspiral nav {
margin-bottom: 0;
}
.headerspiral nav ul {
display: flex;
flex-wrap: wrap;
margin: 0;
}
.headerspiral nav ul li {
position: relative;
}
.headerspiral nav ul li:first-child > a {
padding-left: 0;
}
.headerspiral nav ul li:last-child > a {
padding-right: 0;
}
/* Subnavigation (Drop-Down): */
.headerspiral nav ul ul {
background: var(--content-background-color);
display: none;
position: absolute;
top: 100%;
left: 10px;
padding: 0.5em;
z-index: 1;
border: var(--border);
min-width: 100%;
box-shadow: 0px 1px 5px rgba(0,0,0,0.2);
}
.headerspiral nav ul li:hover ul,
.headerspiral nav ul li:focus-within ul {
display: block;
}
.headerspiral nav ul li strong {
color: var(--link-color);
text-decoration: underline;
font-weight: normal;
}
.headerspiral nav ul ul li a {
display: block;
padding-left: 0;
padding-right: 0;
}
/* -------------------------------------------------------- */
/* CONTENT */
/* -------------------------------------------------------- */
.mainspiral {
line-height: 1.5;
}
.mainspiral a,
.mainspiral a:visited {
color: var(--link-color);
}
.mainspiral a:hover,
.mainspiral a:focus {
color: var(--link-color-hover);
text-decoration-style: wavy;
}
.mainspiral p,
.mainspiral .image,
.mainspiral .full-width-image,
.mainspiral .two-columns {
margin: 0.75em 0;
}
.mainspiral ol,
.mainspiral ul {
margin: 0.5em 0;
padding-left: 1.5em;
}
.mainspiral ol li,
.mainspiral ul li {
margin-bottom: 0.2em;
line-height: 1.3;
}
.mainspiral ol {
padding-left: 2em;
}
.mainspiral blockquote {
background: rgba(0, 0, 0, 0.1);
padding: 15px;
margin: 1em 0;
border-radius: 10px;
}
.mainspiral pre {
margin: 1em 0 1.5em;
}
.mainspiral code {
text-transform: none;
}
.mainspiral center {
margin: 1em 0;
padding: 0 1em;
}
.mainspiral hr {
border: 0;
border-top: var(--border);
margin: 1.5em 0;
}
/* HEADINGS: */
.mainspiral h1,
.mainspiral h2,
.mainspiral h3,
.mainspiral h4,
.mainspiral h5,
.mainspiral h6 {
font-family: var(--heading-font);
margin-bottom: 0;
line-height: 1.5;
}
.mainspiral h1:first-child,
.mainspiral h2:first-child,
.mainspiral h3:first-child,
.mainspiral h4:first-child,
.mainspiral h5:first-child,
.mainspiral h6:first-child {
margin-top: 0;
}
.mainspiral h1 {
font-size: 1.5em;
}
.mainspiral h2 {
font-size: 1.4em;
}
.mainspiral h3 {
font-size: 1.3em;
}
.mainspiral h4 {
font-size: 1.2em;
}
.mainspiral h5 {
font-size: 1.1em;
}
.mainspiral h6 {
font-size: 1em;
}
/* COLUMNS: */
.two-columns {
display: flex;
}
.two-columns > * {
flex: 1 1 0;
margin: 0;
}
.two-columns > *:first-child {
padding-right: 0.75em;
}
.two-columns > *:last-child {
padding-left: 0.75em;
}
/* -------------------------------------------------------- */
/* CONTENT IMAGES */
/* -------------------------------------------------------- */
.image {
display: block;
width: auto;
height: auto;
max-width: 100%;
}
.full-width-image {
display: block;
width: 100%;
height: auto;
}
.images {
display: flex;
width: calc(100% + 5px + 5px);
margin-left: -5px;
margin-right: -5px;
}
.images img {
width: 100%;
height: auto;
padding: 5px;
margin: 0;
overflow: hidden;
}
/* -------------------------------------------------------- */
/* ACCESSIBILITY */
/* -------------------------------------------------------- */
/* please do not remove this. */
#skip-to-content-link {
position: fixed;
top: 0;
left: 0;
display: inline-block;
padding: 0.375rem 0.75rem;
line-height: 1;
font-size: 1.25rem;
background-color: var(--content-background-color);
color: var(--text-color);
transform: translateY(-3rem);
transition: transform 0.1s ease-in;
z-index: 99999999999;
}
#skip-to-content-link:focus,
#skip-to-content-link:focus-within {
transform: translateY(0);
}

View file

@ -0,0 +1,15 @@
.bodyspirit {
line-height: 1;
background-color: pink;
color: white;
font-size: 20px;
background-image: url('https://i.ibb.co/kgSHN1YJ/backstar-1.gif');
text-align: center;
}
.bodyspirit a {
color: lightblue;
}

View file

@ -0,0 +1,16 @@
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
set_current_user || reject_unauthorized_connection
end
private
def set_current_user
if session = Session.find_by(id: cookies.signed[:session_id])
self.current_user = session.user
end
end
end
end

View file

@ -0,0 +1,23 @@
class ApplicationController < ActionController::Base
include Authentication
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern
# Changes to the importmap will invalidate the etag for HTML responses
stale_when_importmap_changes
helper_method :current_user
private
def current_user
@current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
end
def authenticate_user!
unless Current.user
redirect_to new_session_path, alert: "You must be logged in to access this page."
end
end
end

View file

View file

@ -0,0 +1,52 @@
module Authentication
extend ActiveSupport::Concern
included do
before_action :require_authentication
helper_method :authenticated?
end
class_methods do
def allow_unauthenticated_access(**options)
skip_before_action :require_authentication, **options
end
end
private
def authenticated?
resume_session
end
def require_authentication
resume_session || request_authentication
end
def resume_session
Current.session ||= find_session_by_cookie
end
def find_session_by_cookie
Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]
end
def request_authentication
session[:return_to_after_authenticating] = request.url
redirect_to new_session_path
end
def after_authentication_url
session.delete(:return_to_after_authenticating) || root_url
end
def start_new_session_for(user)
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
Current.session = session
cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }
end
end
def terminate_session
Current.session.destroy
cookies.delete(:session_id)
end
end

View file

@ -0,0 +1,10 @@
class FunController < ApplicationController
allow_unauthenticated_access
def index
end
def spirit
end
def show
end
end

View file

@ -0,0 +1,10 @@
class MiniblogController < ApplicationController
allow_unauthenticated_access
def index
@updates = Miniblog.order(created_at: :desc)
end
end

View file

@ -0,0 +1,35 @@
class PasswordsController < ApplicationController
allow_unauthenticated_access
before_action :set_user_by_token, only: %i[ edit update ]
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_password_path, alert: "Try again later." }
def new
end
def create
if user = User.find_by(email_address: params[:email_address])
PasswordsMailer.reset(user).deliver_later
end
redirect_to new_session_path, notice: "Password reset instructions sent (if user with that email address exists)."
end
def edit
end
def update
if @user.update(params.permit(:password, :password_confirmation))
@user.sessions.destroy_all
redirect_to new_session_path, notice: "Password has been reset."
else
redirect_to edit_password_path(params[:token]), alert: "Passwords did not match."
end
end
private
def set_user_by_token
@user = User.find_by_password_reset_token!(params[:token])
rescue ActiveSupport::MessageVerifier::InvalidSignature
redirect_to new_password_path, alert: "Password reset link is invalid or has expired."
end
end

View file

@ -0,0 +1,63 @@
class PostsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy]
allow_unauthenticated_access(only: [:index, :show])
def index
@posts = Post.all.order(created_at: :desc)
@post = Post.find_by(params[:id])
end
def show
@post = Post.find(params[:id])
end
def new
@user = Current.user
@post = Post.new
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to posts_path, notice: "post created."
else
render :new, status: :unprocessable_entity, notice: "U fucked up somewhere."
end
end
def edit
@post = Post.find(params[:id])
end
def update
@post = Post.find(params[:id])
if @post.update(post_params)
redirect_to posts_path, notice: "post edited."
else
render :edit, status: :unprocessable_entity, notice: "U fucked up somewhere."
end
end
def destroy
@post = Post.find(params[:id])
@post.destroy
redirect_to posts_path, notice: "Post deleted."
end
private
def post_params
params.require(:post).permit(:text, :icon_image)
end
def set_user
@user = Current.user
end
end

View file

@ -0,0 +1,53 @@
class ScrapsController < ApplicationController
allow_unauthenticated_access(only: %i[index show])
def index
@scraps = Scrap.all
end
def edit
@scrap = Scrap.find(params[:id])
end
def update
@scrap = Scrap.find(params[:id])
if @scrap.update(scrap_params)
redirect_to posts_path, notice: "scrap edited."
else
render :edit, status: :unprocessable_entity, notice: "U fucked up somewhere."
end
end
def new
@scrap = Scrap.new
end
def create
@scrap = Scrap.new(scrap_params)
if @scrap.save
redirect_to scraps_path, notice: "scrap created."
else
render :new, status: :unprocessable_entity, notice: "U fucked up somewhere."
end
end
def destroy
@scrap = Scrap.find(params[:id])
@scrap.destroy
redirect_to scrap_path(@scrap), notice: "Member deleted."
end
def show
@scrap = Scrap.find_by(id: params[:id])
if @scrap.nil?
flash[:alert] = "Scrapbook entry not found"
redirect_to scraps_path
return
end
end
private
def scrap_params
params.require(:scrap).permit(:text, :image, :music, :mood, :notes)
end
end

View file

@ -0,0 +1,21 @@
class SessionsController < ApplicationController
allow_unauthenticated_access only: %i[ new create ]
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." }
def new
end
def create
if user = User.authenticate_by(params.permit(:email_address, :password))
start_new_session_for user
redirect_to after_authentication_url
else
redirect_to new_session_path, alert: "Try another email address or password."
end
end
def destroy
terminate_session
redirect_to new_session_path, status: :see_other
end
end

View file

@ -0,0 +1,6 @@
class ShrinesController < ApplicationController
allow_unauthenticated_access
def spiraleye
end
end

View file

@ -0,0 +1,57 @@
class UpdatesController < ApplicationController
before_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy]
allow_unauthenticated_access(only: [:index, :show])
def index
@updates = Update.order(created_at: :desc)
end
def show
end
def new
@user = Current.user
@update = Update.new
end
def create
@update = Update.new(update_params)
if Updates.save
redirect_to updates_path, notice: "Update created."
else
render :new, status: :unprocessable_entity, notice: "U fucked up somewhere."
end
end
def edit
end
def update
if @update.update(post_params)
redirect_to updates_path, notice: "Update edited."
else
render :edit, status: :unprocessable_entity, notice: "U fucked up somewhere."
end
end
def destroy
@update.destroy
redirect_to updates_path, notice: "Post deleted."
end
private
def update_params
params.require(:update).permit(:text, :icon_image)
end
def set_user
@user = Current.user
end
end

View file

@ -0,0 +1,10 @@
class WelcomeController < ApplicationController
allow_unauthenticated_access(only: %i[index show homepage])
def index
end
def show
end
def homepage
end
end

View file

@ -0,0 +1,2 @@
module ApplicationHelper
end

View file

@ -0,0 +1,2 @@
module FunHelper
end

View file

@ -0,0 +1,2 @@
module MiniblogHelper
end

View file

@ -0,0 +1,2 @@
module PostsHelper
end

View file

@ -0,0 +1,2 @@
module ScrapsHelper
end

View file

@ -0,0 +1,2 @@
module ShrinesHelper
end

View file

@ -0,0 +1,2 @@
module UpdatesHelper
end

View file

@ -0,0 +1,2 @@
module WelcomeHelper
end

View file

@ -0,0 +1,6 @@
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
import "trix"
import "@rails/actiontext"

View file

@ -0,0 +1,9 @@
import { Application } from "@hotwired/stimulus"
const application = Application.start()
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
export { application }

View file

@ -0,0 +1,7 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
this.element.textContent = "Hello World!"
}
}

View file

@ -0,0 +1,4 @@
// Import and register all your controllers from the importmap via controllers/**/*_controller
import { application } from "controllers/application"
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)

544
app/javascript/sorter.js Normal file
View file

@ -0,0 +1,544 @@
<script>
var namMember = new Array(
"John Sheridan",
"Delenn",
"Londo Mollari",
"Jeffrey Sinclair",
"Michael Garibaldi",
"G'Kar",
"Lyta Alexander",
"Talia Winters",
"Susan Ivanova",
"Na'toth",
"Lennier",
"Vir Cotto",
"Mr. Morden",
"Antono Refa",
"Cartagia",
"Virini",
"Alfred Bester",
"David Corwin",
"Marcus Cole",
"Stephen Franklin",
"Byron Gordon",
"Neroon",
"Elizabeth Lochley",
"Zack Allan",
"Lorien",
"Warren Keffer",
"Ambassador Kosh",
"Ulkesh/Kosh II",
"Zathras",
"Ta'Lon",
"Catherine Sakai"
);
//*********************************************************
var lstMember = new Array();
var parent = new Array();
var equal = new Array();
var rec = new Array();
var cmp1,cmp2;
var head1,head2;
var nrec;
var numQuestion;
var totalSize;
var finishSize;
var finishFlag;
//The initialization of the variable+++++++++++++++++++++++++++++++++++++++++++++
function initList(){
var n = 0;
var mid;
var i;
//The sequence that you should sort
lstMember[n] = new Array();
for (i=0; i<namMember.length; i++) {
lstMember[n][i] = i;
}
parent[n] = -1;
totalSize = 0;
n++;
for (i=0; i<lstMember.length; i++) {
//And element divides it in two/more than two
//Increase divided sequence of last in first member
if(lstMember[i].length>=2) {
mid = Math.ceil(lstMember[i].length/2);
lstMember[n] = new Array();
lstMember[n] = lstMember[i].slice(0,mid);
totalSize += lstMember[n].length;
parent[n] = i;
n++;
lstMember[n] = new Array();
lstMember[n] = lstMember[i].slice(mid,lstMember[i].length);
totalSize += lstMember[n].length;
parent[n] = i;
n++;
}
}
//Preserve this sequence
for (i=0; i<namMember.length; i++) {
rec[i] = 0;
}
nrec = 0;
//List that keeps your results
//Value of link initial
// Value of link initial
for (i=0; i<=namMember.length; i++) {
equal[i] = -1;
}
cmp1 = lstMember.length-2;
cmp2 = lstMember.length-1;
head1 = 0;
head2 = 0;
numQuestion = 1;
finishSize = 0;
finishFlag = 0;
}
//&#12522;&#12473;&#12488;&#12398;&#12477;&#12540;&#12488;+++++++++++++++++++++++++++++++++++++++++++
//flag&#65306;Don't know characters
// -1&#65306;Chose the left
// 0&#65306;Tie
// 1&#65306;Chose the right
function sortList(flag){
var i;
var str;
//rec preservation
if (flag<0) {
rec[nrec] = lstMember[cmp1][head1];
head1++;
nrec++;
finishSize++;
while (equal[rec[nrec-1]]!=-1) {
rec[nrec] = lstMember[cmp1][head1];
head1++;
nrec++;
finishSize++;
}
}
else if (flag>0) {
rec[nrec] = lstMember[cmp2][head2];
head2++;
nrec++;
finishSize++;
while (equal[rec[nrec-1]]!=-1) {
rec[nrec] = lstMember[cmp2][head2];
head2++;
nrec++;
finishSize++;
}
}
else {
rec[nrec] = lstMember[cmp1][head1];
head1++;
nrec++;
finishSize++;
while (equal[rec[nrec-1]]!=-1) {
rec[nrec] = lstMember[cmp1][head1];
head1++;
nrec++;
finishSize++;
}
equal[rec[nrec-1]] = lstMember[cmp2][head2];
rec[nrec] = lstMember[cmp2][head2];
head2++;
nrec++;
finishSize++;
while (equal[rec[nrec-1]]!=-1) {
rec[nrec] = lstMember[cmp2][head2];
head2++;
nrec++;
finishSize++;
}
}
//Processing after finishing with one list
if (head1<lstMember[cmp1].length && head2==lstMember[cmp2].length) {
//List the remainder of cmp2 copies, list cmp1 copies when finished scanning
while (head1<lstMember[cmp1].length){
rec[nrec] = lstMember[cmp1][head1];
head1++;
nrec++;
finishSize++;
}
}
else if (head1==lstMember[cmp1].length && head2<lstMember[cmp2].length) {
//List the remainder of cmp1 copies, list cmp2 copies when finished scanning
while (head2<lstMember[cmp2].length){
rec[nrec] = lstMember[cmp2][head2];
head2++;
nrec++;
finishSize++;
}
}
//When it arrives at the end of both lists
//Update a pro list
if (head1==lstMember[cmp1].length && head2==lstMember[cmp2].length) {
for (i=0; i<lstMember[cmp1].length+lstMember[cmp2].length; i++) {
lstMember[parent[cmp1]][i] = rec[i];
}
lstMember.pop();
lstMember.pop();
cmp1 = cmp1-2;
cmp2 = cmp2-2;
head1 = 0;
head2 = 0;
//Initialize the rec before performing the new comparison
if (head1==0 && head2==0) {
for (i=0; i<namMember.length; i++) {
rec[i] = 0;
}
nrec = 0;
}
}
if (cmp1<0) {
str = "battle #"+(numQuestion-1)+"<br>"+Math.floor(finishSize*100/totalSize)+"% sorted.";
document.getElementById("battleNumber").innerHTML = str;
showResult();
finishFlag = 1;
}
else {
showImage();
}
}
//The results+++++++++++++++++++++++++++++++++++++++++++++++
//&#38918;&#20301;=Rank/Grade/Position/Standing/Status
//&#21517;&#21069;=Identification term
function showResult() {
var ranking = 1;
var sameRank = 1;
var str = "";
var i;
str += "<table style=\"width:200px; font-size:18px; line-height:120%; margin-left:auto; margin-right:auto; border:1px solid #000; border-collapse:collapse\" align=\"center\">";
str += "<tr><td style=\"color:#ffffff; background-color:#e097d9; text-align:center;\">rank<\/td><td style=\"color:#ffffff; background-color:#e097d9; text-align:center;\">options<\/td><\/tr>";
for (i=0; i<namMember.length; i++) {
str += "<tr><td style=\"border:1px solid #000; text-align:center; padding-right:5px;\">"+ranking+"<\/td><td style=\"border:1px solid #000; padding-left:5px;\">"+namMember[lstMember[0][i]]+"<\/td><\/tr>";
if (i<namMember.length-1) {
if (equal[lstMember[0][i]]==lstMember[0][i+1]) {
sameRank++;
} else {
ranking += sameRank;
sameRank = 1;
}
}
}
str += "<\/table>";
document.getElementById("resultField").innerHTML = str;
}
//Indicates two elements to compare+++++++++++++++++++++++++++++++++++
function showImage() {
var str0 = "battle #"+numQuestion+"<br>"+Math.floor(finishSize*100/totalSize)+"% sorted.";
var str1 = ""+toNameFace(lstMember[cmp1][head1]);
var str2 = ""+toNameFace(lstMember[cmp2][head2]);
document.getElementById("battleNumber").innerHTML = str0;
document.getElementById("leftField").innerHTML = str1;
document.getElementById("rightField").innerHTML = str2;
numQuestion++;
}
//Convert numeric value into a name (emoticon)+++++++++++++++++++++++++++++++
function toNameFace(n){
var str = namMember[n];
/*
str += '<br />';
switch(n) {
//case -1 Because it is a sample, delete it
case -1: str+=""; break;
}*/
return str;
}
</script>

View file

@ -0,0 +1,7 @@
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
# discard_on ActiveJob::DeserializationError
end

View file

@ -0,0 +1,4 @@
class ApplicationMailer < ActionMailer::Base
default from: "from@example.com"
layout "mailer"
end

View file

@ -0,0 +1,6 @@
class PasswordsMailer < ApplicationMailer
def reset(user)
@user = user
mail subject: "Reset your password", to: user.email_address
end
end

View file

@ -0,0 +1,3 @@
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
end

View file

4
app/models/current.rb Normal file
View file

@ -0,0 +1,4 @@
class Current < ActiveSupport::CurrentAttributes
attribute :session
delegate :user, to: :session, allow_nil: true
end

2
app/models/miniblog.rb Normal file
View file

@ -0,0 +1,2 @@
class Miniblog < ApplicationRecord
end

6
app/models/post.rb Normal file
View file

@ -0,0 +1,6 @@
class Post < ApplicationRecord
has_one_attached :icon_image
has_rich_text :text
end

5
app/models/scrap.rb Normal file
View file

@ -0,0 +1,5 @@
class Scrap < ApplicationRecord
has_one_attached :image
has_rich_text :text
has_rich_text :notes
end

3
app/models/session.rb Normal file
View file

@ -0,0 +1,3 @@
class Session < ApplicationRecord
belongs_to :user
end

9
app/models/update.rb Normal file
View file

@ -0,0 +1,9 @@
class Update < ApplicationRecord
belongs_to :member
has_one_attached :icon_image
has_rich_text :text
validates :text, presence:true
end

6
app/models/user.rb Normal file
View file

@ -0,0 +1,6 @@
class User < ApplicationRecord
has_secure_password
has_many :sessions, dependent: :destroy
has_many :updates
normalizes :email_address, with: ->(e) { e.strip.downcase }
end

View file

@ -0,0 +1,14 @@
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
<% if blob.representable? %>
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
<% end %>
<figcaption class="attachment__caption">
<% if caption = blob.try(:caption) %>
<%= caption %>
<% else %>
<span class="attachment__name"><%= blob.filename %></span>
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
<% end %>
</figcaption>
</figure>

View file

@ -0,0 +1,602 @@
<%= javascript_tag do %>
var namMember = new Array(
"John Sheridan",
"Delenn",
"Londo Mollari",
"Jeffrey Sinclair",
"Michael Garibaldi",
"G'Kar",
"Lyta Alexander",
"Talia Winters",
"Susan Ivanova",
"Na'toth",
"Lennier",
"Vir Cotto",
"Mr. Morden",
"Antono Refa",
"Cartagia",
"Virini",
"Alfred Bester",
"David Corwin",
"Marcus Cole",
"Stephen Franklin",
"Byron Gordon",
"Neroon",
"Elizabeth Lochley",
"Zack Allan",
"Lorien",
"Warren Keffer",
"Ambassador Kosh",
"Ulkesh/Kosh II",
"Zathras",
"Ta'Lon",
"Catherine Sakai"
);
//*********************************************************
var lstMember = new Array();
var parent = new Array();
var equal = new Array();
var rec = new Array();
var cmp1,cmp2;
var head1,head2;
var nrec;
var numQuestion;
var totalSize;
var finishSize;
var finishFlag;
//The initialization of the variable+++++++++++++++++++++++++++++++++++++++++++++
function initList(){
var n = 0;
var mid;
var i;
//The sequence that you should sort
lstMember[n] = new Array();
for (i=0; i<namMember.length; i++) {
lstMember[n][i] = i;
}
parent[n] = -1;
totalSize = 0;
n++;
for (i=0; i<lstMember.length; i++) {
//And element divides it in two/more than two
//Increase divided sequence of last in first member
if(lstMember[i].length>=2) {
mid = Math.ceil(lstMember[i].length/2);
lstMember[n] = new Array();
lstMember[n] = lstMember[i].slice(0,mid);
totalSize += lstMember[n].length;
parent[n] = i;
n++;
lstMember[n] = new Array();
lstMember[n] = lstMember[i].slice(mid,lstMember[i].length);
totalSize += lstMember[n].length;
parent[n] = i;
n++;
}
}
//Preserve this sequence
for (i=0; i<namMember.length; i++) {
rec[i] = 0;
}
nrec = 0;
//List that keeps your results
//Value of link initial
// Value of link initial
for (i=0; i<=namMember.length; i++) {
equal[i] = -1;
}
cmp1 = lstMember.length-2;
cmp2 = lstMember.length-1;
head1 = 0;
head2 = 0;
numQuestion = 1;
finishSize = 0;
finishFlag = 0;
}
//&#12522;&#12473;&#12488;&#12398;&#12477;&#12540;&#12488;+++++++++++++++++++++++++++++++++++++++++++
//flag&#65306;Don't know characters
// -1&#65306;Chose the left
// 0&#65306;Tie
// 1&#65306;Chose the right
function sortList(flag){
var i;
var str;
//rec preservation
if (flag<0) {
rec[nrec] = lstMember[cmp1][head1];
head1++;
nrec++;
finishSize++;
while (equal[rec[nrec-1]]!=-1) {
rec[nrec] = lstMember[cmp1][head1];
head1++;
nrec++;
finishSize++;
}
}
else if (flag>0) {
rec[nrec] = lstMember[cmp2][head2];
head2++;
nrec++;
finishSize++;
while (equal[rec[nrec-1]]!=-1) {
rec[nrec] = lstMember[cmp2][head2];
head2++;
nrec++;
finishSize++;
}
}
else {
rec[nrec] = lstMember[cmp1][head1];
head1++;
nrec++;
finishSize++;
while (equal[rec[nrec-1]]!=-1) {
rec[nrec] = lstMember[cmp1][head1];
head1++;
nrec++;
finishSize++;
}
equal[rec[nrec-1]] = lstMember[cmp2][head2];
rec[nrec] = lstMember[cmp2][head2];
head2++;
nrec++;
finishSize++;
while (equal[rec[nrec-1]]!=-1) {
rec[nrec] = lstMember[cmp2][head2];
head2++;
nrec++;
finishSize++;
}
}
//Processing after finishing with one list
if (head1<lstMember[cmp1].length && head2==lstMember[cmp2].length) {
//List the remainder of cmp2 copies, list cmp1 copies when finished scanning
while (head1<lstMember[cmp1].length){
rec[nrec] = lstMember[cmp1][head1];
head1++;
nrec++;
finishSize++;
}
}
else if (head1==lstMember[cmp1].length && head2<lstMember[cmp2].length) {
//List the remainder of cmp1 copies, list cmp2 copies when finished scanning
while (head2<lstMember[cmp2].length){
rec[nrec] = lstMember[cmp2][head2];
head2++;
nrec++;
finishSize++;
}
}
//When it arrives at the end of both lists
//Update a pro list
if (head1==lstMember[cmp1].length && head2==lstMember[cmp2].length) {
for (i=0; i<lstMember[cmp1].length+lstMember[cmp2].length; i++) {
lstMember[parent[cmp1]][i] = rec[i];
}
lstMember.pop();
lstMember.pop();
cmp1 = cmp1-2;
cmp2 = cmp2-2;
head1 = 0;
head2 = 0;
//Initialize the rec before performing the new comparison
if (head1==0 && head2==0) {
for (i=0; i<namMember.length; i++) {
rec[i] = 0;
}
nrec = 0;
}
}
if (cmp1<0) {
str = "battle #"+(numQuestion-1)+"<br>"+Math.floor(finishSize*100/totalSize)+"% sorted.";
document.getElementById("battleNumber").innerHTML = str;
showResult();
finishFlag = 1;
}
else {
showImage();
}
}
//The results+++++++++++++++++++++++++++++++++++++++++++++++
//&#38918;&#20301;=Rank/Grade/Position/Standing/Status
//&#21517;&#21069;=Identification term
function showResult() {
var ranking = 1;
var sameRank = 1;
var str = "";
var i;
str += "<table style=\"width:200px; font-size:18px; line-height:120%; margin-left:auto; margin-right:auto; border:1px solid #000; border-collapse:collapse\" align=\"center\">";
str += "<tr><td style=\"color:#ffffff; background-color:#e097d9; text-align:center;\">rank<\/td><td style=\"color:#ffffff; background-color:#e097d9; text-align:center;\">options<\/td><\/tr>";
for (i=0; i<namMember.length; i++) {
str += "<tr><td style=\"border:1px solid #000; text-align:center; padding-right:5px;\">"+ranking+"<\/td><td style=\"border:1px solid #000; padding-left:5px;\">"+namMember[lstMember[0][i]]+"<\/td><\/tr>";
if (i<namMember.length-1) {
if (equal[lstMember[0][i]]==lstMember[0][i+1]) {
sameRank++;
} else {
ranking += sameRank;
sameRank = 1;
}
}
}
str += "<\/table>";
document.getElementById("resultField").innerHTML = str;
}
//Indicates two elements to compare+++++++++++++++++++++++++++++++++++
function showImage() {
var str0 = "battle #"+numQuestion+"<br>"+Math.floor(finishSize*100/totalSize)+"% sorted.";
var str1 = ""+toNameFace(lstMember[cmp1][head1]);
var str2 = ""+toNameFace(lstMember[cmp2][head2]);
document.getElementById("battleNumber").innerHTML = str0;
document.getElementById("leftField").innerHTML = str1;
document.getElementById("rightField").innerHTML = str2;
numQuestion++;
}
document.addEventListener("turbo:load", function () {
initList();
showImage();
});
//Convert numeric value into a name (emoticon)+++++++++++++++++++++++++++++++
function toNameFace(n){
var str = namMember[n];
/*
str += '<br />';
switch(n) {
//case -1 Because it is a sample, delete it
case -1: str+=""; break;
}*/
return str;
}
<% end %>
<p class="instructions">
<center><br /><br />
<b>BABYLON 5 FAVORITE CHARACTER SORTER</b><br /><br>pick who you like better in each battle to get an accurate list of your<br />favorite characters from the show. does not include crusade.<br />note: hitting 'no opinion' or 'I like both' frequently will negatively affect your results.<br /><br /></center>
</p>
<table id="mainTable" align="center">
<tbody><tr>
<td id="battleNumber" colspan="3" style="padding-bottom: 10px;" style="text-align:center;"><b>battle #1<br>0% sorted.</b></td>
</tr>
<tr>
<td id="leftField" onclick="if(finishFlag==0) sortList(-1);" rowspan="2" style="text-align:center;"></td>
<td class="middleField" onclick="if(finishFlag==0) sortList(0);" style="text-align:center;">
I like both
</td>
<td id="rightField" onclick="if(finishFlag==0) sortList(1);" rowspan="2"style="text-align:center;"></td>
</tr>
<tr>
<td class="middleField" onclick="if(finishFlag==0) sortList(0);"style="text-align:center;">
no opinion
</td>
</tr>
</tbody></table>
<br><br>
<div id="resultField" style="text-align: center;">
<br>
</div>
<p class="other">
<center><small><br /><br />used with permission.<br /><br/><a href="http://biasorter.tumblr.com/">created by biasorter</a>.
</small></center></small>

View file

@ -0,0 +1,38 @@
<div class="mainaboutbox">
<h1>little pixel library!!</h1>
<a href="https://hillhouse.neocities.org/cliques/library/#library">a book-related pixel clique :')</a>
<br><br>
my pixel covers underlined in pink...<br>
<div class="shelf">
<img src="https://file.garden/Zw17vw8ctXTQw7PV/Untitled1769_20260513035715.png" alt="The Orange Eats Creeps by Grace Krilanovich" style="border-bottom:4px hotpink solid;"><img src="https://file.garden/Zw17vw8ctXTQw7PV/Untitled1770_20260513164853.png" alt="Serious Weakness by Porpentine Charity Heartscape" style="border-bottom:4px hotpink solid;"><a href='https://cherrycomet.neocities.org/'><img src='https://file.garden/Zw17vw8ctXTQw7PV/war.png' alt='War and Peace by Leo Tolstoy' style="border-bottom:4px #c78a6d solid;"></a><a href='https://juneish.neocities.org'><img src='https://file.garden/Zw17vw8ctXTQw7PV/hol.png' alt='House of Leaves by Mark Z. Danielewski' style="border-bottom:4px #c78a6d solid;"></a>
</div>
<hr>
<h1>Things I Made</h1>
<center>AO3 Clone in HTML</center>
A while ago I made a semi-functional AO3 clone in HTML/css. You use this by copying and pasting your works into the work template, then updating the profile and tag pages to link to each work.
<br><br>
It's insane, but if you're curious or up for a tedious project, or are dreaming of hosting OTW-Archive one day and want to spin up a prototype, you can download it <a href="https://github.com/owoctober/AO3-html">here</a>.<br><br>
<hr>
<center>Babylon 5 favorite character sorter</center>
I didn't code this, biasorter on Tumblr did, I just made it B5. Do you want to know your ultimate favs in B5? Check it out <a href="/fun/b5sorter">here!</a>
<hr>
<center>Graphics</center>
<img src="https://i.ibb.co/V0FPZT9b/janestamp.png" alt="janestamp" border="0">
<img src="https://i.ibb.co/kgwxz7sd/flexstamp.png" alt="flexstamp" border="0">
<img src="https://i.ibb.co/dsrLDFLx/cliffstamp.png" alt="cliffstamp" border="0">
<img src="https://i.ibb.co/zhsm4VhV/larrystamp.png" alt="larrystamp" border="0">
<img src="https://i.ibb.co/355pgmCS/ritastamp.png" alt="ritastamp" border="0">
<img src="https://i.ibb.co/s9PvmBqJ/maurastamp.png" alt="maurastamp" border="0">
<img src="https://i.ibb.co/21f0Fjv6/laurastamp.png" alt="laurastamp" border="0">
<img src="https://i.ibb.co/tpYytR2h/vicstamp.png" alt="vicstamp" border="0">
<img src="https://i.ibb.co/v612b01V/babylon5stamp.png" alt="babylon5stamp" border="0"><br/><img src="https://i.ibb.co/ccjjrxNC/DOOM-PATROL.png" alt="DOOM-PATROL" border="0">
<img src="https://i.ibb.co/DP5xmbSp/ALIEN-GENDER.png" alt="ALIEN-GENDER" border="0">
<img src="https://i.ibb.co/XrZ6y4mc/YAY-LESBIANS.png" alt="YAY-LESBIANS" border="0">
<img src="https://i.ibb.co/FLZJgtKz/BUTCH-LESBIAN.png" alt="BUTCH-LESBIAN" border="0">
<img src="https://i.ibb.co/rLgsL0K/FEMME-LESBIAN-1.png" alt="FEMME-LESBIAN-1" border="0"></div>

View file

@ -0,0 +1,3 @@
<div class="trix-content">
<%= yield -%>
</div>

View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title><%= content_for(:title) || "alien-kissing-computer" %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="Playground">
<meta name="mobile-web-app-capable" content="yes">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= yield :head %>
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
<%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
<link rel="icon" href="/icon.png" type="image/png">
<link rel="apple-touch-icon" href="/icon.png">
<%# Includes all stylesheet files in app/assets/stylesheets %>
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<center> <div class="header">
<% if authenticated? %>
<%= button_to "Sign out", session_path, method: :delete %>
<% else %>
<%= link_to "🛸", new_session_path %>
<% end %>
</div><div class="navigate"><a href="/welcome/homepage">HOME!!!</a> 🛸 <a href="/shrines">SHRINES!!!</a> 🛸
<a href="/fun">FUN STUFF!!!</a> 🛸
<a href="/posts">MINIBLOG!!!</a> 🛸
<a href="/scraps">SCRAPS!!!</a> </center></div>
<%= yield %>
<center><small><small><small><small><a href="https://treasurechest.alien.town/agnes/playground">source</a></small></small></small></small></center>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
/* Email styles need to be inline */
</style>
</head>
<body>
<%= yield %>
</body>
</html>

View file

@ -0,0 +1 @@
<%= yield %>

View file

@ -0,0 +1,12 @@
<div class="mainaboutbox">
<div class="maininfo">
<h1>Recent Updates</h1>
<% if current_user.present? %>
<%= link_to 'New', new_updates_path(@update) %>
<% end %>
</div>
</div>

View file

@ -0,0 +1,9 @@
<h1>Update your password</h1>
<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
<%= form_with url: password_path(params[:token]), method: :put do |form| %>
<%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter new password", maxlength: 72 %><br>
<%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72 %><br>
<%= form.submit "Save" %>
<% end %>

View file

@ -0,0 +1,8 @@
<h1>Forgot your password?</h1>
<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
<%= form_with url: passwords_path do |form| %>
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %><br>
<%= form.submit "Email reset instructions" %>
<% end %>

View file

@ -0,0 +1,6 @@
<p>
You can reset your password on
<%= link_to "this password reset page", edit_password_url(@user.password_reset_token) %>.
This link will expire in <%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %>.
</p>

View file

@ -0,0 +1,4 @@
You can reset your password on
<%= edit_password_url(@user.password_reset_token) %>
This link will expire in <%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %>.

View file

@ -0,0 +1,20 @@
<div class="mainaboutbox">
<div class="maininfo">
<%= form_with model: [@post] do |f| %>
<div>
<%= f.label :text %>
<%= f.rich_text_area :text %>
</div>
<%= f.label :icon_image %>
<%= f.file_field :icon_image %>
</div>
<%= f.submit "Edit Update" %>
<% end %>
</div>
</div>

View file

@ -0,0 +1,34 @@
<div class="mainaboutbox">
<% if authenticated? %>
<%= link_to 'New Update', new_post_path %>
<% else %>
.
<% end %>
<div class="maininfo">
<h1>Recent Updates</h1>
<div class="posts">
<div class="postz">
<% @posts.each do |post| %>
<% if post.icon_image.attached? %>
<%= image_tag post.icon_image %>
<% else %>
🛸
<% end %> <%= time_ago_in_words(@post.created_at) %> ago</p> <% if authenticated? %>
|
<%= link_to 'Edit', edit_post_path(post) %>
<%= link_to 'Destroy', post_path(post), data: {
turbo_method: :delete,
turbo_confirm: "Are you sureee?"
} %>
<% else %>
<% end %>
<%= post.text %>
<hr>
<% end %>
</div>
</div>

View file

@ -0,0 +1,24 @@
<div class="mainaboutbox">
<% if authenticated? %>
<%= link_to 'New Update', new_post_path %>
<% else %>
.
<% end %>
<div class="maininfo">
<%= form_with model: [@post] do |f| %>
<div>
<%= f.label :text %>
<%= f.rich_text_area :text %>
</div>
<%= f.label :icon_image %>
<%= f.file_field :icon_image %>
</div>
<%= f.submit "Create Update" %>
<% end %>
</div>
</div>

View file

View file

@ -0,0 +1,22 @@
{
"name": "Playground",
"icons": [
{
"src": "/icon.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "/icon.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"start_url": "/",
"display": "standalone",
"scope": "/",
"description": "Playground.",
"theme_color": "red",
"background_color": "red"
}

View file

@ -0,0 +1,26 @@
// Add a service worker for processing Web Push notifications:
//
// self.addEventListener("push", async (event) => {
// const { title, options } = await event.data.json()
// event.waitUntil(self.registration.showNotification(title, options))
// })
//
// self.addEventListener("notificationclick", function(event) {
// event.notification.close()
// event.waitUntil(
// clients.matchAll({ type: "window" }).then((clientList) => {
// for (let i = 0; i < clientList.length; i++) {
// let client = clientList[i]
// let clientPath = (new URL(client.url)).pathname
//
// if (clientPath == event.notification.data.path && "focus" in client) {
// return client.focus()
// }
// }
//
// if (clients.openWindow) {
// return clients.openWindow(event.notification.data.path)
// }
// })
// )
// })

View file

View file

@ -0,0 +1,25 @@
<div class="mainaboutbox">
<% if authenticated? %>
<%= link_to 'New Scrap', new_scrap_path %>
<% else %>
.
<% end %><br><br>
<h1>SCRAPBOOK</h1>
random thoughts longer than miniblog length will go here :)<br><br>
<hr>
<% @scraps.each do |scrap| %>
<%= truncate(scrap.text.to_plain_text, length: 350) %> (<%= link_to 'Read', scrap_path(scrap) %>) | <%= time_ago_in_words(scrap.created_at) %> ago
<br>
<% if authenticated? %>
|
<%= link_to 'Edit', edit_scrap_path(scrap) %>
<%= link_to 'Destroy', scrap_path(scrap), data: {
turbo_method: :delete,
turbo_confirm: "Are you sureee?"
} %>
<% else %>
<% end %><br><br><hr>
<% end %>

View file

@ -0,0 +1,33 @@
<% if authenticated? %>
<%= link_to 'New Scrap', new_scrap_path %>
<% else %>
.
<% end %>
<div class="maininfo">
<%= form_with model: [@scrap] do |f| %>
<div>
<%= f.label :text %>
<%= f.rich_text_area :text %>
</div>
<div>
<%= f.label :image %>
<%= f.file_field :image %>
</div>
<div>
<%= f.label :mood %>
<%= f.text_area :mood %>
</div>
<div>
<%= f.label :music %>
<%= f.text_area :music %>
</div>
<div>
<%= f.label :notes %>
<%= f.rich_text_area :notes %>
</div>
<%= f.submit "Create Scrap" %>
<% end %>

View file

@ -0,0 +1,20 @@
<div class="mainaboutbox">
<div class="scrapbox">
<%= link_to '[ b a c k . . ? ]', scraps_path %>
<h2>SCRAPBOOK ENTRY #<%= @scrap.id %></h2>
<%= @scrap.text %>
</p>
<p>
<hr><hr>
<% if @scrap.music.present? %>
<strong>Current Music:</strong>
<%= @scrap.music %>
<% end %>
</p>
<% if @scrap.mood.present? %>
<p>
<strong>Current Mood:</strong>
<%= @scrap.mood %></p>
<% end %>
</div></ddiv>

View file

@ -0,0 +1,11 @@
<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %>
<%= form_with url: session_path do |form| %>
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %><br>
<%= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72 %><br>
<%= form.submit "Sign in" %>
<% end %>
<br>
<%= link_to "Forgot password?", new_password_path %>

View file

@ -0,0 +1,6 @@
<div class="mainaboutbox">
<a href="/shrines/spiraleye">SPIRAL EYE - A shrine to Elouisa from Palia;</a><br>
<a href="https://signsandportents.neocities.org/morden/ambition">AMBITION - A shrine to Morden from Babylon 5;</a> <small><small>currently hosted off-site</small></small><br>
<a href="/shrines/spirit">SPIRIT - A shrine to the fictional Negative Spirit species from Doom Patrol TV;</a><br>
coming soon...<br>
SHADOW OF A GHOST - A shrine to Anna Sheridan from Babylon 5.

View file

@ -0,0 +1,72 @@
<div class="mainelouisa">
<script>
console.log("%cTemplate generated with petrapixel's layout generator: https://petrapixel.neocities.org/coding/layout-generator", "font-size: 14pt; color: #922a45; background: #ffd3ef");
</script>
<body>
<a href="#content" id="skip-to-content-link">Skip to content</a>
<div class="layout">
<!-- =============================================== -->
<!-- headerspiral -->
<!-- =============================================== -->
<div class="headerspiral">
<div class="headerspiral-image">
<img src="https://file.garden/aJzQmzrHVB4BLKwu/elouisaheader.png" alt="" />
</div>
<div class="headerspiral-content">
<div class="headerspiral-title">a crawly conundrum</div>
<!-- NAVIGATION -->
<nav>
<ul>
<li><a href="/shrines/spiraleye">Home</a></li>
<li><a href="/shrines/spiraleyeabout">About</a></li>
<li><a href="/shrines/spiraleyefanworks">Fanworks</a></li>
<li><a href="/shrines/spiraleyeggallery">Gallery</a></li>
<li><a href="/shrines/spiraleyelinks">Links</a></li>
</ul>
</nav>
</div>
</headerspiral>
<div id="content"> <!-- Do NOT remove the ID here -->
<div id="mainspiral">
<!-- =============================================== -->
<!-- MAIN CONTENT -->
<!-- =============================================== -->
<center><a href="https://x.com/SashaFrantseva/status/1738231303269708148"><img src="https://file.garden/aJzQmzrHVB4BLKwu/art.png"></a><big><big>
<strong>spiral eye,</strong> a fan-shrine for the wonderful elouisa from the video game palia.
<p>established oct 15, 2025; last updated oct 17, 2025.</p>
<p><small>this site will update as palia updates.</small></p></center></big></big>
<br><br>
</div>
</div>
<!-- =============================================== -->
<!-- FOOTER -->
<!-- =============================================== -->
<div class="footerspiral">
<a href="https://teeth.kissing.computer/shrines">this is a project of agnes the alien.</a></div>
</div>
</div>

View file

@ -0,0 +1,93 @@
<div class="mainelouisa">
<script>
console.log("%cTemplate generated with petrapixel's layout generator: https://petrapixel.neocities.org/coding/layout-generator", "font-size: 14pt; color: #922a45; background: #ffd3ef");
</script>
<body>
<a href="#content" id="skip-to-content-link">Skip to content</a>
<div class="layout">
<!-- =============================================== -->
<!-- headerspiral -->
<!-- =============================================== -->
<div class="headerspiral">
<div class="headerspiral-image">
<img src="https://file.garden/aJzQmzrHVB4BLKwu/elouisaheader.png" alt="" />
</div>
<div class="headerspiral-content">
<div class="headerspiral-title">a crawly conundrum</div>
<!-- NAVIGATION -->
<nav>
<ul>
<li><a href="/shrines/spiraleye">Home</a></li>
<li><a href="/shrines/spiraleyeabout">About</a></li>
<li><a href="/shrines/spiraleyefanworks">Fanworks</a></li>
<li><a href="/shrines/spiraleyeggallery">Gallery</a></li>
<li><a href="/shrines/spiraleyelinks">Links</a></li>
</ul>
</nav>
</div>
</headerspiral>
<div id="content"> <!-- Do NOT remove the ID here -->
<div id="mainspiral" style="padding:1%;">
<!-- =============================================== -->
<!-- MAIN CONTENT -->
<!-- =============================================== -->
<center> <img src="https://file.garden/aJzQmzrHVB4BLKwu/Elouisa_Reveal.jpg" width="550px" style="border:6px double darkblue;"></center>
<br>"There are several people in Palia who have a thirst for knowledge, and Elouisa is one of them. While her twin sister, Caleri, satiates her desire for information with books, Elouisa explores the world to learn. She enjoys the weird things in life, and if you get close to her, youll be dragged along on some of her expeditions.
Elouisa enjoys Bugs and anything you can gather from the overworld like Sundrop Lilies or Wild Green Onions." - <a href="https://store.epicgames.com/en-US/news/make-friends-with-the-villagers-in-palia-with-our-character-guide">Source</a>
<h3>Background</h3><br><br>
*note: I am still working through Elouisa's quests, so this may not be comprehensive.<br><br>
Elouisa is an adult Majiri.<br><br>Growing up with her twin sister, Caleri, their parents put a significant amount of pressure on both of them --- pressure that shifted entirely to Caleri when Elouisa's mental illness developed later in her life. She is the older twin by a few minutes.<br><br> With her best friend, Gabrina, she took on a significant interest in cryptids and the paranormal, and with Gabrina they were the "paranormal pals". One night, however, after Elouisa begged her to look for a Brighteyes---a cryptid creature--- Gabrina took a lantern made of <a href="https://palia.wiki.gg/wiki/Flow">Flow</a> to their hunt. Elouisa, knowing the Brighteyes feared Flow, extinguished the light, and Gabrina disappeared. Later, she reappeared with a broken arm and began to shun Elouisa for her interest in the paranormal just like everyone else had.<h3>Likes</h3>
Favorite items to be gifted:
<li>Rainbow-tipped butterfly</li>
<li>Aquamarine gemstone</li>
<li>Flowtato fries</li>
<li>Albino eel</li>
<li>Any bug or fish</li>
<h3>Theories</h3>
Elouisa has a variety of paranormal theories. Here are a few:
<li>Humans did not go extinct; they went underground</li>
<li>Auni may be a cryptid</li>
<li>Bugs may be travelers from another dimension</li>
<li>Ancient flowbugs have butts that give off pure Flow energy, and have a psychic link with Silverwing eggs</li>
<li>Fish can communicate psychically with plants</li>
<li>Palium prevents eavesdropping</li>
<li>Gabrina was switched with a Brightfiend, a copy of a person that actually spies for the Brightworld</li>
<li>Cutthroat Trout are the serial killers of the fish world</li></menu>
<hr>
<a href="/shrines/spiraleyeabtsite">You can learn about the webmaster here!</a></center>
<br><br>
</div>
</div>
<!-- =============================================== -->
<!-- FOOTER -->
<!-- =============================================== -->
<div class="footerspiral">
<a href="https://teeth.kissing.computer">this is a project of agnes the alien.</a></div>
</div>
</div>

View file

@ -0,0 +1,70 @@
<div class="mainelouisa">
<script>
console.log("%cTemplate generated with petrapixel's layout generator: https://petrapixel.neocities.org/coding/layout-generator", "font-size: 14pt; color: #922a45; background: #ffd3ef");
</script>
<body>
<a href="#content" id="skip-to-content-link">Skip to content</a>
<div class="layout">
<!-- =============================================== -->
<!-- headerspiral -->
<!-- =============================================== -->
<div class="headerspiral">
<div class="headerspiral-image">
<img src="https://file.garden/aJzQmzrHVB4BLKwu/elouisaheader.png" alt="" />
</div>
<div class="headerspiral-content">
<div class="headerspiral-title">a crawly conundrum</div>
<!-- NAVIGATION -->
<nav>
<ul>
<li><a href="/shrines/spiraleye">Home</a></li>
<li><a href="/shrines/spiraleyeabout">About</a></li>
<li><a href="/shrines/spiraleyefanworks">Fanworks</a></li>
<li><a href="/shrines/spiraleyeggallery">Gallery</a></li>
<li><a href="/shrines/spiraleyelinks">Links</a></li>
</ul>
</nav>
</div>
</headerspiral>
<div id="content"> <!-- Do NOT remove the ID here -->
<div id="mainspiral" style="padding:1%;">
<h3>About the Site</h3>
<p>I fell in love with Palia immediately after I started playing it in January 2024. I found myself drawn to almost every character, but I found myself particularly drawn to Elouisa when I met her. I decided to make a fansite in October 2025, after the <a href="https://events.oubliette.nu/ghostofyou/">Ghost of You shrine challenge</a> inspired me to get into making websites dedicated to characters I love. While this isn't for that challenge specifically, it was a huge inspiration to me.</p>
<p>I love Palia because I love playing video games but I really suck at video games that involve combat-related mechanics. because of this, I never looked into video games as a whole, instead sticking to playing The Sims and making my own browser-based text games. However, Palia's holistic nature -- the ability to have so many skills, the amount of villagers to interact with, the quests, the items, the lack of fall damage or player injury mechanics or any kind, the focus on calmness without rejecting plot --- this got me to be more interested in video games, and I've found myself branching out more and more since I started playing. It has really given me an appreciation for the medium - all genres, not just Palia's.</p>
<p>I love Elouisa because... well. I see myself in her. I too struggle with the same mental illness she does, I have the same hyperniche and whimsical nature she does, and many parts of me have been rejected for being too eccentric like she was. I just want the best for her forever. Also she's hot.</p>
<h3>About the Webmaster</h3>
I'm Agnes (it/zhe/xe/she), a 24 year old computer hobbyist. I run a variety of websites for fun. I really love fantasy and sci-fi, and I've been a writer and artist my whole life. I have been a fan of computers since I was 5 and was given (supervised!) access to what we called back then "the computer room". I love creativity. I also love women. That's about it.
</div>
</div>
<!-- =============================================== -->
<!-- FOOTER -->
<!-- =============================================== -->
<div class="footerspiral">
<a href="https://teeth.kissing.computer/shrines">this is a project of agnes the alien.</a></div>
</div>
</div>

View file

@ -0,0 +1,84 @@
<div class="mainelouisa">
<script>
console.log("%cTemplate generated with petrapixel's layout generator: https://petrapixel.neocities.org/coding/layout-generator", "font-size: 14pt; color: #922a45; background: #ffd3ef");
</script>
<body>
<a href="#content" id="skip-to-content-link">Skip to content</a>
<div class="layout">
<!-- =============================================== -->
<!-- headerspiral -->
<!-- =============================================== -->
<div class="headerspiral">
<div class="headerspiral-image">
<img src="https://file.garden/aJzQmzrHVB4BLKwu/elouisaheader.png" alt="" />
</div>
<div class="headerspiral-content">
<div class="headerspiral-title">a crawly conundrum</div>
<!-- NAVIGATION -->
<nav>
<ul>
<li><a href="/shrines/spiraleye">Home</a></li>
<li><a href="/shrines/spiraleyeabout">About</a></li>
<li><a href="/shrines/spiraleyefanworks">Fanworks</a></li>
<li><a href="/shrines/spiraleyeggallery">Gallery</a></li>
<li><a href="/shrines/spiraleyelinks">Links</a></li>
</ul>
</nav>
</div>
</headerspiral>
<div id="content"> <!-- Do NOT remove the ID here -->
<div id="mainspiral">
<!-- =============================================== -->
<!-- MAIN CONTENT -->
<!-- =============================================== -->
<center><h1>Fanart</h1>
<h3>by me...</h3>
<img src="https://file.garden/aJzQmzrHVB4BLKwu/Untitled1017_20250101224237.png" width="450px">
<img src="https://file.garden/aJzQmzrHVB4BLKwu/Untitled1645_20251022062326.png" width="450px">
<img src="https://file.garden/aJzQmzrHVB4BLKwu/SPOILER_Untitled1675_20251103182947.png" width="450px">
<h3>by others...</h3>
<small>If you would like me to remove your work, <a href="mailto:alienhospitals@gmail.com">email me.</a></small><br>
<a href="https://www.tumblr.com/senaink/750369934849212416/bigbrain?source=share">Cute gif art of Elouisa in anime style by senaink on Tumblr</a>
<br><a href="https://www.tumblr.com/tillero/761117814240182272/maybe-maybe-we-really-can-fix-things-been?source=share">Elouisa & Caleri art by tillero on Tumblr</a><br>
<a href="https://www.tumblr.com/smallestflowtree/741778830335918080/dont-let-them-tell-you-how-to-think-i-love?source=share">Elouisa painting by smallestflowtree on Tumblr</a><br>
<a href="https://www.tumblr.com/tvklike/728448114569199616/i-believe-you?source=share">Elouisa/Tamala art by tvklike on Tumblr</a><br>
<a href="https://www.reddit.com/r/Palia/comments/1ah48ie/elouisa_fanart/">Elouisa fanart by savbaby0509 on Reddit</a><br>
<a href="https://www.tiktok.com/@bscoolart/video/7421996230688984328">Elouisa art by bscoolart on TikTok</a>
<h1>Cosplay</h1>
<a href="https://www.instagram.com/p/DDsShxEvyxn/">Elouisa Cosplay by corvidwitxh on Instagram</a></center>
<br><br>
</div>
</div>
<!-- =============================================== -->
<!-- FOOTER -->
<!-- =============================================== -->
<div class="footerspiral">
<a href="https://teeth.kissing.computer">this is a project of agnes the alien.</a></div>
</div>
</div>

View file

@ -0,0 +1,75 @@
<div class="mainelouisa">
<script>
console.log("%cTemplate generated with petrapixel's layout generator: https://petrapixel.neocities.org/coding/layout-generator", "font-size: 14pt; color: #922a45; background: #ffd3ef");
</script>
<body>
<a href="#content" id="skip-to-content-link">Skip to content</a>
<div class="layout">
<!-- =============================================== -->
<!-- headerspiral -->
<!-- =============================================== -->
<div class="headerspiral">
<div class="headerspiral-image">
<img src="https://file.garden/aJzQmzrHVB4BLKwu/elouisaheader.png" alt="" />
</div>
<div class="headerspiral-content">
<div class="headerspiral-title">a crawly conundrum</div>
<!-- NAVIGATION -->
<nav>
<ul>
<li><a href="/shrines/spiraleye">Home</a></li>
<li><a href="/shrines/spiraleyeabout">About</a></li>
<li><a href="/shrines/spiraleyefanworks">Fanworks</a></li>
<li><a href="/shrines/spiraleyeggallery">Gallery</a></li>
<li><a href="/shrines/spiraleyelinks">Links</a></li>
</ul>
</nav>
</div>
</headerspiral>
<div id="content"> <!-- Do NOT remove the ID here -->
<div id="mainspiral">
<!-- =============================================== -->
<!-- MAIN CONTENT -->
<!-- =============================================== -->
<center> A collection of Elouisa screenshots.
<img src="https://file.garden/aJzQmzrHVB4BLKwu/Screenshot%20(39).png" width="480px"><img src="https://file.garden/aJzQmzrHVB4BLKwu/Screenshot%202025-10-16%20171801.png" width="480px"><img src="https://file.garden/aJzQmzrHVB4BLKwu/Screenshot%20(85).png" width="480px"><img src="https://file.garden/aJzQmzrHVB4BLKwu/Screenshot%20(79).png" width="480px"><img src="https://file.garden/aJzQmzrHVB4BLKwu/Screenshot%20(83).png" width="480px"><img src="https://file.garden/aJzQmzrHVB4BLKwu/Screenshot%202025-10-16%20180248.png" width="480px">
<h3>Elouisa's Room</h3>
<img src="https://file.garden/aJzQmzrHVB4BLKwu/Elouisa's_Room_Interior_3_Ingame.png"><img src="https://file.garden/aJzQmzrHVB4BLKwu/Elouisa's_Room_Entrance_Ingame.png"><img src="https://file.garden/aJzQmzrHVB4BLKwu/Elouisa's_Room_Interior_1_Ingame.png"><img src="https://file.garden/aJzQmzrHVB4BLKwu/Elouisa's_Room_Interior_2_Ingame.png">
<h3>Elouisa Concept Art</h3>
by <a href="https://x.com/SashaFrantseva/status/1738231620736553275/photo/1">sasha frantseva.</a>. <br>
<img src="https://file.garden/aJzQmzrHVB4BLKwu/GB9wXffWUAAdqO8.jpg" width="580px"></center>
<br><br>
</div>
</div>
<!-- =============================================== -->
<!-- FOOTER -->
<!-- =============================================== -->
<div class="footerspiral">
<a href="https://teeth.kissing.computer/shrines">this is a project of agnes the alien.</a></div>
</div>
</div>

View file

@ -0,0 +1,81 @@
<div class="mainelouisa">
<script>
console.log("%cTemplate generated with petrapixel's layout generator: https://petrapixel.neocities.org/coding/layout-generator", "font-size: 14pt; color: #922a45; background: #ffd3ef");
</script>
<body>
<a href="#content" id="skip-to-content-link">Skip to content</a>
<div class="layout">
<!-- =============================================== -->
<!-- headerspiral -->
<!-- =============================================== -->
<div class="headerspiral">
<div class="headerspiral-image">
<img src="https://file.garden/aJzQmzrHVB4BLKwu/elouisaheader.png" alt="" />
</div>
<div class="headerspiral-content">
<div class="headerspiral-title">a crawly conundrum</div>
<!-- NAVIGATION -->
<nav>
<ul>
<li><a href="/shrines/spiraleye">Home</a></li>
<li><a href="/shrines/spiraleyeabout">About</a></li>
<li><a href="/shrines/spiraleyefanworks">Fanworks</a></li>
<li><a href="/shrines/spiraleyeggallery">Gallery</a></li>
<li><a href="/shrines/spiraleyelinks">Links</a></li>
</ul>
</nav>
</div>
</headerspiral>
<div id="content"> <!-- Do NOT remove the ID here -->
<div id="mainspiral" style="padding:1%;">
<!-- =============================================== -->
<!-- MAIN CONTENT -->
<!-- =============================================== -->
Link to me--
(Please do NOT hotlink! Upload to your own site or image hosting platform 🙂)<hr>
<img src="https://file.garden/aJzQmzrHVB4BLKwu/elouisasitebutton.png">
<hr>
Links out---<hr>
<a href="https://emotion.oubliette.nu/"><img src="https://file.garden/aJzQmzrHVB4BLKwu/emotion.png"></a>
<hr>
<a href="https://toothpaste.atabook.org">Guestbook</a>
<hr>
<small>Leave me a message!</small>
</div>
</div>
<!-- =============================================== -->
<!-- FOOTER -->
<!-- =============================================== -->
<div class="footerspiral">
<a href="https://teeth.kissing.computer/shrines">this is a project of agnes the alien.</a></div>
</div>
</div>

View file

@ -0,0 +1,22 @@
<div class="bodyspirit">
<h1><center><img src="https://file.garden/aJzQmzrHVB4BLKwu/home.gif" width="290px"></center></h1>
<h3><a href="/shrines/spirit">HOME</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spiritquiz">QUIZZES</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spirittimeline">EXPLANATION</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spiritsc">SCREENSHOTS & VIDEOS</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spirithc">WORLDBUILDING</a></h3>
Welcome to the homepage of the Negative Spirits! This is a fanpage for the fictional species known as the Negative Spirits from the show Doom Patrol on DC Universe (then, later, HBO Max).<br><br>
<center><h3>WHY NOT THE COMICS?</h3>
I originally fell in love with the Negative Spirit and their species while watching the Doom Patrol show in 2019. While I respect the comics as the basis of the media I love, I don't really care about them as much as the show. However, much of the lore in the <i><a href="/hc">headcanons</a></i> zone is based tangentially on Gerard Way's run of Doom Patrol.
<center><h3>WHAT ARE NEGATIVE SPIRITS?</h3></center>
Negative Spirits are a fictional alien race from the show Doom Patrol, based on the comic series by DC Comics. While the show they come from is based largely on a specific run from the comics (Morrison's), I consider TV Negative Spirits to be divergent from comic Negative Spirits. Negative Spirits are noncorporeal aliens formed of pure energy that have a variety of powers, such as immortality, psychic ability, interdimensional travel, electrocution and electricity manipulation, flight, and possession of human hosts (though it makes them radioactive, but this can be contained via emotional bonding with host).
<center><h3>WHY DO THEY FASCINATE ME SO MUCH?</h3></center>
The Negative Spirits have been shown to be extremely emotionally sensitive, at least according to Niles Caulder. Larry's Negative Spirit, specifically, is my favorite character in the whole universe, and I find its heroic nature to be very beautiful. Larry doesn't want to intervene, doesn't want to help, only wants to hide and shrivel away, but his Negative Spirit forces him to help. Larry doesn't want to heal from his trauma and his Negative Spirit helps him confront his demons. His Negative Spirit helps save the world and fight evil and wants to be in the center of it all, and it has no reason to do this as the world has only been unkind and cruel to it during 95% of its time on earth. <br><br>
Now, the Negative Spirit is still an asshole, but that's also part of why I love it. It puts Larry on the ceiling when he tries to communicate with it. It repeatedly leaves Larry's body (which knocks him unconscious). It crashes Larry's bus after he calls it a parasite. But there are also so many tender moments between them--Larry saying they have an untraditional connection in Frances Patrol, Larry saying he believes in it, their general closeness during the 6 month time skip between 1x14 and 1x15, Larry falling asleep with his hand over his chest as it emitted its glow, Larry's attempted sacrifice and it choosing to stay, and of course the face touch. Negative Spirits are, well, aliens with complex emotional states! Larry's NS only seems to lash out when Larry is cruel to it, as Larry's self hatred is extremely torturous to live with---Negative spirits are merged with their host's mind and thoughts on top of being incredibly emotionally sensitive already.<br><br>
And then there's Valentina Vostok, who is Larry's narrative parallel. A glimpse into what could be for him. Her Negative Spirit has bonded with her entirely, they "fell in love", and they have perfect communication. Larry thinks this has made her entirely alien, but her and her Spirit were alone in a ship with two corpses for Fifty Years, so that kind of isolation rusts the connection to humanity a bit. Valentina acts detached from Earth, more serene, and so does her Spirit; this indicates to me that merging with a Negative Spirit can be a beautiful thing under the right circumstances. Or maybe the most desperate circumstances.
<br><br>I love the Negative Spirits so much. <br><h3>LINK TO ME!!</h3>
<a href="https://teeth.kissing.computer/fun/spirit"><img src="https://i.ibb.co/Pv0Jd5XR/button.png" alt="button" border="0"></a><br><textarea>< <a href="https://teeth.kissing.computer"><img src="https://i.ibb.co/Pv0Jd5XR/button.png" alt="button" border="0"></a></textarea><hr>
<small><small>Contact<br>
Agnes the Alien<br>
alienhospitals@gmail.com<br>
<a href="https://kissing.computer">kissing.computer</a></small></small>

View file

@ -0,0 +1,17 @@
<div class="bodyspirit"><h1><center><img src="https://file.garden/aJzQmzrHVB4BLKwu/home.gif" width="290px"></center></h1>
<h3><a href="/shrines/spirit">HOME</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spiritquiz">QUIZZES</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spirittimeline">EXPLANATION</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spiritsc">SCREENSHOTS & VIDEOS</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spirithc">WORLDBUILDING</a></h3>
<center><h1>HEADCANONS/WORLDBUILDING</h1></center>
Most of this is based on the Gerard Way comics.<hr>
<br>
<b>negative spirits experience everything more intensely than we do.</b> emotions especially, but also sensations and experiences. it's almost unbearable for them, and a major emphasis in spirit society is on dealing with these emotions. hence, creativity is encouraged as an outlet for them, and those particularly good at expressing themselves are highly respected - an example of this is the great poets.<br><br>
<b>the naming rule. </b>in the comics, the only named spirits have four letter first and last names; hence, it is my opinion that in the negative space, spirits are usually given names that have only four letters. spirits may choose any name they wish over the course of their existence, but the "default" spirit names when created have four letters only.<br><br>
<b>speaking of creation... </b>negative spirits do not reproduce sexually, they are incapable of it. instead, to reproduce, they use a system similar to the loom theory from doctor who. the family extracts negative energy from their existence. to create a new being, the spirits need at least one source of negative energy, though there can be more than one source, and the more sources a spirit has, the more powerful they are. the spirit is then woven together and has the knowledge of their source spirits as well, but must adjust to life on their own. when a spirit extracts their energy, they lose an aspect of themselves, whether it is a memory, a trait, a skill, or something else, so many spirits are hesitant to reproduce.</b>
<br><br><b>spirits are immortal.</b> they do not die in their own habitat ever. if the dimension gets too populated beyond what it can sustain, the great sun expands it further; therefore the negative space is technically infinite.<br><br><b>negative spirits mate for life.</b> unless they don't. there is the idea of a b'keh, a soul pairing. this can be of any kind; platonic, romantic, undefined. some have more than one. once they find their soulmate, both/all are taken to what is called a realization sequence -- they are briefly transported into a psychic projection involving the two spirits that displays meaningful imagery to them and generally lets them know that they've found their soulmate(s). however, many spirits do not participate in this aspect of spirit culture and those who choose not to do not experience realization sequences. everything in the negative space is voluntary, which is why being involuntarily forced out of the negative space was so hard for larry's spirit.
the soulmates are said to be chosen by...
<br><br><b>the great sun.</b> this is the negative space's concept of the being that created the negative space, it is not deified in the traditional human sense but rather considered a personal friend of each negative spirit. there are many different views of the great sun, and many different belief systems, but most of them have the great sun as a central figure. the great sun is a being that lives within the fabric that composes the dimension; it created the Hall of N'Hal first before anything else in the dimension, hence N'Hal hall is considered the atelier of the dimension. religion in the negative space is different than on earth; in the negative space the great sun is prayed to, yes, and is considered to be very powerful, but it is considered offensive to the great sun to worship it or give offerings, because the great sun is supposed to be seen as similar to any negative spiritit is a friend, not a deity to them. its warmth embraces everyone.
<hr>
<small><small>Contact<br>
Agnes Alien<br>
spirits@alien.hospital</small></small>
</div>

View file

@ -0,0 +1,10 @@
<div class="bodyspirit"> <h1><center><img src="https://file.garden/aJzQmzrHVB4BLKwu/home.gif" width="290px"></center></h1>
<h3><a href="/shrines/spirit">HOME</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spiritquiz">QUIZZES</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spirittimeline">EXPLANATION</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spiritsc">SCREENSHOTS & VIDEOS</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spirithc">WORLDBUILDING</a></h3><h1>QUIZZES!</h1>
<h2>Which Negative Spirit are you?</h2>psst..not showing up? refresh the page!<br><br>
<div id="uquiz_embed" data-quiz-id="8x3y3h" data-width="30%"></div>
<script src="https://uquiz.com/embed.js"></script>
Got an idea for a quiz? Email me!</center>
<hr>
<small><small>Contact<br>
Agnes Alien<br>
spirits@alien.hospital</small></small></div>

View file

@ -0,0 +1,35 @@
<div class="bodyspirit"
<h1><center><img src="https://file.garden/aJzQmzrHVB4BLKwu/home.gif" width="290px"></center></h1>
<h3><a href="/shrines/spirit">HOME</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spiritquiz">QUIZZES</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spirittimeline">EXPLANATION</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spiritsc">SCREENSHOTS & VIDEOS</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spirithc">WORLDBUILDING</a></h3>
<center><h2>Screenshots & Videos</h2>
<h3>Larry Trainor's Negative Spirit Screenshots</h3>
<img src="https://i.ibb.co/C5hQy7f9/DP-S01-E01-0164-Copy.jpg" alt="DP-S01-E01-0164-Copy" border="0" width="590px">
<img src="https://i.ibb.co/FP38c9S/DP-S01-E02-1390-Copy.jpg" alt="DP-S01-E02-1390-Copy" border="0" width="590px">
<img src="https://i.ibb.co/Zz6FnXnP/DP-S01-E04-0130-Copy.jpg" alt="DP-S01-E04-0130-Copy" border="0" width="590px">
<img src="https://i.ibb.co/673vyk3n/DP-S01-E04-0765-Copy.jpg" alt="DP-S01-E04-0765-Copy" border="0" width="590px">
<img src="https://i.ibb.co/5mSJ96d/DP-S01-E06-1157.jpg" alt="DP-S01-E06-1157" border="0" width="590px">
<img src="https://i.ibb.co/hFSQVxWh/DP-S01-E13-0109.jpg" alt="DP-S01-E13-0109" border="0" width="590px">
<img src="https://i.ibb.co/My2fWt7P/DP-S01-E13-0124-Copy.jpg" alt="DP-S01-E13-0124-Copy" border="0" width="590px">
<img src="https://i.ibb.co/tksWk70/DP-S01-E13-0199.jpg" alt="DP-S01-E13-0199" border="0" width="590px">
<img src="https://i.ibb.co/BKrx2G9T/DP-S01-E13-0238.jpg" alt="DP-S01-E13-0238" border="0" width="590px">
<img src="https://i.ibb.co/Rp15yQF9/DP-S01-E13-0924.jpg" alt="DP-S01-E13-0924" border="0" width="590px">
<img src="https://i.ibb.co/3yZ8yPxw/DP-S01-E13-0936-Copy.jpg" alt="DP-S01-E13-0936-Copy" border="0" width="590px">
<img src="https://i.ibb.co/0yzvfR9S/DP-S01-E13-1210.jpg" alt="DP-S01-E13-1210" border="0" width="590px">
<img src="https://i.ibb.co/WWF6C4R0/DP-S01-E13-1216-Copy.jpg" alt="DP-S01-E13-1216-Copy" border="0" width="590px">
<img src="https://i.ibb.co/Q73bWN4n/DP-S02-E01-0941.jpg" alt="DP-S02-E01-0941" border="0" width="590px">
<img src="https://i.ibb.co/ymsqdMyr/maxresdefault-4.jpg" alt="maxresdefault-4" border="0" width="590px"> q
<h3>Larry Trainor's Negative Spirit Videos</h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/b3neG8_blWg?si=Zju1kfdesDLg_k0x" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<h3>Valentina Vostok's Negative Spirit Screencaps</h3>
<img src="https://i.ibb.co/84s3pgVG/DP-S02-E06-0539.jpg" alt="DP-S02-E06-0539" border="0" width="590px">
<img src="https://i.ibb.co/Ldyh7LSv/DP-S02-E06-0538.jpg" alt="DP-S02-E06-0538" border="0" width="590px">
<img src="https://i.ibb.co/BHZx1LCN/DP-S02-E06-0454.jpg" alt="DP-S02-E06-0454" border="0" width="590px">
<hr>
<small><small>Contact<br>
Agnes Alien<br>
spirits@alien.hospital</small></small>

View file

@ -0,0 +1,32 @@
<div class="bodyspirit" style="padding:9px;">
<h1><center><img src="https://file.garden/aJzQmzrHVB4BLKwu/home.gif" width="290px"></center></h1>
<h3><a href="/shrines/spirit">HOME</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spiritquiz">QUIZZES</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spirittimeline">EXPLANATION</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spiritsc">SCREENSHOTS & VIDEOS</a> <img src="https://file.garden/aJzQmzrHVB4BLKwu/stars.gif" width="50px"> <a href="/shrines/spirithc">WORLDBUILDING</a></h3>
<h3>Timeline</h3>
<small><i>Note: As I am primarily a show fan, feel free to email me any corrections. I probably missed something.<br></i><br>
<h3>Comics</h3>
June 1963. The first Doom Patrol comic story featuring Larry Trainor/Negative Man, an Air Force test pilot fused to an alien negative spirit, is released. (My Greatest Adventure #80)<br>
September 1977. After Larry Trainor's death, the Negative Spirit inside of him is transferred to Valentina Vostok of the Soviet Air Force. (Showcase vol 1 #94)<br>
February 1989. The Negative Spirit, potentially known as Mercurius, originally thought to be a projection of Larry's consciousness, is revealed to be its own unique being. (Doom Patrol Vol 2 #19)<br>
February 1989. The Negative Spirit, Larry Trainor, and Dr. Eleanor Poole merge to become a different being known as Rebis, who is not any of them. (Doom Patrol vol 2 #19)<br>
December 2016. In Gerard Way's run of Doom Patrol, Larry's Negative Spirit is known as Keeg Bovo. He is introduced for the first time. This may or may not be the same individual as Mercurius. (Doom Patrol vol 6 #2)
January 2017. Keeg Bovo takes Larry Trainor and Cliff Steele to his dimension, the Negative Space. (Doom Patrol vol 6 #4) <br>
August 2019. Larry Trainor becomes Positive Man. (Weight of the Worlds #2)<br>
July 2023. We learn that Keeg Bovo left Larry Trainor sometime after he became positive man. (Unstoppable Doom Patrol #4)<br><br>
<h3>Live Action Adaptation</h3>
February 2019. The first episode of Doom Patrol, introducing Larry Trainor's live action/cgi Negative Spirit, premieres on DC Universe. <br>
July 2020. Space Patrol airs and introduces Valentina Vostok, a one-episode character who has a Spirit of her own.<br>
September 2021. The episode Vacay Patrol, season 3 episode 2, writes the Negative Spirit off. It tells Larry it wants to take him to space, but abandons him, leaving for its dimension and sending Larry back home.<br>
October 2021. Keeg, the infant son of Larry Trainor and his Negative Spirit, is born.<br>
December 2022. In the show, Larry Trainor travels to the future and sees an adult version of his son, Keeg, and his own ghost in a dystopian apocalypse as the final season premieres.<br>
April 2023. In an adjacent universe, Titans, another DC show, has its own version of the Negative Spirit. This Spirit is an adult; however, as Larry's negative Spirit carries only signatures of blue, this Spirit carries both blue and red signatures, just as Larry's infant son does. It is unclear if this is the original Negative Spirit of Larry's or an adult version of Larry's son.<br>
November 2023. Larry's Negative Spirit makes an appearance once more as Larry time travels back to 1996. The Spirit possesses past!Larry's body and speaks to present!Larry.<br>
November 2023. Doom Patrol ends.<br>
<hr>
<small><small>Contact<br>
Agnes Alien<br>
spirits@alien.hospital</small></small>

Some files were not shown because too many files have changed in this diff Show more