django #1

Merged
dominic merged 5 commits from django into master 2025-06-29 18:30:35 -04:00
52 changed files with 1976 additions and 0 deletions

207
.gitignore vendored Normal file
View file

@ -0,0 +1,207 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/

BIN
db.sqlite3 Normal file

Binary file not shown.

22
manage.py Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shrinkinminkin.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

137
requirements.txt Normal file
View file

@ -0,0 +1,137 @@
appdirs==1.4.4
argcomplete==3.1.4
attrs==23.2.0
beautifulsoup4==4.12.3
blinker==1.7.0
bottle==0.12.25
Brlapi==0.8.5
Brotli==1.1.0
certifi==2023.11.17
chardet==5.2.0
click==8.1.6
colorama==0.4.6
command-not-found==0.3
configobj==5.0.8
contourpy==1.0.7
cryptography==41.0.7
cupshelpers==1.0
cycler==0.11.0
dbus-python==1.3.2
decorator==5.1.1
defer==1.0.6
defusedxml==0.7.1
deprecation==2.0.7
dill==0.3.8
distro==1.9.0
docker==5.0.3
eyed3==0.9.7
filelock==3.13.1
filetype==1.2.0
fonttools==4.46.0
fs==2.4.16
Glances==3.4.0.3
gyp==0.1
html2text==2024.2.26
httplib2==0.20.4
idna==3.6
ifaddr==0.2.0
IMDbPY==2021.4.18
influxdb==5.3.1
kiwisolver==0.0.0
launchpadlib==1.11.0
lazr.restfulclient==0.14.6
lazr.uri==1.0.6
libevdev==0.5
libvirt-python==10.0.0
louis==3.29.0
lxml==5.2.1
lz4==4.0.2+dfsg
Mako==1.3.2.dev0
Markdown==3.5.2
markdown-it-py==3.0.0
MarkupSafe==2.1.5
matplotlib==3.6.3
mdurl==0.1.2
mpmath==0.0.0
msgpack==1.0.3
multiprocess==0.70.16
mutagen==1.46.0
nemo-emblems==6.4.0
netaddr==0.8.0
netifaces==0.11.0
nicotine-plus==3.2.9
numpy==1.26.4
oauthlib==3.2.2
onboard==1.4.1
packaging==24.0
PAM==0.4.2
pexpect==4.9.0
pillow==10.2.0
pipx==1.4.3
platformdirs==4.2.0
ply==3.11
psutil==5.9.8
ptyprocess==0.7.0
pulsemixer==1.5.1
pyasn1==0.4.8
pyasyncore==1.0.2
pycairo==1.25.1
pycryptodomex==3.20.0
pycups==2.0.1
pycurl==7.45.3
pyelftools==0.30
Pygments==2.17.2
PyGObject==3.48.2
PyICU==2.12
pyinotify==0.9.6
PyJWT==2.7.0
PyNaCl==1.5.0
pyparsing==3.1.1
pyparted==3.12.0
pypng==0.20231004.0
pysmi==0.3.4
pysnmp==4.4.12
pystache==0.6.0
python-apt==2.7.7+ubuntu4
python-dateutil==2.8.2
python-debian==0.1.49+ubuntu2
python-gnupg==0.5.2
python-magic==0.4.27
python-xlib==0.33
pytz==2024.1
pyudev==0.24.0
pyxdg==0.28
PyYAML==6.0.1
qrcode==7.4.2
ranger-fm==1.9.3
repolib==2.2.1
requests==2.31.0
requests-file==1.5.1
rich==13.7.1
SciPy==1.11.4
setproctitle==1.3.3
setuptools==68.1.2
six==1.16.0
soupsieve==2.5
sympy==1.12
systemd-python==235
tinycss2==1.2.1
tldextract==3.1.2
typing_extensions==4.10.0
ubuntu-drivers-common==0.0.0
ufoLib2==0.16.0
ufw==0.36.2
ujson==5.9.0
unicodedata2==15.1.0
Unidecode==1.3.8
urllib3==2.0.7
userpath==1.9.1
wadllib==1.3.6
webencodings==0.5.1
websocket-client==1.7.0
websockets==10.4
wheel==0.42.0
xdg==5
xkit==0.0.0
xlrd==2.0.1
yt-dlp==2024.4.9

View file

16
shrinkinminkin/asgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
ASGI config for shrinkinminkin project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shrinkinminkin.settings')
application = get_asgi_application()

130
shrinkinminkin/settings.py Normal file
View file

@ -0,0 +1,130 @@
"""
Django settings for shrinkinminkin project.
Generated by 'django-admin startproject' using Django 5.2.3.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-)z5-$t&%njx^$4wrsq4+@#oybrssf_z*1(p9t(^o2v!2nkybab'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['localhost', 'localhost:8000', '127.0.0.1']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'web',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'shrinkinminkin.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'shrinkinminkin.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static',]
STORAGES = {
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

13
shrinkinminkin/urls.py Normal file
View file

@ -0,0 +1,13 @@
from django.contrib import admin
from django.urls import path
from web.views import index, album, socials, mailing_list
urlpatterns = [
path('', index, name='index'),
path('album/', album, name='album'),
path('album.html/', album, name='album'),
path('socials/', socials, name='socials'),
path('socials.html/', socials, name='socials'),
path('mailing_list/', mailing_list, name='mailing_list'),
path('admin/', admin.site.urls),
]

16
shrinkinminkin/wsgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
WSGI config for shrinkinminkin project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shrinkinminkin.settings')
application = get_wsgi_application()

7
static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

70
static/css/style.css Normal file
View file

@ -0,0 +1,70 @@
@font-face { font-family: Virgo; src: url('../fonts/virgo.ttf'); }
body {
background-color: #189ccf;
font-family: Virgo, monospace;
}
.header-img {
width: 100%;
max-width: 600px;
height: auto;
}
.link {
margin-top: 20px;
width: 100%;
}
.nav-link {
width: 150px;
padding-right: 10px;
display: inline;
margin-top: 10px;
}
h1 {
font-size: 45px;
}
#band-img-1 {
height: auto;
}
.iframe-container {
position: relative;
overflow: hidden;
width: 100%;
padding-top: 56.25%; /* 16:9 Aspect Ratio (divide 9 by 16 = 0.5625) */
}
.responsive-iframe {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
width: 100%;
height: 100%;
}
.navi-link {
color: #ff0000;
padding-left: 10px;
padding-right: 10px;
}
.navi-link:hover {
opacity: 70%;
color: #ff0000;
}
.contact-container {
color: #20d582;
background-color: #8220d5;
}
.footer-image {
width: 100%;
height: auto;
}

BIN
static/fonts/nabla.ttf Normal file

Binary file not shown.

BIN
static/fonts/virgo.ttf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
static/img/band_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
static/img/band_banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

BIN
static/img/members.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

7
static/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
static/js/p5.min.js vendored Normal file

File diff suppressed because one or more lines are too long

230
static/js/sketch.js Normal file
View file

@ -0,0 +1,230 @@
let myFont;
function preload() {
myFont = loadFont('assets/fonts/nabla.ttf');
}
function setup() {
if (windowWidth <= 480) {
var canvas = createCanvas(windowWidth, 200);
} else {
var canvas = createCanvas(windowWidth, 300);
}
canvas.parent('shrinkin-sketch')
}
function windowResized() {
if (windowWidth <= 480) {
var canvas = resizeCanvas(windowWidth, 200);
} else {
var canvas = resizeCanvas(windowWidth, 300);
}
}
class Ball {
constructor(x, y, r) {
this.position = new p5.Vector(x, y);
this.velocity = p5.Vector.random2D();
this.velocity.mult(3);
this.r = r;
this.m = r * 0.1;
}
update() {
this.position.add(this.velocity);
}
checkBoundaryCollision() {
if (this.position.x > width - this.r) {
this.position.x = width - this.r;
this.velocity.x *= -1;
} else if (this.position.x < this.r) {
this.position.x = this.r;
this.velocity.x *= -1;
} else if (this.position.y > height - this.r) {
this.position.y = height - this.r;
this.velocity.y *= -1;
} else if (this.position.y < this.r) {
this.position.y = this.r;
this.velocity.y *= -1;
}
}
checkCollision(other) {
// Get distances between the balls components
let distanceVect = p5.Vector.sub(other.position, this.position);
// Calculate magnitude of the vector separating the balls
let distanceVectMag = distanceVect.mag();
// Minimum distance before they are touching
let minDistance = this.r + other.r;
if (distanceVectMag < minDistance) {
let distanceCorrection = (minDistance - distanceVectMag) / 2.0;
let d = distanceVect.copy();
let correctionVector = d.normalize().mult(distanceCorrection);
other.position.add(correctionVector);
this.position.sub(correctionVector);
// get angle of distanceVect
let theta = distanceVect.heading();
// precalculate trig values
let sine = sin(theta);
let cosine = cos(theta);
/* bTemp will hold rotated ball this.positions. You
just need to worry about bTemp[1] this.position*/
let bTemp = [new p5.Vector(), new p5.Vector()];
/* this ball's this.position is relative to the other
so you can use the vector between them (bVect) as the
reference point in the rotation expressions.
bTemp[0].this.position.x and bTemp[0].this.position.y will initialize
automatically to 0.0, which is what you want
since b[1] will rotate around b[0] */
bTemp[1].x = cosine * distanceVect.x + sine * distanceVect.y;
bTemp[1].y = cosine * distanceVect.y - sine * distanceVect.x;
// rotate Temporary velocities
let vTemp = [new p5.Vector(), new p5.Vector()];
vTemp[0].x = cosine * this.velocity.x + sine * this.velocity.y;
vTemp[0].y = cosine * this.velocity.y - sine * this.velocity.x;
vTemp[1].x = cosine * other.velocity.x + sine * other.velocity.y;
vTemp[1].y = cosine * other.velocity.y - sine * other.velocity.x;
/* Now that velocities are rotated, you can use 1D
conservation of momentum equations to calculate
the final this.velocity along the x-axis. */
let vFinal = [new p5.Vector(), new p5.Vector()];
// final rotated this.velocity for b[0]
vFinal[0].x =
((this.m - other.m) * vTemp[0].x + 2 * other.m * vTemp[1].x) /
(this.m + other.m);
vFinal[0].y = vTemp[0].y;
// final rotated this.velocity for b[0]
vFinal[1].x =
((other.m - this.m) * vTemp[1].x + 2 * this.m * vTemp[0].x) /
(this.m + other.m);
vFinal[1].y = vTemp[1].y;
// hack to avoid clumping
bTemp[0].x += vFinal[0].x;
bTemp[1].x += vFinal[1].x;
/* Rotate ball this.positions and velocities back
Reverse signs in trig expressions to rotate
in the opposite direction */
// rotate balls
let bFinal = [new p5.Vector(), new p5.Vector()];
bFinal[0].x = cosine * bTemp[0].x - sine * bTemp[0].y;
bFinal[0].y = cosine * bTemp[0].y + sine * bTemp[0].x;
bFinal[1].x = cosine * bTemp[1].x - sine * bTemp[1].y;
bFinal[1].y = cosine * bTemp[1].y + sine * bTemp[1].x;
// update balls to screen this.position
other.position.x = this.position.x + bFinal[1].x;
other.position.y = this.position.y + bFinal[1].y;
this.position.add(bFinal[0]);
// update velocities
this.velocity.x = cosine * vFinal[0].x - sine * vFinal[0].y;
this.velocity.y = cosine * vFinal[0].y + sine * vFinal[0].x;
other.velocity.x = cosine * vFinal[1].x - sine * vFinal[1].y;
other.velocity.y = cosine * vFinal[1].y + sine * vFinal[1].x;
}
}
display(friend1, friend2) {
this.update()
noStroke();
fill('#ff0000');
ellipse(this.position.x, this.position.y, this.r * 2, this.r * 2);
this.checkBoundaryCollision()
this.checkCollision(friend1)
this.checkCollision(friend2)
}
}
class Logo {
constructor() {
this.yOffset = 0
this.up = false
this.textSize = 36
this.bounceDepth = 10
this.speed = 0.2
}
display() {
fill('#20d582');
textFont(myFont);
if (windowWidth/6 > 120) {
textSize(120);
} else {
textSize(windowWidth/6)
}
if (windowWidth <= 480) {
text('shrinkin', width/2 - 120, 20 + 60 + this.yOffset);
text('-minkin', width/2 - 120, 100 + 60 + this.yOffset);
} else {
text('shrinkin', width/2 - 120, 50 + 90 + this.yOffset);
text('-minkin', width/2 - 120, 150 + 90 + this.yOffset);
}
this.bounce()
}
bounce() {
if (!this.up) {
if (this.yOffset <= this.bounceDepth) {
this.yOffset = this.yOffset + this.speed
}
if (this.yOffset > this.bounceDepth) {
this.up = true
}
} else {
if (this.yOffset > 0) {
this.yOffset = this.yOffset - this.speed
}
if (this.yOffset < 0) {
this.up = false
}
}
}
}
let ball1 = new Ball(100, 400, 20)
let ball2 = new Ball(700, 400, 80)
let ball3 = new Ball(0, 200, 60)
let logo = new Logo()
function draw() {
background('#209bd0');
ball1.display(ball2, ball3)
ball2.display(ball1, ball3)
ball3.display(ball1, ball2)
if (windowWidth <= 480) {
ball2.r = 40;
ball3.r = 30;
} else {
ball2.r = 80;
ball3.r = 60;
}
logo.display()
}

227
static/js/sketch_export.js Normal file
View file

@ -0,0 +1,227 @@
let myFont;
function preload() {
myFont = loadFont('assets/fonts/nabla.ttf');
}
function setup() {
var canvas = createCanvas(1200, 400);
canvas.parent('shrinkin-sketch')
createLoop({duration:120, gif:true})
}
function windowResized() {
if (windowWidth <= 480) {
var canvas = resizeCanvas(windowWidth, 200);
} else {
var canvas = resizeCanvas(windowWidth, 300);
}
}
class Ball {
constructor(x, y, r) {
this.position = new p5.Vector(x, y);
this.velocity = p5.Vector.random2D();
this.velocity.mult(3);
this.r = r;
this.m = r * 0.1;
}
update() {
this.position.add(this.velocity);
}
checkBoundaryCollision() {
if (this.position.x > width - this.r) {
this.position.x = width - this.r;
this.velocity.x *= -1;
} else if (this.position.x < this.r) {
this.position.x = this.r;
this.velocity.x *= -1;
} else if (this.position.y > height - this.r) {
this.position.y = height - this.r;
this.velocity.y *= -1;
} else if (this.position.y < this.r) {
this.position.y = this.r;
this.velocity.y *= -1;
}
}
checkCollision(other) {
// Get distances between the balls components
let distanceVect = p5.Vector.sub(other.position, this.position);
// Calculate magnitude of the vector separating the balls
let distanceVectMag = distanceVect.mag();
// Minimum distance before they are touching
let minDistance = this.r + other.r;
if (distanceVectMag < minDistance) {
let distanceCorrection = (minDistance - distanceVectMag) / 2.0;
let d = distanceVect.copy();
let correctionVector = d.normalize().mult(distanceCorrection);
other.position.add(correctionVector);
this.position.sub(correctionVector);
// get angle of distanceVect
let theta = distanceVect.heading();
// precalculate trig values
let sine = sin(theta);
let cosine = cos(theta);
/* bTemp will hold rotated ball this.positions. You
just need to worry about bTemp[1] this.position*/
let bTemp = [new p5.Vector(), new p5.Vector()];
/* this ball's this.position is relative to the other
so you can use the vector between them (bVect) as the
reference point in the rotation expressions.
bTemp[0].this.position.x and bTemp[0].this.position.y will initialize
automatically to 0.0, which is what you want
since b[1] will rotate around b[0] */
bTemp[1].x = cosine * distanceVect.x + sine * distanceVect.y;
bTemp[1].y = cosine * distanceVect.y - sine * distanceVect.x;
// rotate Temporary velocities
let vTemp = [new p5.Vector(), new p5.Vector()];
vTemp[0].x = cosine * this.velocity.x + sine * this.velocity.y;
vTemp[0].y = cosine * this.velocity.y - sine * this.velocity.x;
vTemp[1].x = cosine * other.velocity.x + sine * other.velocity.y;
vTemp[1].y = cosine * other.velocity.y - sine * other.velocity.x;
/* Now that velocities are rotated, you can use 1D
conservation of momentum equations to calculate
the final this.velocity along the x-axis. */
let vFinal = [new p5.Vector(), new p5.Vector()];
// final rotated this.velocity for b[0]
vFinal[0].x =
((this.m - other.m) * vTemp[0].x + 2 * other.m * vTemp[1].x) /
(this.m + other.m);
vFinal[0].y = vTemp[0].y;
// final rotated this.velocity for b[0]
vFinal[1].x =
((other.m - this.m) * vTemp[1].x + 2 * this.m * vTemp[0].x) /
(this.m + other.m);
vFinal[1].y = vTemp[1].y;
// hack to avoid clumping
bTemp[0].x += vFinal[0].x;
bTemp[1].x += vFinal[1].x;
/* Rotate ball this.positions and velocities back
Reverse signs in trig expressions to rotate
in the opposite direction */
// rotate balls
let bFinal = [new p5.Vector(), new p5.Vector()];
bFinal[0].x = cosine * bTemp[0].x - sine * bTemp[0].y;
bFinal[0].y = cosine * bTemp[0].y + sine * bTemp[0].x;
bFinal[1].x = cosine * bTemp[1].x - sine * bTemp[1].y;
bFinal[1].y = cosine * bTemp[1].y + sine * bTemp[1].x;
// update balls to screen this.position
other.position.x = this.position.x + bFinal[1].x;
other.position.y = this.position.y + bFinal[1].y;
this.position.add(bFinal[0]);
// update velocities
this.velocity.x = cosine * vFinal[0].x - sine * vFinal[0].y;
this.velocity.y = cosine * vFinal[0].y + sine * vFinal[0].x;
other.velocity.x = cosine * vFinal[1].x - sine * vFinal[1].y;
other.velocity.y = cosine * vFinal[1].y + sine * vFinal[1].x;
}
}
display(friend1, friend2) {
this.update()
noStroke();
fill('#ff0000');
ellipse(this.position.x, this.position.y, this.r * 2, this.r * 2);
this.checkBoundaryCollision()
this.checkCollision(friend1)
this.checkCollision(friend2)
}
}
class Logo {
constructor() {
this.yOffset = 0
this.up = false
this.textSize = 36
this.bounceDepth = 10
this.speed = 0.2
}
display() {
fill('#20d582');
textFont(myFont);
if (windowWidth/6 > 120) {
textSize(120);
} else {
textSize(windowWidth/6)
}
if (windowWidth <= 480) {
text('shrinkin', width/2 - 120, 20 + 60 + this.yOffset);
text('-minkin', width/2 - 120, 100 + 60 + this.yOffset);
} else {
text('shrinkin', width/2 - 120, 50 + 90 + this.yOffset);
text('-minkin', width/2 - 120, 150 + 90 + this.yOffset);
}
this.bounce()
}
bounce() {
if (!this.up) {
if (this.yOffset <= this.bounceDepth) {
this.yOffset = this.yOffset + this.speed
}
if (this.yOffset > this.bounceDepth) {
this.up = true
}
} else {
if (this.yOffset > 0) {
this.yOffset = this.yOffset - this.speed
}
if (this.yOffset < 0) {
this.up = false
}
}
}
}
let ball1 = new Ball(100, 400, 20)
let ball2 = new Ball(700, 400, 80)
let ball3 = new Ball(0, 200, 60)
let logo = new Logo()
function draw() {
background('#209bd0');
ball1.display(ball2, ball3)
ball2.display(ball1, ball3)
ball3.display(ball1, ball2)
if (windowWidth <= 480) {
ball2.r = 40;
ball3.r = 30;
} else {
ball2.r = 80;
ball3.r = 60;
}
logo.display()
}

0
web/__init__.py Normal file
View file

6
web/admin.py Normal file
View file

@ -0,0 +1,6 @@
from django.contrib import admin
from web.models import MailingList
admin.site.register(MailingList)
# Register your models here.

6
web/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class WebConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'web'

View file

@ -0,0 +1,27 @@
# Generated by Django 5.2.3 on 2025-06-29 21:28
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='MailingList',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('updated_at', models.DateTimeField(auto_now=True, null=True)),
('active', models.BooleanField(default=True)),
('email', models.EmailField(max_length=254)),
],
options={
'abstract': False,
},
),
]

View file

Binary file not shown.

14
web/models.py Normal file
View file

@ -0,0 +1,14 @@
from django.db import models
class BaseModel(models.Model):
created_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
updated_at = models.DateTimeField(blank=True, null=True, auto_now=True)
active = models.BooleanField(default=True)
class Meta:
abstract = True
class MailingList(BaseModel):
email = models.EmailField(max_length=254, blank=False, null=False)

7
web/static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

69
web/static/css/style.css Normal file
View file

@ -0,0 +1,69 @@
@font-face { font-family: Virgo; src: url('../fonts/virgo.ttf'); }
body {
background-color: #189ccf;
font-family: Virgo, sans-serif;
}
.header-img {
width: 100%;
max-width: 600px;
height: auto;
}
.link {
margin-top: 20px;
width: 100%;
}
.nav-link {
width: 150px;
padding-right: 10px;
display: inline;
}
h1 {
font-size: 45px;
}
#band-img-1 {
height: auto;
}
.iframe-container {
position: relative;
overflow: hidden;
width: 100%;
padding-top: 56.25%; /* 16:9 Aspect Ratio (divide 9 by 16 = 0.5625) */
}
.responsive-iframe {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
width: 100%;
height: 100%;
}
.navi-link {
color: #ff0000;
padding-left: 10px;
padding-right: 10px;
}
.navi-link:hover {
opacity: 70%;
color: #ff0000;
}
.contact-container {
color: #20d582;
background-color: #8220d5;
}
.footer-image {
width: 100%;
height: auto;
}

BIN
web/static/fonts/nabla.ttf Normal file

Binary file not shown.

BIN
web/static/fonts/virgo.ttf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
web/static/img/band_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

BIN
web/static/img/members.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

7
web/static/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
web/static/js/p5.min.js vendored Normal file

File diff suppressed because one or more lines are too long

230
web/static/js/sketch.js Normal file
View file

@ -0,0 +1,230 @@
let myFont;
function preload() {
myFont = loadFont('assets/fonts/nabla.ttf');
}
function setup() {
if (windowWidth <= 480) {
var canvas = createCanvas(windowWidth, 200);
} else {
var canvas = createCanvas(windowWidth, 300);
}
canvas.parent('shrinkin-sketch')
}
function windowResized() {
if (windowWidth <= 480) {
var canvas = resizeCanvas(windowWidth, 200);
} else {
var canvas = resizeCanvas(windowWidth, 300);
}
}
class Ball {
constructor(x, y, r) {
this.position = new p5.Vector(x, y);
this.velocity = p5.Vector.random2D();
this.velocity.mult(3);
this.r = r;
this.m = r * 0.1;
}
update() {
this.position.add(this.velocity);
}
checkBoundaryCollision() {
if (this.position.x > width - this.r) {
this.position.x = width - this.r;
this.velocity.x *= -1;
} else if (this.position.x < this.r) {
this.position.x = this.r;
this.velocity.x *= -1;
} else if (this.position.y > height - this.r) {
this.position.y = height - this.r;
this.velocity.y *= -1;
} else if (this.position.y < this.r) {
this.position.y = this.r;
this.velocity.y *= -1;
}
}
checkCollision(other) {
// Get distances between the balls components
let distanceVect = p5.Vector.sub(other.position, this.position);
// Calculate magnitude of the vector separating the balls
let distanceVectMag = distanceVect.mag();
// Minimum distance before they are touching
let minDistance = this.r + other.r;
if (distanceVectMag < minDistance) {
let distanceCorrection = (minDistance - distanceVectMag) / 2.0;
let d = distanceVect.copy();
let correctionVector = d.normalize().mult(distanceCorrection);
other.position.add(correctionVector);
this.position.sub(correctionVector);
// get angle of distanceVect
let theta = distanceVect.heading();
// precalculate trig values
let sine = sin(theta);
let cosine = cos(theta);
/* bTemp will hold rotated ball this.positions. You
just need to worry about bTemp[1] this.position*/
let bTemp = [new p5.Vector(), new p5.Vector()];
/* this ball's this.position is relative to the other
so you can use the vector between them (bVect) as the
reference point in the rotation expressions.
bTemp[0].this.position.x and bTemp[0].this.position.y will initialize
automatically to 0.0, which is what you want
since b[1] will rotate around b[0] */
bTemp[1].x = cosine * distanceVect.x + sine * distanceVect.y;
bTemp[1].y = cosine * distanceVect.y - sine * distanceVect.x;
// rotate Temporary velocities
let vTemp = [new p5.Vector(), new p5.Vector()];
vTemp[0].x = cosine * this.velocity.x + sine * this.velocity.y;
vTemp[0].y = cosine * this.velocity.y - sine * this.velocity.x;
vTemp[1].x = cosine * other.velocity.x + sine * other.velocity.y;
vTemp[1].y = cosine * other.velocity.y - sine * other.velocity.x;
/* Now that velocities are rotated, you can use 1D
conservation of momentum equations to calculate
the final this.velocity along the x-axis. */
let vFinal = [new p5.Vector(), new p5.Vector()];
// final rotated this.velocity for b[0]
vFinal[0].x =
((this.m - other.m) * vTemp[0].x + 2 * other.m * vTemp[1].x) /
(this.m + other.m);
vFinal[0].y = vTemp[0].y;
// final rotated this.velocity for b[0]
vFinal[1].x =
((other.m - this.m) * vTemp[1].x + 2 * this.m * vTemp[0].x) /
(this.m + other.m);
vFinal[1].y = vTemp[1].y;
// hack to avoid clumping
bTemp[0].x += vFinal[0].x;
bTemp[1].x += vFinal[1].x;
/* Rotate ball this.positions and velocities back
Reverse signs in trig expressions to rotate
in the opposite direction */
// rotate balls
let bFinal = [new p5.Vector(), new p5.Vector()];
bFinal[0].x = cosine * bTemp[0].x - sine * bTemp[0].y;
bFinal[0].y = cosine * bTemp[0].y + sine * bTemp[0].x;
bFinal[1].x = cosine * bTemp[1].x - sine * bTemp[1].y;
bFinal[1].y = cosine * bTemp[1].y + sine * bTemp[1].x;
// update balls to screen this.position
other.position.x = this.position.x + bFinal[1].x;
other.position.y = this.position.y + bFinal[1].y;
this.position.add(bFinal[0]);
// update velocities
this.velocity.x = cosine * vFinal[0].x - sine * vFinal[0].y;
this.velocity.y = cosine * vFinal[0].y + sine * vFinal[0].x;
other.velocity.x = cosine * vFinal[1].x - sine * vFinal[1].y;
other.velocity.y = cosine * vFinal[1].y + sine * vFinal[1].x;
}
}
display(friend1, friend2) {
this.update()
noStroke();
fill('#ff0000');
ellipse(this.position.x, this.position.y, this.r * 2, this.r * 2);
this.checkBoundaryCollision()
this.checkCollision(friend1)
this.checkCollision(friend2)
}
}
class Logo {
constructor() {
this.yOffset = 0
this.up = false
this.textSize = 36
this.bounceDepth = 10
this.speed = 0.2
}
display() {
fill('#20d582');
textFont(myFont);
if (windowWidth/6 > 120) {
textSize(120);
} else {
textSize(windowWidth/6)
}
if (windowWidth <= 480) {
text('shrinkin', width/2 - 120, 20 + 60 + this.yOffset);
text('-minkin', width/2 - 120, 100 + 60 + this.yOffset);
} else {
text('shrinkin', width/2 - 120, 50 + 90 + this.yOffset);
text('-minkin', width/2 - 120, 150 + 90 + this.yOffset);
}
this.bounce()
}
bounce() {
if (!this.up) {
if (this.yOffset <= this.bounceDepth) {
this.yOffset = this.yOffset + this.speed
}
if (this.yOffset > this.bounceDepth) {
this.up = true
}
} else {
if (this.yOffset > 0) {
this.yOffset = this.yOffset - this.speed
}
if (this.yOffset < 0) {
this.up = false
}
}
}
}
let ball1 = new Ball(100, 400, 20)
let ball2 = new Ball(700, 400, 80)
let ball3 = new Ball(0, 200, 60)
let logo = new Logo()
function draw() {
background('#209bd0');
ball1.display(ball2, ball3)
ball2.display(ball1, ball3)
ball3.display(ball1, ball2)
if (windowWidth <= 480) {
ball2.r = 40;
ball3.r = 30;
} else {
ball2.r = 80;
ball3.r = 60;
}
logo.display()
}

View file

@ -0,0 +1,227 @@
let myFont;
function preload() {
myFont = loadFont('assets/fonts/nabla.ttf');
}
function setup() {
var canvas = createCanvas(1200, 400);
canvas.parent('shrinkin-sketch')
createLoop({duration:120, gif:true})
}
function windowResized() {
if (windowWidth <= 480) {
var canvas = resizeCanvas(windowWidth, 200);
} else {
var canvas = resizeCanvas(windowWidth, 300);
}
}
class Ball {
constructor(x, y, r) {
this.position = new p5.Vector(x, y);
this.velocity = p5.Vector.random2D();
this.velocity.mult(3);
this.r = r;
this.m = r * 0.1;
}
update() {
this.position.add(this.velocity);
}
checkBoundaryCollision() {
if (this.position.x > width - this.r) {
this.position.x = width - this.r;
this.velocity.x *= -1;
} else if (this.position.x < this.r) {
this.position.x = this.r;
this.velocity.x *= -1;
} else if (this.position.y > height - this.r) {
this.position.y = height - this.r;
this.velocity.y *= -1;
} else if (this.position.y < this.r) {
this.position.y = this.r;
this.velocity.y *= -1;
}
}
checkCollision(other) {
// Get distances between the balls components
let distanceVect = p5.Vector.sub(other.position, this.position);
// Calculate magnitude of the vector separating the balls
let distanceVectMag = distanceVect.mag();
// Minimum distance before they are touching
let minDistance = this.r + other.r;
if (distanceVectMag < minDistance) {
let distanceCorrection = (minDistance - distanceVectMag) / 2.0;
let d = distanceVect.copy();
let correctionVector = d.normalize().mult(distanceCorrection);
other.position.add(correctionVector);
this.position.sub(correctionVector);
// get angle of distanceVect
let theta = distanceVect.heading();
// precalculate trig values
let sine = sin(theta);
let cosine = cos(theta);
/* bTemp will hold rotated ball this.positions. You
just need to worry about bTemp[1] this.position*/
let bTemp = [new p5.Vector(), new p5.Vector()];
/* this ball's this.position is relative to the other
so you can use the vector between them (bVect) as the
reference point in the rotation expressions.
bTemp[0].this.position.x and bTemp[0].this.position.y will initialize
automatically to 0.0, which is what you want
since b[1] will rotate around b[0] */
bTemp[1].x = cosine * distanceVect.x + sine * distanceVect.y;
bTemp[1].y = cosine * distanceVect.y - sine * distanceVect.x;
// rotate Temporary velocities
let vTemp = [new p5.Vector(), new p5.Vector()];
vTemp[0].x = cosine * this.velocity.x + sine * this.velocity.y;
vTemp[0].y = cosine * this.velocity.y - sine * this.velocity.x;
vTemp[1].x = cosine * other.velocity.x + sine * other.velocity.y;
vTemp[1].y = cosine * other.velocity.y - sine * other.velocity.x;
/* Now that velocities are rotated, you can use 1D
conservation of momentum equations to calculate
the final this.velocity along the x-axis. */
let vFinal = [new p5.Vector(), new p5.Vector()];
// final rotated this.velocity for b[0]
vFinal[0].x =
((this.m - other.m) * vTemp[0].x + 2 * other.m * vTemp[1].x) /
(this.m + other.m);
vFinal[0].y = vTemp[0].y;
// final rotated this.velocity for b[0]
vFinal[1].x =
((other.m - this.m) * vTemp[1].x + 2 * this.m * vTemp[0].x) /
(this.m + other.m);
vFinal[1].y = vTemp[1].y;
// hack to avoid clumping
bTemp[0].x += vFinal[0].x;
bTemp[1].x += vFinal[1].x;
/* Rotate ball this.positions and velocities back
Reverse signs in trig expressions to rotate
in the opposite direction */
// rotate balls
let bFinal = [new p5.Vector(), new p5.Vector()];
bFinal[0].x = cosine * bTemp[0].x - sine * bTemp[0].y;
bFinal[0].y = cosine * bTemp[0].y + sine * bTemp[0].x;
bFinal[1].x = cosine * bTemp[1].x - sine * bTemp[1].y;
bFinal[1].y = cosine * bTemp[1].y + sine * bTemp[1].x;
// update balls to screen this.position
other.position.x = this.position.x + bFinal[1].x;
other.position.y = this.position.y + bFinal[1].y;
this.position.add(bFinal[0]);
// update velocities
this.velocity.x = cosine * vFinal[0].x - sine * vFinal[0].y;
this.velocity.y = cosine * vFinal[0].y + sine * vFinal[0].x;
other.velocity.x = cosine * vFinal[1].x - sine * vFinal[1].y;
other.velocity.y = cosine * vFinal[1].y + sine * vFinal[1].x;
}
}
display(friend1, friend2) {
this.update()
noStroke();
fill('#ff0000');
ellipse(this.position.x, this.position.y, this.r * 2, this.r * 2);
this.checkBoundaryCollision()
this.checkCollision(friend1)
this.checkCollision(friend2)
}
}
class Logo {
constructor() {
this.yOffset = 0
this.up = false
this.textSize = 36
this.bounceDepth = 10
this.speed = 0.2
}
display() {
fill('#20d582');
textFont(myFont);
if (windowWidth/6 > 120) {
textSize(120);
} else {
textSize(windowWidth/6)
}
if (windowWidth <= 480) {
text('shrinkin', width/2 - 120, 20 + 60 + this.yOffset);
text('-minkin', width/2 - 120, 100 + 60 + this.yOffset);
} else {
text('shrinkin', width/2 - 120, 50 + 90 + this.yOffset);
text('-minkin', width/2 - 120, 150 + 90 + this.yOffset);
}
this.bounce()
}
bounce() {
if (!this.up) {
if (this.yOffset <= this.bounceDepth) {
this.yOffset = this.yOffset + this.speed
}
if (this.yOffset > this.bounceDepth) {
this.up = true
}
} else {
if (this.yOffset > 0) {
this.yOffset = this.yOffset - this.speed
}
if (this.yOffset < 0) {
this.up = false
}
}
}
}
let ball1 = new Ball(100, 400, 20)
let ball2 = new Ball(700, 400, 80)
let ball3 = new Ball(0, 200, 60)
let logo = new Logo()
function draw() {
background('#209bd0');
ball1.display(ball2, ball3)
ball2.display(ball1, ball3)
ball3.display(ball1, ball2)
if (windowWidth <= 480) {
ball2.r = 40;
ball3.r = 30;
} else {
ball2.r = 80;
ball3.r = 60;
}
logo.display()
}

41
web/templates/album.html Normal file
View file

@ -0,0 +1,41 @@
{% extends "base.html" %}
{% load static %}
{% block 'content' %}
<div class="container">
<center>
<br>
<br>
<h5>Listen to our first album Tyrannizing Harmonics Everywhere!!</h5>
<br>
<div style="margin: 0 auto; max-width: 600px;">
<iframe style="border: 0; width: 100%; height: 439px;" src="https://bandcamp.com/EmbeddedPlayer/album=4037494074/size=large/bgcol=ffffff/linkcol=0687f5/artwork=small/transparent=true/" seamless><a href="https://shrinkin-minkin.bandcamp.com/album/tyrannizing-harmonics">Tyrannizing Harmonics by shrinkin-minkin</a></iframe>
</div>
<a target="_blank" href="https://open.spotify.com/album/4imchLIIQnfFfrxcy0bQ3l">
<button type="button" class="btn btn-light link">Spotify</button><br>
</a>
<a target="_blank" href="https://shrinkin-minkin.bandcamp.com/album/tyrannizing-harmonics">
<button type="button" class="btn btn-light link">Bandcamp</button><br>
</a>
<a target="_blank" href="https://www.youtube.com/watch?v=hwaWr7GQ09M&list=OLAK5uy_mr2CAlUomJZIEd3Y0_vIiguRz80bXNmwI">
<button type="button" class="btn btn-light link">YouTube</button><br>
</a>
<a target="_blank" href="https://music.apple.com/us/album/tyrannizing-harmonics/1814317803" style="display:none;">
<button type="button" class="btn btn-light link">Apple Music</button><br>
</a>
<a target="_blank" href="https://www.pandora.com/artist/shrinkin-minkin/tyrannizing-harmonics/AL3j4zhrhJbf4cP">
<button type="button" class="btn btn-light link">Pandora</button><br>
</a>
<a target="_blank" href="https://www.instagram.com/shrinkinminkin/">
<button type="button" class="btn btn-light link">Follow us on Instagram</button><br>
</a>
<br>
{% include 'mailing_list.html' %}
We hope you enjoy our ad free listening experience! Please visit our socials and like and subscribe to support us!
</center>
<br><br>
</div>
{% endblock %}

45
web/templates/base.html Normal file
View file

@ -0,0 +1,45 @@
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>Shrinkin-Minkin</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body style="padding: 0px; margin: 0px;">
{% if messages %}
{% for message in messages %}
<div class="alert alert-success alert-dismissible fade show"
role="alert"
style="max-width:400px;
position: fixed;
bottom:0;
right: 0;
margin:20px">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">X</span>
</button>
</div>
{% endfor %}
{% endif %}
<center>
<a href="{% url 'index' %}">
<img src="{% static 'img/album_release_header.png' %}"
alt=""
class="header-img">
<br>
</a>
</center>
{% block 'content' %}{% endblock %}
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin="anonymous"></script>
</body>

123
web/templates/index.html Normal file
View file

@ -0,0 +1,123 @@
{% extends "base.html" %}
{% load static %}
{% block 'content' %}
<div>
<center>
<a href="#listen">
<button type="button" class="btn btn-light nav-link">listen</button>
</a>
<a href="#watch">
<button type="button" class="btn btn-light nav-link">watch</button>
</a>
<a href="#book">
<button type="button" class="btn btn-light nav-link">book us</button>
</a>
<a href="{% url 'socials' %}">
<button type="button" class="btn btn-light nav-link">social</button>
</a>
<br>
</center>
</div>
<div class="container-fluid pt-2 mt-3">
<div class="row pb-4">
<div class="col-lg-6">
<center>
<img id="band-img-1" class="img-fluid" src="{% static 'img/members.png' %}" alt="" style="margin-bottom: 20px; width:100%; height: auto; max-width: 840px;">
<div style="margin-top:30px; margin-bottom: 20px;">
{% include 'mailing_list.html' %}
</div>
</center>
</div>
<div class="col-lg-6" style="padding:30px; background-color: #54b879;">
<h1>About Us:</h1>
<p>
<b>SHRINKIN-MINKIN</b> thrives on the fringes of genre, violating the confines of classification.
Founded in 2022 by singer/guitarists Dominic DiTaranto and Michael Heinfling;
SHRINKIN-MINKIN blends Rock, Funk, Jazz, Punk, infused with creative improvisation and FX landscape.
The NJ based 5-piece is <i>tight</i>. Technical, Playful, and dripping with thirst-quenching satire.
</p>
<p>
Their musical multiplicity allows SHRINKIN-MINKIN to fit into any lineup.
Their songs range from the Funk, Rock, Hip Hop, Gospel fusion of "GLOP";
to Post-Punk anthems like "Industry Rock" and the comical love-lament of "Butter Queen".
Songs that are guaranteed to get asses convulsing in rhythmic fervor.
More discerning listeners can relax and watch the band trade solos on "Down Polypore Wood".
Add in Alternative ballads, Blues tracks, and even a Psychedelic-Waltz and you will begin to scratch
the surface of what SHRINKIN-MINKIN has to offer.
<br><br>
<a href="#book">Book Us</a> now to support or headline!
</p>
<p>
<b>Members:</b>
<br>Dominic DiTaranto - Guitar/Vocals/Synth
<br>Michael Heinfling - Guitar/Vocals
<br>Charlie Misch - Keyboard/Synth
<br>B.S. Ashford - Bass
<br>Recon Paul - Drums
</p>
<p>
<b>Recommended if you like:</b>
Frank Zappa,
King Crimson,
Talking Heads,
David Bowie,
Mahavishnu Orchestra,
Phish,
Miles Davis,
The Stooges,
Funkadelic,
John Coltrane,
The Stranglers,
The Damned,
Weather Report,
The Cars
</p>
</div>
</div>
<h1 id="listen">Listen:</h1>
<center>
<iframe style="border: 0; max-width: 350px; height: 753px;" src="https://bandcamp.com/EmbeddedPlayer/album=4037494074/size=large/bgcol=ffffff/linkcol=de270f/transparent=true/" seamless><a href="https://shrinkin-minkin.bandcamp.com/album/tyrannizing-harmonics">Tyrannizing Harmonics by shrinkin-minkin</a></iframe>
</center>
<br><br>
<h1 id="watch">Watch Us Live:</h1>
<div class="iframe-container">
<iframe class="responsive-iframe" src="https://www.youtube.com/embed/7Dbg5MuOgbY?si=qW3dw8QY4a8sHqbX" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
</div>
<br><br>
<div class="iframe-container">
<iframe class="responsive-iframe" src="https://www.youtube.com/embed/4Cx1_LMJRzM?si=fS9EuyczsNvsJuSb&amp;start=42" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
</div>
<br><br>
<div class="iframe-container">
<iframe class="responsive-iframe" src="https://www.youtube.com/embed/R3fo-k2_wfM?si=Foz0OaTK6GC5psKo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
</div>
<div class="mt-5 mb-5 p-5">
<h1 id="book">Book Us:</h1>
<br>
Email: shrinkinminkin<span style="font-family: monospace;">@</span>gmail.com <br>
Phone: 732-895-5253
<br><br>
</div>
</div>
<div>
<img class="footer-image" src="{% static 'img/bugman_sticker.png' %}" alt="">
</div>
{% endblock %}

View file

@ -0,0 +1,8 @@
<div class="card p-2" style="margin-bottom:10px;">
<form action="/mailing_list/" method="post">
{% csrf_token %}
<label for="email">Join Our Mailing List:</label>
<input type="email" id="email" name="email" placeholder="your@email.com" style="font-family: monospace;">
<input type="submit" class="btn btn-success" value="JOIN">
</form>
</div>

View file

@ -0,0 +1,44 @@
{% extends "base.html" %}
{% load static %}
{% block 'content' %}
<div class="container">
<center>
<br>
<br>
<iframe style="border: 0;
width: 100%;
height: 42px"
src="https://bandcamp.com/EmbeddedPlayer/album=4037494074/size=small/bgcol=ffffff/linkcol=0687f5/transparent=true/"
seamless><a href="https://shrinkin-minkin.bandcamp.com/album/tyrannizing-harmonics">Tyrannizing Harmonics by shrinkin-minkin</a></iframe>
<a target="_blank" href="{% url 'album' %}">
<button type="button" class="btn btn-success link">Listen To Our Album!</button>
<br>
</a>
<a target="_blank" href="https://www.instagram.com/shrinkinminkin/">
<button type="button" class="btn btn-light link">Instagram</button>
<br>
</a>
<a target="_blank" href="https://shrinkin-minkin.bandcamp.com/">
style="margin-bottom:10px;" <button type="button" class="btn btn-light link">Bandcamp</button>
<br>
</a>
<a target="_blank"
href="https://www.youtube.com/channel/UCCmRKeoqtHC9G1X-yvuUG7A">
<button type="button" class="btn btn-light link">YouTube</button>
<br>
</a>
<a target="_blank" href="https://www.shrinkinminkin.com/">
<button type="button" class="btn btn-light link">For Promoters And Venues</button>
<br>
</a>
<a target="_blank" href="https://www.bonfire.com/store/shrinkin-minkin/">
<button type="button" class="btn btn-danger link">Merch</button>
<br>
</a>
<br>
<br>
</center>
<br>
<br>
</div>
{% endblock %}

3
web/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

33
web/views.py Normal file
View file

@ -0,0 +1,33 @@
from django.contrib import messages
from django.shortcuts import render, redirect
from web.models import MailingList
def index(request):
return render(request, 'index.html')
def album(request):
return render(request, 'album.html')
def socials(request):
return render(request, 'socials.html')
def mailing_list(request):
if request.method == 'POST':
referer = request.META['HTTP_REFERER'][:-1].split('/')[-1].split('.')[0]
email = request.POST.get('email')
if email:
exists = MailingList.objects.filter(email=email).first()
if not exists:
m = MailingList(
email=email
)
m.save()
messages.add_message(request, messages.INFO, "Nice! Thanks for joining our mailing list!")
return redirect(referer)