Compare commits
1 Commits
main
...
ynh_packag
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b1fde020a |
13
.gitignore
vendored
@ -1,3 +1,10 @@
|
|||||||
TODO.md
|
.cache/
|
||||||
clean-after-make_readme.sh
|
.parcel-cache/
|
||||||
*.geany
|
package-lock.json
|
||||||
|
.eslintrc.json
|
||||||
|
.parcelrc
|
||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
hint-report/
|
||||||
|
.csslintrc
|
||||||
|
coverage/
|
||||||
|
|||||||
23
README.md
@ -1,22 +1,3 @@
|
|||||||
# SR2 Initiative Tracker for YunoHost
|
# sr2ini
|
||||||
|
|
||||||
## Overview
|
Simple Initiative tracker for Shadowrun 2e.
|
||||||
|
|
||||||
sr2ini is a lightweight, single-page initiative tracker for the TTRPG Shadowrun in its 2nd edition. The app helps DMs and players to manage fights by tracking each combatant's initiative, order of action, damage (stun and physical), and wound modifiers. It was written specifically for mobile use and can be installed as a web app.
|
|
||||||
|
|
||||||
This package allows you to install sr2ini quickly and simply on a YunoHost server. If you don't have YunoHost, please consult the guide to learn how to install it.
|
|
||||||
|
|
||||||
## YunoHost specific features
|
|
||||||
|
|
||||||
* supports multi-install
|
|
||||||
* defaut visibility is public
|
|
||||||
|
|
||||||
**Shipped version:** 0.9.5~ynh1
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Documentation and resources
|
|
||||||
|
|
||||||
* App source: <https://git.unterdemradar.de/tobias/sr2ini>
|
|
||||||
118
TODO.md
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# sr2ini: Bugs and Features Tracker
|
||||||
|
|
||||||
|
## Bugs
|
||||||
|
|
||||||
|
- x Bug: KO/dead buttons fkt. nicht
|
||||||
|
- applyDamage, ersetze erste Zeile: let $btn = $(e.target).is("button") ? $(e.target) : $(e.target).parents("button")[0];
|
||||||
|
- x Bug: Kann nicht weit genug runterscrollen für remove bzw die unteren schadenskastchen
|
||||||
|
- Footer hatte viel zu großen z-index
|
||||||
|
- x Bug: resort after remove -> einfach eingefügt
|
||||||
|
- x background-image wiederholt sich -> hat sich nach Redesign erledigt
|
||||||
|
- x Habe alle $("…").modal("hide|show") eliminiert; die Sichtbarkeit der Modals wird jetzt ausschließlich durch data-bs-Attribute gesteuert
|
||||||
|
- x add-Button leert die inputs vom Modal nicht mehr
|
||||||
|
- event hidden.bs.modal feuert nicht mehr
|
||||||
|
- zuerst dachte ich, es liegt daran, dass ich das bootstrap js zu Klasse Modal nicht laden konnte. Mittlerweile habe ich es hingekriegr, doch das event feuert trotzdem nicht
|
||||||
|
- hab's hingekriegt, indem ich jquery und bootstrap nun doch wieder in der index.html lade und nicht mehr als import.
|
||||||
|
- x in großer Darstellung bleiben u.U. links und rechts Ränder frei, wo das Hintergrundbild nicht skaliert wird -> background-size: cover
|
||||||
|
- x bei Benutzung von augmented-ui: der Text von .combatant-name ist nach oben verschoben und links+rechts abgeschnitten
|
||||||
|
- grundlegendes Problem: augmented-ui nutzt ::before und ::after, um die Styles zu erzeugen; mein eigenes ::before kollidiert damit
|
||||||
|
- füge den .combatant-name jetzt wieder direkt ein, nicht mehr per Datenattribut
|
||||||
|
- x damit nirgendwo ein Stück border fehlt, müssen die Ecken von zwei benachbarten combatant tablerows nicht gegeneinander verschoben sein
|
||||||
|
- d.h. die beiden linken und die beiden rechten Ecken einer Tablerow müssen jeweils die gleiche X-Position haben
|
||||||
|
- x alles, was vom damage monitor über den unteren Tabellenrand rüberragt, wird abgeschnitten
|
||||||
|
- musste den clip-path von tr und td auf none setzen
|
||||||
|
- x jetzt kann ich den damage-monitor zwar aufklappen, aber die Buttons der weiter unten liegenden combatant-rows überdecken ihn jedesmal.
|
||||||
|
- x wenn ich einen Damage-Button anklicke, beginnt die Transition des damage-monitor von Neuem
|
||||||
|
- x wenn die Navbar borders hat, sollte sie nicht direkt am oberen Bildrand anfangen, sondern ein paar Pixel weiter unten
|
||||||
|
- x Unterschied zwischen enabled und disabled button nicht gut erkennbar
|
||||||
|
- x Output vom HTML Validator:
|
||||||
|
- img elements müssen alt attributes haben
|
||||||
|
- The form attribute must refer to a form element. (bei den Input Elementen im combatant-modal) -> muss nachsehen, was ich dann genau da eintragen muss
|
||||||
|
- x minusten.svg benutzt die falsche Schriftart
|
||||||
|
- x manchmal spinnt das actions-menu, dann taucht es einfach nicht mehr auf
|
||||||
|
- Bsp: more -> clone -> cancel
|
||||||
|
- x derzeit werden die icons im actions-menu nicht dunkler gemacht (.zero-ini o.Ä.), weil ich die img-Rules aus dem CSS rausgenommen habe
|
||||||
|
- ich könnte die Rules wieder reinnehmen
|
||||||
|
- x oder die Icons mit SVG nachbauen (edit und trash)
|
||||||
|
- x damage-monitor und actions-menu überlappen sich; die oberen drei phys-dmg-buttons lösen edit/clone/remove aus
|
||||||
|
- x bei tot/KO wird die Klasse ko-or-dead nicht gesetzt
|
||||||
|
- es werden gar keine contextual classes gesetzt
|
||||||
|
- musste die entspr. arrow function in sortTable() wieder zu einer regular function machen
|
||||||
|
- x combatant modal hat auf einmal zwei OK buttons
|
||||||
|
- Fehler beim sichtbar/unsichtbar-Setzen der modal buttons in handleEditButtonClick()
|
||||||
|
|
||||||
|
- x Änderungen im edit modal werden nicht mehr übernommen
|
||||||
|
- war ein fehlendes let in editCombatant() -> wo das nur wieder herkam …
|
||||||
|
- im FP3T Tor Browser kann ich rauszoomen, bis ich die ganzen damage monitors und action menus sehe -> verhindern!
|
||||||
|
- bug in validateCombatant: OK schließt das modal, auch wenn die Eingaben invalid sind
|
||||||
|
- wenn ich bei add combatant mit der Maus auf OK klicke, macht er das Modal zu, auch wenn die Eingaben invalid sind
|
||||||
|
- nach dem Laden passiert es manchmal, dass nach dem Einfügen von testCombatant das add Modal gleich wieder aufgeht
|
||||||
|
- schien die gleiche Sache zu sein wie mit dem hidden.bs.modal event
|
||||||
|
- jetzt kommt es aber trotzdem manchmal wieder
|
||||||
|
- vllt. ein timeout-Problem?
|
||||||
|
- add modal geht bei enter key nicht zu -> liegt daran, dass ich nicht mehr .modal("hide") verwende
|
||||||
|
- ich könnt's umstellen, aber will ich das?
|
||||||
|
- wenn ein damage monitor offen ist und ich auf add combatant clicke, springt der Fokus nicht zuverlässig ins erste input feld
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Feature Requests
|
||||||
|
|
||||||
|
- x nochmal wg. Daten wie name, dice, rea, true-ini und damage-x:
|
||||||
|
- Verwalte sie jetzt komplett mit der data-* API; verwende dafür ausschließlich .attr() als Getter/Setter
|
||||||
|
- Füge die Werte aus dem Attribut per CSS direkt ins Element ein (::after und content).
|
||||||
|
- x prettify code: alle HTML class names von camelCale zu dash-case komvertieren
|
||||||
|
- x Design cyberpunkig machen
|
||||||
|
- x im modal soll man die damage levels einstellen/verändern können
|
||||||
|
- x Im modal, wenn ich die wound penalties anzeige, die Fälle KO und Tod gesondert behandeln
|
||||||
|
- x clone button
|
||||||
|
- x und im combatant-modal ein weiterer OK-Button, der das Modal offenlässt
|
||||||
|
- x Design: favicon
|
||||||
|
- x imput[type=range] schicker machen
|
||||||
|
- x input elements styling anpassen für :focus. :focus-visible, :valid, :invalid
|
||||||
|
- x contextual classes hübsch machen
|
||||||
|
- x neue Icons
|
||||||
|
- x dmg-mon/actions-menu: tabindex auf -1 setzen, wenn nicht sichtbar
|
||||||
|
- x dmg-mon/actions-menu direkt unter dem jeweiligen Button ausrichten (X-Achse)
|
||||||
|
- ich richte die Buttons jetzt rechtsbündig aus, dann weiß ich immer die (ungefähre) Position des Buttons
|
||||||
|
- x SVG Icons alle in eine externe Datei als <symbol>s mit ID packen. Diese kann ich dann in anderen Files referenzieren: <svg><use href="icons.svg#icon1" /></svg>
|
||||||
|
- https://stackoverflow.com/questions/34225008/how-to-reuse-an-embedded-svg-element-in-the-same-page
|
||||||
|
- leider geht das nicht: addCombatant() fügt HTML dynamisch ins Dokument ein, und wenn in diesem HTML SVG enthalten ist, werden die <use>-Elemente darin nicht ausgewertet (also duch das referenzierte <symbol> ersetzt). Das SVG wird daher nicht angezeigt.
|
||||||
|
- um das zu beheben. müsste ich die jQuery-Methode parseHTML() modifizieren und alle Aufrufe von createElement() durch createElementNS() ersetzen
|
||||||
|
- aber am Ende bringt das gar nichts, weil der Browser bei jedem <use> den Inhalt des <symbol>s aufs Neue ins DOM kopiert. Die HTML-Platzersparnis wäre also gleich Null.
|
||||||
|
- x kann die classes text-center, text-end etc aus den Tabellenzellen entfernen
|
||||||
|
- x CSS: maroon durch b3005f ersetzen
|
||||||
|
- x anonymous functions wo möglich durch arrow functions ersetzen
|
||||||
|
- x auf bootstrap.js verzichten?
|
||||||
|
- zumindest modal.js muss ich einzeln laden
|
||||||
|
- aber sonst brauch ich es glaube ich nicht
|
||||||
|
- x dependencies lokal einbinden
|
||||||
|
- x progressive web app
|
||||||
|
- x Service Worker einrichten, um die Dateien lokal zu cachen
|
||||||
|
- x lief nicht mit Parcel pur, brauchte Paket "serve" -> npx serve dist/
|
||||||
|
-> mittlerweile geht's doch
|
||||||
|
- x parcel bindet sw.js nicht automatisch mit ein, ich muss es nach dist/ hardverlinken
|
||||||
|
- und die File-list in sw.js darf nur Files enthalten, die auch geladen werden können; sobald einer 404 ergibt, schlägt der gesamte Cache-Vorgang fehl
|
||||||
|
- geht
|
||||||
|
- x dafür sorgen, dass die Seite erst dann aufgebaut wird, wenn die CSS-Files geladen sind, damit man nicht den ungestylten Krams sieht -> passt schon
|
||||||
|
- x CSS aufräumen
|
||||||
|
- Variablen für Farben, Filter etc.
|
||||||
|
|
||||||
|
- installability (PWA) auf android noch ungetestet
|
||||||
|
- Seite auch mal im Chrome checken
|
||||||
|
- -moz-… mit -webkit-… ergänzen
|
||||||
|
- noch mehr Design
|
||||||
|
- Seite für größere Screens anpassen
|
||||||
|
- Schrift, Buttons, Icons skalieren
|
||||||
|
- em und % statt px
|
||||||
|
- Tabellenbreite begrenzen
|
||||||
|
- Animationen? Transitions?
|
||||||
|
- deployment: dist/* soll direkt auf hermes hochgeladen werden
|
||||||
|
- warum sind im dist/-Folder immer zwei Versionen der gleichen Datei?
|
||||||
|
- HTML soll nicht in eine Zeile umgedingst werden, das sieht doch nicht aus
|
||||||
|
- bootstrap, jquery, font auch lokal vorhalten
|
||||||
|
- docstrings
|
||||||
|
- parcel soll aus dem HTML code nicht die Newlines rausnehmen -> macht er das überhaupt noch?
|
||||||
|
- falls ja: .htmlnanorc anlegen, s. https://parceljs.org/languages/html/#minification und https://htmlnano.netlify.app/modules#collapsewhitespace
|
||||||
|
- nicetohave: Wenn ich rea editiere, könnte sich die ini automatisch anpassen -> da müsste ich aber die Würfelergebnisse für speichern
|
||||||
|
- nicetohave: Anzeige, wieviele Aktionen einer hat u.d wieviele davon schon verbraucht sind
|
||||||
16
__tests__/sr2ini.test.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const s = require("../src/js/sr2ini.js");
|
||||||
|
|
||||||
|
describe ("test function rollForInitiative()", () => {
|
||||||
|
test("function should accept numbes as integers as well as strings", () => {
|
||||||
|
expect(typeof s.rollForInitiative("1", "5")).toBe("number");
|
||||||
|
expect(typeof s.rollForInitiative(1, 5)).toBe("number");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("result should be greater than zero", () => {
|
||||||
|
expect(s.rollForInitiative("1", "5")).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return zero for bad parameters", () => {
|
||||||
|
expect(s.rollForInitiative("a", 2)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,11 +0,0 @@
|
|||||||
#sub_path_only rewrite ^__PATH__$ __PATH__/ permanent;
|
|
||||||
location __PATH__/ {
|
|
||||||
|
|
||||||
# Path to source
|
|
||||||
alias __INSTALL_DIR__/;
|
|
||||||
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# Include SSOWAT user panel
|
|
||||||
# include conf.d/yunohost_panel.conf.inc;
|
|
||||||
}
|
|
||||||
BIN
dist/sr2ini_ynh-0.1~ynh1.tar.gz
vendored
@ -1,8 +0,0 @@
|
|||||||
sr2ini is a lightweight, single-page initiative tracker for the TTRPG Shadowrun in its 2nd edition. The app helps DMs and players to manage fights by tracking each combatant's initiative, order of action, damage (stun and physical), and wound modifiers. It was written specifically for mobile use and can be installed as a web app.
|
|
||||||
|
|
||||||
This package allows you to install sr2ini quickly and simply on a YunoHost server. If you don't have YunoHost, please consult the guide to learn how to install it.
|
|
||||||
|
|
||||||
## YunoHost specific features
|
|
||||||
|
|
||||||
* supports multi-install
|
|
||||||
* defaut visibility is public
|
|
||||||
|
Before Width: | Height: | Size: 280 KiB |
BIN
icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
icons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
9
icons/browserconfig.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/icons/mstile-150x150.png"/>
|
||||||
|
<TileColor>#004aa5</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
||||||
BIN
icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
icons/favicon.ico
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
22
icons/sr2ini.webmanifest
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "Shadowrun 2e Ini Tracker",
|
||||||
|
"short_name": "sr2ini",
|
||||||
|
"start_url": "https://unterdemradar.de/sr2ini",
|
||||||
|
"description": "A simple Initiative tracker for Shadowrun 2e",
|
||||||
|
"orientation": "portrait",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/icons/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icons/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "deeppink",
|
||||||
|
"background_color": "#004aa5",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
@ -1,51 +0,0 @@
|
|||||||
packaging_format = 2
|
|
||||||
|
|
||||||
id = "sr2ini"
|
|
||||||
name = "SR2 Initiative Tracker"
|
|
||||||
description.en = "A simple initiative tracker for Shaodwrun 2e."
|
|
||||||
description.de = "Ein einfacher Initiative-Tracker für Shaodwrun, 2. Edition."
|
|
||||||
|
|
||||||
version = "0.9.5~ynh1"
|
|
||||||
|
|
||||||
maintainers = ["Eclipse729"]
|
|
||||||
|
|
||||||
[upstream]
|
|
||||||
license = "ISC"
|
|
||||||
|
|
||||||
[integration]
|
|
||||||
yunohost = ">= 11.1"
|
|
||||||
architectures = "all"
|
|
||||||
multi_instance = true
|
|
||||||
ldap = "not_relevant"
|
|
||||||
sso = "not_relevant"
|
|
||||||
disk = "1M"
|
|
||||||
ram.build = "1M"
|
|
||||||
ram.runtime = "1M"
|
|
||||||
|
|
||||||
[install]
|
|
||||||
[install.domain]
|
|
||||||
# this is a generic question - ask strings are automatically handled by Yunohost's core
|
|
||||||
type = "domain"
|
|
||||||
|
|
||||||
[install.path]
|
|
||||||
# this is a generic question - ask strings are automatically handled by Yunohost's core
|
|
||||||
type = "path"
|
|
||||||
default = "/sr2ini"
|
|
||||||
|
|
||||||
[install.init_main_permission]
|
|
||||||
type = "group"
|
|
||||||
default = "visitors"
|
|
||||||
|
|
||||||
[resources]
|
|
||||||
[resources.sources.main]
|
|
||||||
url = "https://git.unterdemradar.de/tobias/sr2ini/releases/download/0.9.5/sr2ini-0.9.5.tar.gz"
|
|
||||||
sha256 = "7dd22080f8768eeda1b080e293f8646a33f60d9b9bd2352c3de572efd454064b"
|
|
||||||
format = "tar.gz"
|
|
||||||
in_subdir = false
|
|
||||||
|
|
||||||
[resources.system_user]
|
|
||||||
|
|
||||||
[resources.install_dir]
|
|
||||||
|
|
||||||
[resources.permissions]
|
|
||||||
main.url = "/"
|
|
||||||
58
package.json
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"name": "sr2ini",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Simple Initiative tracker for Shadowrun 2e",
|
||||||
|
"private": true,
|
||||||
|
"author": {
|
||||||
|
"name": "Eclipse729",
|
||||||
|
"email": "eclipse@unterdemradar.de",
|
||||||
|
"url": "https://unterdemradar.de"
|
||||||
|
},
|
||||||
|
"homepage": "https://unterdemradar.de/sr2ini/",
|
||||||
|
"license": "ISC",
|
||||||
|
"source": "src/index.html",
|
||||||
|
"devDependencies": {
|
||||||
|
"@parcel/packager-raw-url": "^2.8.3",
|
||||||
|
"@parcel/packager-xml": "^2.8.3",
|
||||||
|
"@parcel/transformer-sass": "^2.8.3",
|
||||||
|
"@parcel/transformer-webmanifest": "^2.8.3",
|
||||||
|
"@parcel/transformer-xml": "^2.8.3",
|
||||||
|
"hint": "^7.1.3",
|
||||||
|
"jest": "^29.4.3",
|
||||||
|
"jest-environment-jsdom": "^29.4.3",
|
||||||
|
"jsdom": "^21.1.0",
|
||||||
|
"parcel": "^2.8.3",
|
||||||
|
"parcel-reporter-static-files-copy": "^1.5.0"
|
||||||
|
},
|
||||||
|
"staticFiles": {
|
||||||
|
"staticPath": "src/img"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "npx parcel serve src/index.html --public-url / --dist-dir dist",
|
||||||
|
"prebuild": "rm -rf dist/",
|
||||||
|
"build": "npx parcel build --no-optimize --public-url ./",
|
||||||
|
"test": "jest --coverage --env=jsdom",
|
||||||
|
"webhint": "hint http://localhost:1234"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "gitea@git.unterdemradar.de:tobias/sr2ini.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Shadowrun",
|
||||||
|
"Initiative tracker",
|
||||||
|
"sr2e"
|
||||||
|
],
|
||||||
|
"jest": {
|
||||||
|
"setupFiles": [
|
||||||
|
"./tools/setup-jest.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@parcel/service-worker": "^2.8.3",
|
||||||
|
"augmented-ui": "^2.0.0",
|
||||||
|
"bootstrap": "^5.2.3",
|
||||||
|
"jquery": "^3.6.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,32 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# GENERIC START
|
|
||||||
#=================================================
|
|
||||||
# IMPORT GENERIC HELPERS
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
source /usr/share/yunohost/helpers
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# DECLARE DATA AND CONF FILES TO BACKUP
|
|
||||||
#=================================================
|
|
||||||
ynh_print_info --message="Declaring files to be backed up..."
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# BACKUP THE APP MAIN DIR
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
ynh_backup --src_path="$install_dir"
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# BACKUP THE NGINX CONFIGURATION
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
ynh_backup --src_path="/etc/nginx/conf.d/$domain.d/$app.conf"
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# END OF SCRIPT
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
ynh_print_info --message="Backup script completed for $app. (YunoHost will then actually copy those files to the archive)."
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# GENERIC STARTING
|
|
||||||
#=================================================
|
|
||||||
# IMPORT GENERIC HELPERS
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
source /usr/share/yunohost/helpers
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# STANDARD MODIFICATIONS
|
|
||||||
#=================================================
|
|
||||||
# MODIFY URL IN NGINX CONF
|
|
||||||
#=================================================
|
|
||||||
ynh_script_progression --message="Updating NGINX web server configuration..." --weight=2
|
|
||||||
|
|
||||||
ynh_change_url_nginx_config
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# END OF SCRIPT
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
ynh_script_progression --message="Change of URL completed for $app" --last
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source /usr/share/yunohost/helpers
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# DOWNLOAD, CHECK AND UNPACK SOURCE
|
|
||||||
#=================================================
|
|
||||||
ynh_script_progression --message="Setting up source files…" --weight=1
|
|
||||||
|
|
||||||
ynh_setup_source --dest_dir="$install_dir"
|
|
||||||
|
|
||||||
chown -R $app:www-data "$install_dir"
|
|
||||||
chmod -R o-rwx "$install_dir"
|
|
||||||
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# WEBSERVER CONFIGURATION
|
|
||||||
#=================================================
|
|
||||||
ynh_script_progression --message="Adding nginx configuration …" --weight=1
|
|
||||||
|
|
||||||
ynh_add_nginx_config
|
|
||||||
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source /usr/share/yunohost/helpers
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# REMOVE THE NGINX CONFIGURATION
|
|
||||||
#=================================================
|
|
||||||
ynh_script_progression --message="Removing nginx web server configuration..." --weight=1
|
|
||||||
|
|
||||||
ynh_remove_nginx_config
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# GENERIC START
|
|
||||||
#=================================================
|
|
||||||
# IMPORT GENERIC HELPERS
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
source /usr/share/yunohost/helpers
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# RESTORE THE APP MAIN DIR
|
|
||||||
#=================================================
|
|
||||||
ynh_script_progression --message="Restoring the app main directory..." --weight=1
|
|
||||||
|
|
||||||
ynh_restore_file --origin_path="$install_dir"
|
|
||||||
|
|
||||||
# Restore permissions on app files
|
|
||||||
chown -R $app:www-data $install_dir
|
|
||||||
chmod -R o-rwx $install_dir
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# RESTORE THE NGINX CONFIGURATION
|
|
||||||
#=================================================
|
|
||||||
ynh_script_progression --message="Restoring the NGINX configuration..." --weight=1
|
|
||||||
|
|
||||||
ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf"
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# RELOAD NGINX
|
|
||||||
#=================================================
|
|
||||||
ynh_script_progression --message="Reloading NGINX web server..." --weight=2
|
|
||||||
|
|
||||||
ynh_systemd_action --service_name=nginx --action=reload
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# END OF SCRIPT
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
ynh_script_progression --message="Restoration completed for $app" --last
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# GENERIC START
|
|
||||||
#=================================================
|
|
||||||
# IMPORT GENERIC HELPERS
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
source /usr/share/yunohost/helpers
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# CHECK VERSION
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
upgrade_type=$(ynh_check_app_version_changed)
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# STANDARD UPGRADE STEPS
|
|
||||||
#=================================================
|
|
||||||
# DOWNLOAD, CHECK AND UNPACK SOURCE
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
if [ "$upgrade_type" == "UPGRADE_APP" ]
|
|
||||||
then
|
|
||||||
ynh_script_progression --message="Upgrading source files …" --weight=2
|
|
||||||
|
|
||||||
# Download, check integrity, uncompress and patch the source from app.src
|
|
||||||
ynh_setup_source --dest_dir="$install_dir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
chown -R $app:www-data "$install_dir"
|
|
||||||
chmod -R o-rwx "$install_dir"
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# REAPPLY SYSTEM CONFIGURATIONS
|
|
||||||
#=================================================
|
|
||||||
ynh_script_progression --message="Upgrading nginx configuration …" --weight=1
|
|
||||||
|
|
||||||
ynh_add_nginx_config
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#=================================================
|
|
||||||
# END OF SCRIPT
|
|
||||||
#=================================================
|
|
||||||
|
|
||||||
ynh_script_progression --message="Upgrade of $app completed" --last
|
|
||||||
506
src/css/sr2ini.scss
Normal file
@ -0,0 +1,506 @@
|
|||||||
|
$fg: deeppink;
|
||||||
|
$fg-bright: lightpink;
|
||||||
|
$fg-dark: #b3005f;
|
||||||
|
|
||||||
|
$bg: cyan;
|
||||||
|
$bg-bright: lightcyan;
|
||||||
|
$bg-dark: darkcyan;
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Electrolize";
|
||||||
|
src: local("Electrolize"), url("../img/Electrolize-Regular.ttf") format("truetype"), url("https://fonts.googleapis.com/css2?family=Electrolize&display=swap");
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin aug() {
|
||||||
|
--aug-b: 5px;
|
||||||
|
--aug-bl: 5px;
|
||||||
|
--aug-br: 5px;
|
||||||
|
--aug-l: 5px;
|
||||||
|
--aug-r: 5px;
|
||||||
|
--aug-tl: 5px;
|
||||||
|
--aug-tr: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin border() {
|
||||||
|
--aug-border-all: 2px;
|
||||||
|
--aug-border-bg: cyan; // variables don't work in this specific instance
|
||||||
|
--aug-border-opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin inlay() {
|
||||||
|
--aug-inlay-bg: rgba(0, 0, 0, .5);
|
||||||
|
--aug-inlay-y: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin button() {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid $bg;
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 0 0 2px $bg-bright, 0 0 4px $bg, 0 0 8px $bg-dark;
|
||||||
|
color: $fg;
|
||||||
|
padding-inline: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: 'Electrolize', sans-serif;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: darkslategray;
|
||||||
|
background-image: url("../img/bg.jpg");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon { fill: $fg; }
|
||||||
|
|
||||||
|
.sr2-button:focus-visible {
|
||||||
|
border: 1px solid $bg !important;
|
||||||
|
box-shadow: 0 0 4px $bg-bright, 0 0 8px $bg, 0 0 16px $bg-dark !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 4px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.navbar {
|
||||||
|
@include border;
|
||||||
|
@include inlay;
|
||||||
|
|
||||||
|
--aug-b: 7px;
|
||||||
|
--aug-bl: 7px;
|
||||||
|
--aug-br: 7px;
|
||||||
|
--aug-inlay-bg: rgba(0, 0, 0, .75);
|
||||||
|
--aug-l: 7px;
|
||||||
|
--aug-r: 7px;
|
||||||
|
--aug-tl: 7px;
|
||||||
|
--aug-tr: 7px;
|
||||||
|
padding-right: .6rem;;
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
color: $bg;
|
||||||
|
text-shadow: 0 0 3px, 0 0 6px, 0 0 12px, 0 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
@include button;
|
||||||
|
|
||||||
|
height: 38px;
|
||||||
|
margin-left: 3px;
|
||||||
|
margin-right: 3px;
|
||||||
|
width: 38px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
filter: invert(34%) sepia(78%) saturate(7014%) hue-rotate(316deg) brightness(95%) contrast(105%);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive { margin-bottom: 1px; }
|
||||||
|
|
||||||
|
.combatants-table {
|
||||||
|
margin-top: .5rem;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
@include aug;
|
||||||
|
@include border;
|
||||||
|
@include inlay;
|
||||||
|
|
||||||
|
--aug-border-bottom: 0px;
|
||||||
|
--aug-border-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combatant-row {
|
||||||
|
clip-path: none;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:not(:first-of-type) { text-align: center; }
|
||||||
|
|
||||||
|
.th-ini { min-width: 3rem; }
|
||||||
|
|
||||||
|
.th-dice-and-rea { min-width: 3.75rem; }
|
||||||
|
|
||||||
|
.th-actions { min-width: 6.5rem; }
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
@include aug;
|
||||||
|
@include border;
|
||||||
|
@include inlay;
|
||||||
|
|
||||||
|
--aug-border-bottom: 0px;
|
||||||
|
--aug-border-right: 0px;
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
color: $bg;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
clip-path: none;
|
||||||
|
color: $fg;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:first-of-type,
|
||||||
|
td:first-of-type {
|
||||||
|
padding-left: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:last-of-type,
|
||||||
|
td:last-of-type {
|
||||||
|
--aug-border-right: 2px;
|
||||||
|
padding-right: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:last-of-type td,
|
||||||
|
tr:last-of-type th {
|
||||||
|
--aug-border-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combatant-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr2-button {
|
||||||
|
@include button;
|
||||||
|
|
||||||
|
height: 24px;
|
||||||
|
margin-left: 3px;
|
||||||
|
margin-right: 3px;
|
||||||
|
width: 24px;
|
||||||
|
|
||||||
|
img,
|
||||||
|
.icon {
|
||||||
|
bottom: 3px;
|
||||||
|
height: 16px;
|
||||||
|
position: relative;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: $bg-dark;
|
||||||
|
|
||||||
|
.icon { fill: $fg-dark; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.combatant-ini {
|
||||||
|
padding-right: 1rem !important;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combatant-dice-and-rea { text-align: center; }
|
||||||
|
|
||||||
|
.combatant-dice::before { content: attr(data-combatant-dice); }
|
||||||
|
|
||||||
|
.combatant-rea::before { content: attr(data-combatant-rea); }
|
||||||
|
|
||||||
|
.actions-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
@include button;
|
||||||
|
|
||||||
|
height: 24px;
|
||||||
|
margin: 4px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-ini td {
|
||||||
|
text-shadow: 0 0 .15em $fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zero-ini td {
|
||||||
|
color: $fg-dark !important;
|
||||||
|
|
||||||
|
.icon { fill: $fg-dark !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ko-or-dead td {
|
||||||
|
background-color: rgba(0, 0, 0, .5);
|
||||||
|
color: $fg-dark !important;
|
||||||
|
text-decoration: line-through .1em $fg;
|
||||||
|
|
||||||
|
.icon { fill: $fg-dark !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.bg-warning {
|
||||||
|
background: radial-gradient(circle at center, $bg, $bg-dark);
|
||||||
|
bottom: -4px;
|
||||||
|
left: 12px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.bg-danger {
|
||||||
|
background: radial-gradient(circle at center, $fg, $fg-dark);
|
||||||
|
left: 12px;
|
||||||
|
top: 12px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-dropdown,
|
||||||
|
.actions-dropdown { display: inline-block; }
|
||||||
|
|
||||||
|
.damage-monitor,
|
||||||
|
.actions-menu {
|
||||||
|
@include aug;
|
||||||
|
@include border;
|
||||||
|
|
||||||
|
--aug-inlay-bg: rgba(0, 0, 0, .5);
|
||||||
|
--aug-border-opacity: .75;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% - 2px);
|
||||||
|
transition: transform .25s ease-in-out;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-monitor { left: calc(100vw - 1em); }
|
||||||
|
|
||||||
|
.actions-menu { left: calc(100vw + 3.5em); }
|
||||||
|
|
||||||
|
.seen { transform: translateX(-100vw); }
|
||||||
|
|
||||||
|
.damage-monitor {
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: smaller;
|
||||||
|
height: 24px;
|
||||||
|
margin: 0px 2px;
|
||||||
|
width: 24px;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
filter: brightness(150%) !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-stun {
|
||||||
|
background: radial-gradient(circle at center, $bg, $bg-dark);
|
||||||
|
box-shadow: none;
|
||||||
|
transition: background .5s, box-shadow .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-stun.active {
|
||||||
|
background: radial-gradient(circle at center, $bg-bright, $bg);
|
||||||
|
box-shadow: 0 0 3px $bg-bright, 0 0 6px $bg, 0 0 12px $bg-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-physical {
|
||||||
|
background: radial-gradient(circle at center, $fg, $fg-dark);
|
||||||
|
border-radius: 50%;;
|
||||||
|
box-shadow: none;
|
||||||
|
transition: background .5s, box-shadow .5s;
|
||||||
|
|
||||||
|
&:focus-visible { outline: $fg !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-physical.active {
|
||||||
|
background: radial-gradient(circle at center, $fg-bright, $fg);
|
||||||
|
box-shadow: 0 0 3px $fg-bright, 0 0 6px $fg, 0 0 12px $fg-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.footer-container {
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
position: fixed;
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
@include aug;
|
||||||
|
@include inlay;
|
||||||
|
|
||||||
|
--aug-border-all: 2px !important;
|
||||||
|
--aug-border-bg: cyan !important; // vars don't work here
|
||||||
|
--aug-border-opacity: .5 !important;
|
||||||
|
--aug-inlay-bg: rgba(0, 0, 0, .75) !important;
|
||||||
|
--aug-tl: 10px;
|
||||||
|
--aug-tr: 10px;
|
||||||
|
height: 2.5em;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: $bg;
|
||||||
|
font-size: xx-small;
|
||||||
|
margin: .25rem;
|
||||||
|
padding-top: .65em;
|
||||||
|
text-align: center;
|
||||||
|
user-select: auto;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: hotpink;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.sr2-modal {
|
||||||
|
@include border;
|
||||||
|
@include aug;
|
||||||
|
|
||||||
|
color: $bg;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
button { @include button; }
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
@include inlay;
|
||||||
|
@include border;
|
||||||
|
|
||||||
|
--aug-inlay-bottom: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
button { width: 30px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
@include inlay;
|
||||||
|
@include border;
|
||||||
|
|
||||||
|
--aug-inlay-y: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
@include inlay;
|
||||||
|
@include border;
|
||||||
|
|
||||||
|
--aug-inlay-top: 0;
|
||||||
|
border-top: none;
|
||||||
|
|
||||||
|
button { width: 4rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
label { margin: 0; }
|
||||||
|
|
||||||
|
.label-swap {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
|
||||||
|
label { order: 2; }
|
||||||
|
|
||||||
|
input { order: 1; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
background-color: transparent;
|
||||||
|
color: $fg;
|
||||||
|
user-select: text;
|
||||||
|
|
||||||
|
&::selection {
|
||||||
|
background-color: $fg;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: transparent;
|
||||||
|
color: $fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: transparent;
|
||||||
|
color: $fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([type=range]):valid {
|
||||||
|
background-image: none !important;
|
||||||
|
border: 1px solid $bg !important ;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:invalid {
|
||||||
|
background-image: none !important;
|
||||||
|
border: 1px solid $fg;
|
||||||
|
box-shadow: 0 0 3px $fg-bright, 0 0 6px $fg, 0 0 12px $fg-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[type=number]::-webkit-inner-spin-button,
|
||||||
|
&[type=number]::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[type=range] {
|
||||||
|
// -webkit-appearance: none;
|
||||||
|
margin-left: calc(4.5% - 2px);
|
||||||
|
width: 91%;
|
||||||
|
|
||||||
|
+ datalist {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
option {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 9%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-track {
|
||||||
|
border: 1px;
|
||||||
|
border-radius: 1px;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-thumb {
|
||||||
|
height: 20px;
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-text {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: $fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border-top: 2px solid $bg;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#combatant-modal-stun {
|
||||||
|
&::-moz-range-track { background-color: $bg-dark; }
|
||||||
|
&::-moz-range-thumb { background-color: $bg; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#combatant-modal-physical {
|
||||||
|
&::-moz-range-track { background-color: $fg-dark; }
|
||||||
|
&::-moz-range-thumb { background-color: $fg; }
|
||||||
|
}
|
||||||
BIN
src/img/Electrolize-Regular.ttf
Normal file
BIN
src/img/add.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
src/img/bg.jpg
Normal file
|
After Width: | Height: | Size: 620 KiB |
BIN
src/img/cross.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/img/newround.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
src/img/zzz.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
130
src/index.html
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>Shadowrun 2e Ini Tracker</title>
|
||||||
|
<meta name="description" content="A simple Initiative tracker for Shadowrun 2e" />
|
||||||
|
<meta name="author" content="Eclipse729" />
|
||||||
|
<meta name="theme-color" content="" />
|
||||||
|
|
||||||
|
<link rel="manifest" href="/icons/sr2ini.webmanifest">
|
||||||
|
|
||||||
|
<link type="text/css" rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.css" >
|
||||||
|
<link type="text/css" rel="stylesheet" href="../node_modules/augmented-ui/augmented-ui.css">
|
||||||
|
<link type="text/css" rel="stylesheet" href="css/sr2ini.scss">
|
||||||
|
|
||||||
|
<script type="module" src="js/sr2ini.js" defer></script>
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="/icons/sr2ini.webmanifest">
|
||||||
|
<link rel="shortcut icon" href="/icons/favicon.ico">
|
||||||
|
<meta name="msapplication-TileColor" content="#004aa5">
|
||||||
|
<meta name="msapplication-config" content="/icons/browserconfig.xml">
|
||||||
|
<meta name="theme-color" content="#004aa5">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header class="navbar navbar-expand" data-augmented-ui="tl-2-clip-x tr-clip-y bl-clip-y br-2-clip-x b-scoop-x both">
|
||||||
|
<span class="navbar-brand ps-4">SR2 Initiative Tracker</span>
|
||||||
|
<nav class="container-fluid justify-content-end" aria-label="Main navigation">
|
||||||
|
<button type="submit" class="sr2-button" id="add-combatant-button" title="Add combatant" data-bs-toggle="modal" data-bs-target="#combatant-modal"><img src="img/add.png" alt="user outline with a plus sign"></button>
|
||||||
|
<button type="submit" class="sr2-button" id="new-round-button" title="Start new round" data-bs-toggle="modal" data-bs-target="#confirm-modal"><img src="img/newround.png" alt="a die being rolled"></button>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<main class="table-responsive overflow-visible">
|
||||||
|
<table class="table table-sm table-borderless combatants-table">
|
||||||
|
<thead>
|
||||||
|
<tr data-augmented-ui="tl-2-clip-y r-clip-y">
|
||||||
|
<th class="col th-name" data-augmented-ui="tl-2-clip-y both" title="Name">Name</th>
|
||||||
|
<th class="col-2 th-ini" data-augmented-ui="both" title="Initiative">Ini</th>
|
||||||
|
<th class="col-2 th-dice-and-rea" data-augmented-ui="both" title="Initiative Dice and Reaction">D+R</th>
|
||||||
|
<th class="col-3 th-actions" data-augmented-ui="r-clip-y both" title="Actions">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-container">
|
||||||
|
<footer data-augmented-ui="tl-2-clip-x tr-2-clip-x both">
|
||||||
|
<p>code & design by <a href="https://tobias-radloff.de/" tabindex="-1" title="Eclipse">Eclipse</a> | Shadowrun trademarked by <a href="https://www.topps.com/" tabindex="-1" title="Topps">Topps</a><br>
|
||||||
|
icons by <a href="https://www.freepik.com" tabindex="-1" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" tabindex="-1" title="Flaticon">www.flaticon.com</a> | background by <a href="https://www.deviantart.com/xxaries1970xx" tabindex="-1" title="xxAries1970xx on DeviantArt">xxAries1970xx</a></p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="confirm-modal" tabindex="-2" role="dialog">
|
||||||
|
<div class="modal-dialog modal-sm" role="document">
|
||||||
|
<div class="sr2-modal" data-augmented-ui="tl-2-clip-x tr-clip-y bl-clip-y br-2-clip-x b-scoop-x border">
|
||||||
|
<div class="modal-header" data-augmented-ui="inlay">
|
||||||
|
<h5 class="modal-title">Start New Round</h5>
|
||||||
|
<button type="button" class="sr2-button" data-bs-dismiss="modal" aria-label="Close">✖</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" data-augmented-ui="inlay">
|
||||||
|
<p>Are you sure?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer" data-augmented-ui="inlay">
|
||||||
|
<button type="button" class="sr2-button" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="sr2-button" id="confirm-modal-new-round-ok-button" data-bs-dismiss="modal">OK</button>
|
||||||
|
<button type="submit" class="sr2-button d-none" id="confirm-modal-remove-combatant-ok-button" data-bs-dismiss="modal">OK</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="combatant-modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog modal-sm" role="document">
|
||||||
|
<div class="sr2-modal" data-augmented-ui="tl-2-clip-x tr-clip-y bl-clip-y br-2-clip-x b-scoop-x border">
|
||||||
|
<div class="modal-header" data-augmented-ui="inlay">
|
||||||
|
<h5 class="modal-title">Add New Combatant</h5>
|
||||||
|
<button type="button" class="sr2-button" data-bs-dismiss="modal" aria-label="Close">✖</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" data-augmented-ui="inlay">
|
||||||
|
<form id="combatant-form" name="combatant-modal-form" class="was-validated" onsubmit="return false;">
|
||||||
|
<div class="my-2">
|
||||||
|
<input type="text" maxlength="40" class="form-control form-control-sm" id="combatant-modal-name" form="combatant-form" placeholder="Name" required>
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-sm my-2">
|
||||||
|
<input type="number" min="1" max="5" class="form-control form-control-sm" id="combatant-modal-dice" form="combatant-form" placeholder="Dice">
|
||||||
|
<span class="input-group-text">D+</span>
|
||||||
|
<input type="number" min="1" max="25" class="form-control form-control-sm" id="combatant-modal-rea" form="combatant-form" placeholder="REA">
|
||||||
|
<span class="input-group-text"> </span>
|
||||||
|
<input type="number" min="0" max="55" class="form-control form-control-sm" id="combatant-modal-ini" form="combatant-form" placeholder="Ini">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="my-2">
|
||||||
|
<label for="combatant-modal-stun" class="form-label">Stun Damage <span id="combatant-modal-penalty-stun"></span></label>
|
||||||
|
<input type="range" class="form-range" min="0" max="10" value="0" id="combatant-modal-stun" list="damage">
|
||||||
|
<datalist id="damage">
|
||||||
|
<option>-</option><option>L</option><option>.</option><option>M</option><option>.</option><option>.</option><option>S</option><option>.</option><option>.</option><option>.</option><option>D</option>
|
||||||
|
</datalist>
|
||||||
|
<div class="label-swap">
|
||||||
|
<label for="combatant-modal-physical" class="form-label">Physical Damage <span id="combatant-modal-penalty-physical"></span></label>
|
||||||
|
<input type="range" class="form-range" min="0" max="10" value="0" id="combatant-modal-physical" list="damage">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer" data-augmented-ui="inlay">
|
||||||
|
<button type="button" class="sr2-button" id="combatant-modal-cancel-button" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="sr2-button" id="combatant-modal-add-apply-button">Apply</button>
|
||||||
|
<button type="submit" class="sr2-button" id="combatant-modal-add-ok-button" data-bs-dismiss="modal">OK</button>
|
||||||
|
<button type="submit" class="sr2-button d-none" id="combatant-modal-edit-ok-button" data-bs-dismiss="modal">OK</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
38
src/js/service-worker.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import {manifest, version} from '@parcel/service-worker';
|
||||||
|
|
||||||
|
console.log("[Service Worker] Version:", version);
|
||||||
|
console.log("[Service Worker] Manifest:", manifest);
|
||||||
|
|
||||||
|
// install Service Worker
|
||||||
|
async function install() {
|
||||||
|
const cache = await caches.open(version); //cacheNAme
|
||||||
|
console.log("[Service Worker] Caching all: app shell and content");
|
||||||
|
await cache.addAll(manifest); // fileList
|
||||||
|
}
|
||||||
|
console.log("[Service Worker] Install");
|
||||||
|
self.addEventListener("install", e => e.waitUntil(install()));
|
||||||
|
|
||||||
|
// fetch content
|
||||||
|
/*self.addEventListener('fetch', (e) => {
|
||||||
|
e.respondWith((async () => {
|
||||||
|
const r = await caches.match(e.request);
|
||||||
|
console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
|
||||||
|
if (r) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
const response = await fetch(e.request);
|
||||||
|
const cache = await caches.open(cacheName);
|
||||||
|
console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
|
||||||
|
cache.put(e.request, response.clone());
|
||||||
|
return response;
|
||||||
|
})());
|
||||||
|
});*/
|
||||||
|
|
||||||
|
|
||||||
|
// activate: clear outdated files from cache
|
||||||
|
async function activate() {
|
||||||
|
const keys = await caches.keys();
|
||||||
|
await Promise.all(keys.map(key => key !== version && caches.delete(key)));
|
||||||
|
};
|
||||||
|
console.log("[Service Worker] Activate");
|
||||||
|
self.addEventListener("activate", e => e.waitUntil(activate()));
|
||||||
506
src/js/sr2ini.js
Normal file
@ -0,0 +1,506 @@
|
|||||||
|
// Register Service Worker
|
||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
navigator.serviceWorker.register(
|
||||||
|
new URL("service-worker.js", import.meta.url), { type: "module" }
|
||||||
|
).then( (registration) => {
|
||||||
|
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
||||||
|
}, (err) => {
|
||||||
|
console.log('ServiceWorker registration failed: ', err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error("Service workers are not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* import libraries
|
||||||
|
*/
|
||||||
|
|
||||||
|
const bs = require("../../node_modules/bootstrap/js/dist/modal.js");
|
||||||
|
const $ = require("../../node_modules/jquery/dist/jquery.js");
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* constants definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DAMAGE_PENALTY = [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4];
|
||||||
|
const DAMAGE_NIVEAU = ["", "L", "M", "S", "D"];
|
||||||
|
|
||||||
|
const COMBATANT_TABLE_ROW = [
|
||||||
|
'<tr class="combatant-row" data-true-ini="" data-augmented-ui="tl-scoop bl-clip-y tr-clip-y br-scoop">\n', //TODO: add data-damage-* attributes with initial damage levels
|
||||||
|
'<td class="combatant-name" title="Combatant\'s name" data-bs-toggle="modal" data-bs-target="#combatant-modal" data-augmented-ui="tl-scoop bl-clip-y both"></td>\n',
|
||||||
|
'<td class="combatant-ini" title="Effective initiative (w/ wound penalties)" data-bs-toggle="modal" data-bs-target="#combatant-modal" data-augmented-ui="both"></td>\n',
|
||||||
|
'<td class="combatant-dice-and-rea" title="Iniative dice and reaction" data-bs-toggle="modal" data-bs-target="#combatant-modal" data-augmented-ui="both"><span class="combatant-dice"></span>D+<span class="combatant-rea"></span></td>\n',
|
||||||
|
'<td class="combatant-actions" data-augmented-ui="tr-clip-y br-scoop both">\n',
|
||||||
|
'<button type="button" class="sr2-button act-button" title="Act and reduce ini by 10"><svg class="icon" viewBox="0 0 512 512"><path d="M 0 272 h 96 v 64 h -96 Z" /><path d="M 160 64 h 64 v 384 h -64 v -296 l -64 64 l -40 -40 Z" /><path d="M 352 64 h 96 l 64 64 v 256 l -64 64 h -96 l -64 -64 v -256 l 64 -64 l 32 64 h 32 l 32 32 v 192 l -32 32 h -32 l -32 -32 v -192 l 32 -32" fill-rule="evenodd" /></svg></button>\n',
|
||||||
|
'<div class="damage-dropdown">\n',
|
||||||
|
'<button type="button" class="sr2-button damage-button" title="Take damage"><svg class="icon" viewBox="0 0 512 512"><path d="M 0 288 L 144 224 L 64 32 L 224 128 L 272 0 L 336 144 L 480 96 L 400 224 L 512 304 L 384 352 L 432 512 L 272 416 L 128 512 L 160 352 L 0 288 L 166 267 L 222 290 L 211 346 L 262 312 L 318 346 L 301 290 L 346 273 L 306 245 L 334 200 L 284 217 L 262 166 L 245 211 L 189 178 L 217 245 L 166 267" fill-rule="evenodd" /></svg></button>\n',
|
||||||
|
'</div>\n',
|
||||||
|
'<div class="actions-dropdown">\n',
|
||||||
|
'<button type="button" class="sr2-button actions-button" title="More actions"><svg class="icon" viewBox="0 0 512 512"><polygon points="32 32 480 32 256 480" /></svg></button>\n',
|
||||||
|
'<div class="actions-menu" data-augmented-ui="tl-scoop bl-clip-y tr-clip-y br-scoop both">\n',
|
||||||
|
'<button type="button" class="sr2-button edit-button" title="Edit combatant" data-bs-toggle="modal" data-bs-target="#combatant-modal" tabindex="-1"><svg class="icon" viewBox="0 0 512 512"><polygon points="0 512 0 352 224 128 384 288 160 512" /><polygon points="352 0 512 160 416 256 256 96" /></svg></button>\n',
|
||||||
|
'<button type="button" class="sr2-button clone-button" title="Clone combatant" data-bs-toggle="modal" data-bs-target="#combatant-modal" tabindex="-1"><svg class="icon" viewBox="0 0 512 512"><rect x="0" y="0" width="512" height="128" /><rect x="0" y="384" width="512" height="128" /><polygon points="128 192 384 192 256 320" />/svg></button>\n',
|
||||||
|
'<button type="button" class="sr2-button remove-button" title="Remove combatant" data-bs-toggle="modal" data-bs-target="#confirm-modal" tabindex="-1"><svg class="icon" viewBox="0 0 512 512"><polygon points="96 0 416 0 416 64 480 64 480 128 32 128 32 64 96 64" /><path d="M 64 512 H 448 V 160 H 64 V 512 L 128 480 V 192 H 192 V 480 H 224 V 192 H 288 V 480 H 320 V 192 H 384 V 480 H 128" fill-rule="evenodd" /></svg></button>\n',
|
||||||
|
'</div>\n',
|
||||||
|
'</div>\n',
|
||||||
|
'</td>\n',
|
||||||
|
'</tr>'].join("");
|
||||||
|
|
||||||
|
const DAMAGE_MONITOR_HTML = [
|
||||||
|
'<div class="damage-monitor" data-augmented-ui="tl-scoop bl-clip-y tr-clip-y br-scoop both">\n',
|
||||||
|
'<table>\n',
|
||||||
|
'<tr><td><button type="button" class="damage-stun active" title="Light stun damage" tabindex="-1">L</button></td><td><button type="button" class="damage-physical active" title="Light physical damage" tabindex="-1">L</button></td></tr>\n',
|
||||||
|
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
||||||
|
'<tr><td><button type="button" class="damage-stun active" title="Medium stun damage" tabindex="-1">M</button></td><td><button type="button" class="damage-physical active" title="Medium physical damage" tabindex="-1">M</button></td></tr>\n',
|
||||||
|
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
||||||
|
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
||||||
|
'<tr><td><button type="button" class="damage-stun active" title="Severe stun damage" tabindex="-1">S</button></td><td><button type="button" class="damage-physical active" title="Severe physical damage" tabindex="-1">S</button></td></tr>\n',
|
||||||
|
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
||||||
|
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
||||||
|
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
||||||
|
'<tr><td><button type="button" class="damage-stun active" title="K.O." tabindex="-1"><img src="zzz.png" height="16" /></button></td><td><button type="button" class="damage-physical active" title="dead" tabindex="-1" ><img src="cross.png" height="16"/></button></td></tr>\n',
|
||||||
|
'</table>\n',
|
||||||
|
'</div>'].join("");
|
||||||
|
|
||||||
|
const STUN_BADGE_HTML = '<sup><span class="badge bg-warning position-absolute translate-middle stun-badge" title="Stun damage niveau"></span></sup>';
|
||||||
|
const PHYSICAL_BADGE_HTML = '<sub><span class="badge bg-danger position-absolute translate-middle physical-badge" title="Physical damage niveau"></span></sub>';
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* helper functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// roll for initiative with the given reaction and number of ini dice
|
||||||
|
function rollForInitiative(dice, rea) {
|
||||||
|
let diceRolls = Array.from({ length: parseInt(dice) }, () => Math.ceil(Math.random() * 6));
|
||||||
|
return diceRolls.reduce((a, b) => a + b, 0) + parseInt(rea);
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out whose action comes first out of two combatants a and b
|
||||||
|
function whoGoesFirst(a, b) {
|
||||||
|
// check for K.O./death
|
||||||
|
let tmpA = $(a).hasClass("ko-or-dead") ? -1 : parseInt($(a).find(".combatant-ini").text()) || 0;
|
||||||
|
let tmpB = $(b).hasClass("ko-or-dead") ? -1 : parseInt($(b).find(".combatant-ini").text()) || 0;
|
||||||
|
// compare ini
|
||||||
|
let compIni = tmpB - tmpA;
|
||||||
|
if (compIni != 0) {
|
||||||
|
return compIni;
|
||||||
|
}
|
||||||
|
// tie; compare reaction
|
||||||
|
else {
|
||||||
|
if ($(a).find(".combatant-rea").text() == "" || $(b).find(".combatant-rea").text() == "") {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return parseInt($(b).find(".combatant-rea").text()) - parseInt($(a).find(".combatant-rea").text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute a combatant's effective ini value (modified by wound penalties)
|
||||||
|
function getEffectiveIni(tr) {
|
||||||
|
// return -1 if combatant is K.O. or dead
|
||||||
|
if ($(tr).hasClass("ko-or-dead")) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// otherwise compute effective ini (true ini minus wound penalties)
|
||||||
|
let effectiveIni = parseInt($(tr).attr("data-true-ini")) - DAMAGE_PENALTY[parseInt($(tr).attr("data-damage-stun")) || 0] - DAMAGE_PENALTY[parseInt($(tr).attr("data-damage-physical")) || 0];
|
||||||
|
return Math.max(effectiveIni, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add test combatant for testing purposes (duh)
|
||||||
|
function addTestCombatant() {
|
||||||
|
// Eclipse
|
||||||
|
$("#add-combatant-button").click();
|
||||||
|
$("#combatant-modal-name").val("Eclipse");
|
||||||
|
$("#combatant-modal-dice").val(3);
|
||||||
|
$("#combatant-modal-rea").val(6);
|
||||||
|
// $("#combatant-modal-ini").val(12);
|
||||||
|
addCombatant();
|
||||||
|
// setTimeout( () => $("#combatant-modal-add-ok-button").click(), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Event handler functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// click handler for act buttons; reduces ini by 10
|
||||||
|
function handleActButtonClick(e) {
|
||||||
|
// reduce ini by 10 but not lower than 0
|
||||||
|
let ini = Math.max(parseInt($(e.target).parents(".combatant-row").attr("data-true-ini")) - 10, 0);
|
||||||
|
// set new ini value
|
||||||
|
$(e.target).parents(".combatant-row").attr("data-true-ini", ini);
|
||||||
|
// resort table
|
||||||
|
sortTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// click handler for add buttons
|
||||||
|
function handleAddButtonClick(e) {
|
||||||
|
// restyle modal
|
||||||
|
$("#combatant-modal .modal-title").text("Add Combatant");
|
||||||
|
$("#combatant-modal-add-ok-button, #combatant-modal-add-apply-button").removeClass("d-none");
|
||||||
|
$("#combatant-modal-edit-ok-button").addClass("d-none");
|
||||||
|
// set default values
|
||||||
|
$("#combatant-modal-name").val("Goon 1");
|
||||||
|
$("#combatant-modal-dice").val("2");
|
||||||
|
$("#combatant-modal-rea").val("7");
|
||||||
|
// set damage sliders to zero
|
||||||
|
$("#combatant-modal-stun, #combatant-modal-physical").val("0");
|
||||||
|
// add handler for enter key
|
||||||
|
$("#combatant-modal input[id*='combatant-modal']").off("keydown");
|
||||||
|
$("#combatant-modal input[id*='combatant-modal']").on("keydown", (e) => { if (e.which == 13 || e.which == 10) { addCombatant(e); } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// click handler for clone buttons -> like handleAddButtonClick but with a pre-filled modal
|
||||||
|
function handleCloneButtonClick(e) {
|
||||||
|
// find current table row
|
||||||
|
let $tr = $(e.target).parents(".combatant-row");
|
||||||
|
// hide actions menu
|
||||||
|
$tr.find(".actions-menu").removeClass("seen");
|
||||||
|
// restyle modal
|
||||||
|
$("#combatant-modal .modal-title").text("Clone Combatant");
|
||||||
|
$("#combatant-modal-add-ok-button, #combatant-modal-add-apply-button").removeClass("d-none");
|
||||||
|
$("#combatant-modal-edit-ok-button").addClass("d-none");
|
||||||
|
// populate modal with values from row
|
||||||
|
$("#combatant-modal-name").val($tr.find(".combatant-name").text());
|
||||||
|
$("#combatant-modal-dice").val($tr.find(".combatant-dice").attr("data-combatant-dice"));
|
||||||
|
$("#combatant-modal-rea").val($tr.find(".combatant-rea").attr("data-combatant-rea"));
|
||||||
|
$("#combatant-modal-ini").val($tr.attr("data-true-ini"));
|
||||||
|
$("#combatant-modal-stun").val($tr.attr("data-damage-stun") || "0");
|
||||||
|
$("#combatant-modal-physical").val($tr.attr("data-damage-physical") || "0");
|
||||||
|
// add handler for enter key
|
||||||
|
$("#combatant-modal input[id*='combatant-modal']").off("keydown");
|
||||||
|
$("#combatant-modal input[id*='combatant-modal']").on("keydown", (e) => { if (e.which == 13 || e.which == 10) { addCombatant(e); } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// click handler for damage buttons; basically toggles visibility of table.damage-monitor
|
||||||
|
function handleDamageButtonClick(e) {
|
||||||
|
// get visibility status at click time
|
||||||
|
let seenAtClick = $(e.target).parents(".damage-dropdown").find(".damage-monitor").hasClass("seen");
|
||||||
|
// hide all damage monitors and actions menus
|
||||||
|
$(".damage-monitor.seen, .actions-menu.seen").removeClass("seen").find("button").attr("tabindex", "-1");
|
||||||
|
// if targeted dm was hidden before, show it now
|
||||||
|
if (! seenAtClick) {
|
||||||
|
$(e.target).parents(".damage-dropdown").find(".damage-monitor").addClass("seen").find("button").attr("tabindex", "0");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// click handler for edit buttons
|
||||||
|
function handleEditButtonClick(e) {
|
||||||
|
// find current table row
|
||||||
|
let $tr = $(e.target).parents(".combatant-row");
|
||||||
|
// restyle modal
|
||||||
|
$("#combatant-modal .modal-title").text("Edit Combatant");
|
||||||
|
$("#combatant-modal-edit-ok-button").removeClass("d-none");
|
||||||
|
$("#combatant-modal-add-ok-button, #combatant-modal-add-apply-button").addClass("d-none");
|
||||||
|
// populate modal with values from row
|
||||||
|
$("#combatant-modal-name").val($tr.find(".combatant-name").text());
|
||||||
|
$("#combatant-modal-dice").val($tr.find(".combatant-dice").attr("data-combatant-dice"));
|
||||||
|
$("#combatant-modal-rea").val($tr.find(".combatant-rea").attr("data-combatant-rea"));
|
||||||
|
$("#combatant-modal-ini").val($tr.attr("data-true-ini"));
|
||||||
|
$("#combatant-modal-stun").val($tr.attr("data-damage-stun") || "0");
|
||||||
|
$("#combatant-modal-physical").val($tr.attr("data-damage-physical") || "0");
|
||||||
|
// mark which row is being edited
|
||||||
|
$("#combatant-modal").data("row", $(".combatant-row").index($tr)); // here it's okay to use the jQuery data() function (which is not the same as using a data attribute) b/c this value is used only in this script and not via HTML or CSS
|
||||||
|
// add handler for enter key
|
||||||
|
$("#combatant-modal input[id*='combatant-modal']").off("keydown");
|
||||||
|
$("#combatant-modal input[id*='combatant-modal']").on("keydown", (e) => { if (e.which == 13 || e.which == 10) { editCombatant(e); } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// click handler for the more-actions menus
|
||||||
|
function handleMoreActionsButtonClick(e) {
|
||||||
|
// get visibility status at click time
|
||||||
|
let seenAtClick = $(e.target).parents(".actions-dropdown").find(".actions-menu").hasClass("seen");
|
||||||
|
|
||||||
|
// hide all damage monitors
|
||||||
|
$(".actions-menu.seen, .damage-monitor.seen").removeClass("seen").find("button").attr("tabindex", "-1");
|
||||||
|
// if targeted dm was seen before, show it now
|
||||||
|
if (! seenAtClick) {
|
||||||
|
$(e.target).parents(".actions-dropdown").find(".actions-menu").addClass("seen").find("button").attr("tabindex", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNewRoundButton(e) {
|
||||||
|
// restyle modal
|
||||||
|
$("#confirm-modal .modal-title").text("Start new Round");
|
||||||
|
$("#confirm-modal-new-round-ok-button").removeClass("d-none");
|
||||||
|
$("#confirm-modal-remove-combatant-ok-button").addClass("d-none");
|
||||||
|
}
|
||||||
|
|
||||||
|
// click handler for remove buttons
|
||||||
|
function handleRemoveButtonClick(e) {
|
||||||
|
// restyle modal
|
||||||
|
$("#confirm-modal .modal-title").text("Remove Combatant");
|
||||||
|
$("#confirm-modal-remove-combatant-ok-button").removeClass("d-none");
|
||||||
|
$("#confirm-modal-new-round-ok-button").addClass("d-none");
|
||||||
|
// mark which row is being removed
|
||||||
|
$("#confirm-modal").data("row", $(".combatant-row").index($(e.target).parents(".combatant-row"))); // here it's okay to use .data() b/c HTML/CSS does not care about this value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Main functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// add new combatant
|
||||||
|
function addCombatant(e) {
|
||||||
|
// e.preventDefault();
|
||||||
|
// validate form
|
||||||
|
if (!validateCombatant()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// roll for initiative if necessary
|
||||||
|
let ini = $("#combatant-modal-ini").val().trim();
|
||||||
|
ini = (ini != "") ? ini : rollForInitiative($("#combatant-modal-dice").val(), $("#combatant-modal-rea").val());
|
||||||
|
// construct jQuery object for table row
|
||||||
|
let $tr = $($.parseHTML(COMBATANT_TABLE_ROW));
|
||||||
|
$tr.find(".damage-dropdown").append($.parseHTML(DAMAGE_MONITOR_HTML));
|
||||||
|
// populate table row with values from modal
|
||||||
|
$tr.attr("data-true-ini", ini);
|
||||||
|
$tr.find(".combatant-name").text($("#combatant-modal-name").val().trim());
|
||||||
|
$tr.find(".combatant-dice").attr("data-combatant-dice", $("#combatant-modal-dice").val().trim());
|
||||||
|
$tr.find(".combatant-rea").attr("data-combatant-rea", $("#combatant-modal-rea").val().trim());
|
||||||
|
// retrieve initial damage levels
|
||||||
|
$tr.attr("data-damage-stun", $("#combatant-modal-stun").val() || "0");
|
||||||
|
$tr.find(".damage-stun").addClass("active").slice(0, parseInt($tr.attr("data-damage-stun")) || 0).removeClass("active");
|
||||||
|
$tr.attr("data-damage-physical", $("#combatant-modal-physical").val() || "0");
|
||||||
|
$tr.find(".damage-physical").addClass("active").slice(0, parseInt($tr.attr("data-damage-physical")) || 0).removeClass("active");
|
||||||
|
// add event handlers
|
||||||
|
$tr.find("button.act-button").on("click", handleActButtonClick);
|
||||||
|
$tr.find("button.damage-button").on("click", handleDamageButtonClick);
|
||||||
|
$tr.find("button.actions-button").on("click", handleMoreActionsButtonClick);
|
||||||
|
$tr.find("button.edit-button, .combatant-name, .combatant-ini, .combatant-dice-and-rea").on("click", handleEditButtonClick);
|
||||||
|
$tr.find("button.clone-button").on("click", handleCloneButtonClick);
|
||||||
|
$tr.find("button.remove-button").on("click", handleRemoveButtonClick);
|
||||||
|
// add event handler to damage monitor
|
||||||
|
$tr.find(".damage-stun, .damage-physical").on("click", applyDamage);
|
||||||
|
// append row to table and sort
|
||||||
|
$(".combatants-table").append($tr);
|
||||||
|
sortTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply damage to combatant
|
||||||
|
function applyDamage(e) {
|
||||||
|
let $btn = $(e.target).is("button") ? $(e.target) : $(e.target).parent();
|
||||||
|
// retrieve new damage level and type from button position and "damage-[type]" class
|
||||||
|
let damageLevel = $btn.parent().parent().index();
|
||||||
|
if ( $btn.hasClass("active") ) {
|
||||||
|
damageLevel += 1;
|
||||||
|
}
|
||||||
|
let damageType = $btn.attr("class").split(" ").filter(cls => cls.substr(0, 7) == "damage-" ? cls : false).toString().substr(7);
|
||||||
|
console.log("damageType is", damageType);
|
||||||
|
// add damage level to table row as as data attribute
|
||||||
|
$btn.parents("tr.combatant-row").attr("data-damage-" + damageType, damageLevel);
|
||||||
|
// select/unselect damage buttons above/below
|
||||||
|
$btn.toggleClass("active");
|
||||||
|
$btn.parent().parent().prevAll().find("button.damage-" + damageType).removeClass("active");
|
||||||
|
$btn.parent().parent().nextAll().find("button.damage-" + damageType + ":not(.active)").addClass("active");
|
||||||
|
sortTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// edit combatant
|
||||||
|
function editCombatant(e) {
|
||||||
|
console.log("editing combatant …")
|
||||||
|
// e.preventDefault();
|
||||||
|
// validate form
|
||||||
|
if (!validateCombatant()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// get values
|
||||||
|
let name = $("#combatant-modal-name").val().trim();
|
||||||
|
let ini = $("#combatant-modal-ini").val().trim();
|
||||||
|
let dice = $("#combatant-modal-dice").val().trim();
|
||||||
|
let rea = $("#combatant-modal-rea").val().trim();
|
||||||
|
// roll for initiative if ini is empty
|
||||||
|
ini = (ini != "") ? ini : rollForInitiative(dice, rea);
|
||||||
|
// get correct row
|
||||||
|
let index = parseInt($("#combatant-modal").data("row"));
|
||||||
|
console.log("row index is", index);
|
||||||
|
let $tr = $("tr.combatant-row").eq(index);
|
||||||
|
console.log("row is", $tr);
|
||||||
|
// set new values
|
||||||
|
$tr.attr("data-true-ini", ini);
|
||||||
|
$tr.find(".combatant-name").text(name);
|
||||||
|
$tr.find(".combatant-dice").attr("data-combatant-dice", dice);
|
||||||
|
$tr.find(".combatant-rea").attr("data-combatant-rea", rea);
|
||||||
|
$tr.attr("data-damage-stun", $("#combatant-modal-stun").val() || "0");
|
||||||
|
$tr.find(".damage-stun").addClass("active").slice(0, parseInt($tr.attr("data-damage-stun")) || 0).removeClass("active");
|
||||||
|
$tr.attr("data-damage-physical", $("#combatant-modal-physical").val() || "0");
|
||||||
|
$tr.find(".damage-physical").addClass("active").slice(0, parseInt($tr.attr("data-damage-physical")) || 0).removeClass("active");
|
||||||
|
// sort table
|
||||||
|
sortTable();
|
||||||
|
// clean up
|
||||||
|
$("#combatant-modal").data("row", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove combatant
|
||||||
|
function removeCombatant(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
// remove correct row
|
||||||
|
let index = parseInt($("#confirm-modal").data("row"));
|
||||||
|
$(".combatant-row").eq(index).remove();
|
||||||
|
sortTable();
|
||||||
|
// clean up
|
||||||
|
$("#confirm-modal").data("row", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// start a new combat round
|
||||||
|
function startNewRound(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
// are there rows at all?
|
||||||
|
if ($(".combatant-row").length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// reset ini values
|
||||||
|
$(".combatant-row").each(function () {
|
||||||
|
if ($(this).find(".combatant-dice").attr("data-combatant-dice") == "") {
|
||||||
|
$(this).attr("data-true-ini", 1);
|
||||||
|
} else {
|
||||||
|
$(this).attr("data-true-ini", rollForInitiative(parseInt($(this).find(".combatant-dice").attr("data-combatant-dice")), parseInt($(this).find(".combatant-rea").attr("data-combatant-rea"))));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// resort table
|
||||||
|
sortTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort combatants by ini value and add contextual classes
|
||||||
|
function sortTable() {
|
||||||
|
// do some clean up: remove previous classes from rows, disable act buttons, remove effective ini and damage badges
|
||||||
|
$(".combatant-row").removeClass("ko-or-dead max-ini zero-ini"); //REGULAR_INI
|
||||||
|
$(".combatant-row").find(".act-button").prop("disabled", true).attr("aria-disabled", "true");
|
||||||
|
$(".combatant-ini").empty();
|
||||||
|
// mark KO or death with class
|
||||||
|
$(".combatant-row").each(function() {
|
||||||
|
if (parseInt($(this).attr("data-damage-stun")) == 10 || parseInt($(this).attr("data-damage-physical")) == 10) {
|
||||||
|
$(this).addClass("ko-or-dead");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// compute highest effective ini
|
||||||
|
let iniMax = Math.max.apply(null, $.map($(".combatant-row"), function (tr, i) {
|
||||||
|
// write current effective ini to table row
|
||||||
|
$(tr).find(".combatant-ini").text($(tr).hasClass("ko-or-dead") ? 0 : getEffectiveIni($(tr)));
|
||||||
|
return $(tr).find(".combatant-ini").text();
|
||||||
|
}));
|
||||||
|
// add damage badges and contextual classes
|
||||||
|
$(".combatant-row").each(function () {
|
||||||
|
// damage badges
|
||||||
|
if ($(this).attr("data-damage-stun") && $(this).attr("data-damage-stun") != "0") {
|
||||||
|
$(this).find(".combatant-ini").append($.parseHTML(STUN_BADGE_HTML));
|
||||||
|
$(this).find(".stun-badge").append(DAMAGE_NIVEAU[DAMAGE_PENALTY[$(this).attr("data-damage-stun")]]);
|
||||||
|
}
|
||||||
|
if ($(this).attr("data-damage-physical") && $(this).attr("data-damage-physical") != "0") {
|
||||||
|
$(this).find(".combatant-ini").append($.parseHTML(PHYSICAL_BADGE_HTML));
|
||||||
|
$(this).find(".physical-badge").append(DAMAGE_NIVEAU[DAMAGE_PENALTY[$(this).attr("data-damage-physical")]]);
|
||||||
|
}
|
||||||
|
// K.O./dead -> don't add anything
|
||||||
|
if ($(this).hasClass("ko-or-dead")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// ini = zero
|
||||||
|
if (parseInt($(this).find(".combatant-ini").text()) == 0) {
|
||||||
|
$(this).addClass("zero-ini");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// ini = max and non-zero
|
||||||
|
if (parseInt($(this).find(".combatant-ini").text()) == iniMax && iniMax > 0) {
|
||||||
|
$(this).addClass("max-ini").find(".act-button").prop("disabled", false).removeAttr("aria-disabled");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// sort rows and append them in new order
|
||||||
|
let $rows = $(".combatant-row").toArray().sort(whoGoesFirst);
|
||||||
|
for (let i = 0; i < $rows.length; i++) {
|
||||||
|
$(".combatants-table").append($rows[i]);
|
||||||
|
$($rows[i]).css("z-index", 50-i).css("position", "relative");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate a combatant row form by checking for all conditions, including regular HTML5 validation
|
||||||
|
function validateCombatant() {
|
||||||
|
// get input elements
|
||||||
|
let inputElements = {
|
||||||
|
name: $("#combatant-modal-name").get(0),
|
||||||
|
ini: $("#combatant-modal-ini").get(0),
|
||||||
|
dice: $("#combatant-modal-dice").get(0),
|
||||||
|
rea: $("#combatant-modal-rea").get(0)
|
||||||
|
};
|
||||||
|
// do standard HTML5 form validation first
|
||||||
|
// (makes sure that name is not empty and that all other values are numbers within their individual ranges)
|
||||||
|
let valid = true;
|
||||||
|
Object.values(inputElements).forEach( input => {
|
||||||
|
if (!input.reportValidity()) { valid = false; }
|
||||||
|
})
|
||||||
|
if (!valid) { return false; }
|
||||||
|
// now for some custom validation; first we need to get the input values
|
||||||
|
let ini = inputElements["ini"].value.trim();
|
||||||
|
let dice = inputElements["dice"].value.trim();
|
||||||
|
let rea = inputElements["rea"].value.trim();
|
||||||
|
// invalidate if ini, dice and rea are all empty
|
||||||
|
if (ini == "" && (dice == "" || rea == "")) {
|
||||||
|
inputElements["ini"].setCustomValidity("Requiring values for ini dice and reaction, or initiative, or all three");
|
||||||
|
inputElements["ini"].reportValidity();
|
||||||
|
inputElements["ini"].setCustomValidity("");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// invalidate if dice or rea is empty but not both
|
||||||
|
if ((dice == "") != (rea == "")) {
|
||||||
|
inputElements["dice"].setCustomValidity("Values required for both dice and reaction, or none (in which case ini is required)");
|
||||||
|
inputElements["dice"].reportValidity();
|
||||||
|
inputElements["dice"].setCustomValidity("");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// ok then
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize document
|
||||||
|
*/
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
// add event handlers to navbar buttons
|
||||||
|
$("#add-combatant-button").on("click", handleAddButtonClick);
|
||||||
|
$("#new-round-button").on("click", handleNewRoundButton);
|
||||||
|
// add event handlers to modal buttons
|
||||||
|
$("#combatant-modal-add-ok-button, #combatant-modal-add-apply-button").on("click", addCombatant);
|
||||||
|
$("#combatant-modal-edit-ok-button").on("click", editCombatant);
|
||||||
|
$("#confirm-modal-new-round-ok-button").on("click", startNewRound);
|
||||||
|
$("#confirm-modal-remove-combatant-ok-button").on("click", removeCombatant);
|
||||||
|
|
||||||
|
// add event listeners to damage sliders in combatant modal
|
||||||
|
$("#combatant-modal-stun").on("change", () => {
|
||||||
|
if ($("#combatant-modal-stun").val() == "10") {
|
||||||
|
$("#combatant-modal-penalty-stun").text("(K.O.)");
|
||||||
|
} else {
|
||||||
|
$("#combatant-modal-penalty-stun").text("(wound penalty -" + DAMAGE_PENALTY[$("#combatant-modal-stun").val()] + ")");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#combatant-modal-physical").on("change", () => {
|
||||||
|
if ($("#combatant-modal-physical").val() == "10") {
|
||||||
|
$("#combatant-modal-penalty-physical").text("(dead)");
|
||||||
|
} else {
|
||||||
|
$("#combatant-modal-penalty-physical").text("(wound penalty -" + DAMAGE_PENALTY[$("#combatant-modal-physical").val()] + ")");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// always focus name input field when combatant modal appears
|
||||||
|
$('#combatant-modal').on('shown.bs.modal', () => $('#combatant-modal-name').focus());
|
||||||
|
// always empty input fields when combatant modal disappears
|
||||||
|
$("#combatant-modal").on('hidden.bs.modal', () => $("input[id*='combatant-modal']").val(""));
|
||||||
|
// Hide damage monitors after click somewhere else
|
||||||
|
$("html").on("click", (e) => {
|
||||||
|
if ($(e.target).parents(".damage-monitor").length == 0) {
|
||||||
|
$(".damage-monitor.seen").removeClass("seen");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addTestCombatant();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { rollForInitiative, validateCombatant };
|
||||||
@ -1,3 +0,0 @@
|
|||||||
test_format = 1.0
|
|
||||||
|
|
||||||
[default]
|
|
||||||
BIN
tools/Icon Template/dice512.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
31
tools/Icon Template/tmp.css
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #004aa5;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
position: relative;
|
||||||
|
top: 100px;
|
||||||
|
|
||||||
|
|
||||||
|
border: 8px solid cyan;
|
||||||
|
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
background-color: rgba(0, 0, 0, .75);
|
||||||
|
box-shadow: 0 0 16px lightcyan, 0 0 32px lightcyan, 0 0 32px cyan, 0 0 64px cyan, 0 0 64px darkcyan, 0 0 128px darkcyan;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 384px;
|
||||||
|
height: 384px;
|
||||||
|
filter: invert(34%) sepia(78%) saturate(7014%) hue-rotate(316deg) brightness(125%) contrast(105%);
|
||||||
|
}
|
||||||
4
tools/Icon Template/tmp.html
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head><link type="text/css" rel="stylesheet" href="tmp.css"></head>
|
||||||
|
<body><div><button><img src="dice512.png"></button></div></body></html>
|
||||||
BIN
tools/Icon Template/tmp.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
tools/circuit_board_by_xxaries1970xx_d9ut9nd.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
tools/favicon-master-image.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
1
tools/faviconData.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"result":{"status":"success"},"favicon":{"package_url":"https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/favicon_package_v0.16.zip","files_urls":["https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/android-chrome-192x192.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/android-chrome-512x512.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/apple-touch-icon.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/browserconfig.xml","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/favicon-16x16.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/favicon-32x32.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/favicon.ico","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/mstile-150x150.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/site.webmanifest"],"html_code":"<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/icons/apple-touch-icon.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/icons/favicon-32x32.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/icons/favicon-16x16.png\">\n<link rel=\"manifest\" href=\"/icons/site.webmanifest\">\n<link rel=\"shortcut icon\" href=\"/icons/favicon.ico\">\n<meta name=\"msapplication-TileColor\" content=\"#004aa5\">\n<meta name=\"msapplication-config\" content=\"/icons/browserconfig.xml\">\n<meta name=\"theme-color\" content=\"#004aa5\">","compression":"false","overlapping_markups":["link[rel=\"apple-touch-icon\"]","link[rel=\"shortcut\"]","link[rel=\"shortcut icon\"]","link[rel=\"icon\",sizes=\"16x16\"]","link[rel=\"icon\",sizes=\"32x32\"]","meta[name=\"msapplication-TileColor\"]","meta[name=\"msapplication-config\"]","link[rel=\"manifest\"]","meta[name=\"theme-color\"]"]},"files_location":{"type":"path","path":"/icons"},"preview_picture_url":"https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/favicon_preview.png","version":"0.16"}
|
||||||
59
tools/faviconDescription.json
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"masterPicture": "tools/favicon-master-image.png",
|
||||||
|
"iconsPath": "/icons",
|
||||||
|
"design": {
|
||||||
|
"ios": {
|
||||||
|
"pictureAspect": "backgroundAndMargin",
|
||||||
|
"backgroundColor": "#004aa5",
|
||||||
|
"margin": "11%",
|
||||||
|
"assets": {
|
||||||
|
"ios6AndPriorIcons": false,
|
||||||
|
"ios7AndLaterIcons": false,
|
||||||
|
"precomposedIcons": false,
|
||||||
|
"declareOnlyDefaultIcon": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"desktopBrowser": {
|
||||||
|
"design": "raw"
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
"pictureAspect": "noChange",
|
||||||
|
"backgroundColor": "#004aa5",
|
||||||
|
"onConflict": "override",
|
||||||
|
"assets": {
|
||||||
|
"windows80Ie10Tile": false,
|
||||||
|
"windows10Ie11EdgeTiles": {
|
||||||
|
"small": false,
|
||||||
|
"medium": true,
|
||||||
|
"big": false,
|
||||||
|
"rectangle": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"androidChrome": {
|
||||||
|
"pictureAspect": "backgroundAndMargin",
|
||||||
|
"margin": "13%",
|
||||||
|
"backgroundColor": "#004aa5",
|
||||||
|
"themeColor": "#004aa5",
|
||||||
|
"manifest": {
|
||||||
|
"display": "standalone",
|
||||||
|
"orientation": "notSet",
|
||||||
|
"onConflict": "override",
|
||||||
|
"declared": true
|
||||||
|
},
|
||||||
|
"assets": {
|
||||||
|
"legacyIcon": false,
|
||||||
|
"lowResolutionIcons": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"scalingAlgorithm": "Mitchell",
|
||||||
|
"errorOnImageTooSmall": false,
|
||||||
|
"readmeFile": false,
|
||||||
|
"htmlCodeFile": false,
|
||||||
|
"usePathAsIs": false
|
||||||
|
}
|
||||||
|
}
|
||||||
1
tools/setup-jest.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
global.jQuery = global.$ = require('./jquery-latest.min.js');
|
||||||
23
tools/test.svg
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip-headtop">
|
||||||
|
<path d="M 0 144 v -144 h 216 v 112 h 80 v -112 h 216 v 144 Z" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-coattop">
|
||||||
|
<path d="M 100 264 a 144 132 0 0 0 312 0 h 100 v 256 h -512 v -256 Z" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect x="0" y="0" width="512" height="512" fill="green" stroke="none"/>
|
||||||
|
<path d="M 112 164 h 288 v 72 l -24 24 h -88 q -32 -128 -64 0 h -88 l -24 -24 v -72 Z" fill-rule="evenodd" /> <!-- glasses -->
|
||||||
|
<path d="M 136 280 a 132 164 0 0 0 240 0 Z" /> <!-- bottom of head -->
|
||||||
|
<path d="M 128 144 a 144 128 0 0 1 256 0 Z" clip-path="url(#clip-headtop)"/> <!-- top of head -->
|
||||||
|
<path d="M 232 0 h 48 v 96 h -48 Z" /> <!-- mohawk -->
|
||||||
|
<path d="M 64 304 h 384 v 48 l -64 176 h -256 l -64 -192 Z" clip-path="url(#clip-coattop)" /> <!-- coat -->
|
||||||
|
|
||||||
|
<path d="M 128 512 h -128 v -112 l 48 -32 l 48 144 Z" />
|
||||||
|
<path d="M 384 512 h 128 v -112 l -48 -32 l -48 144 Z" />
|
||||||
|
|
||||||
|
<!-- <path d="M 32 96 h 448 v 96 l -32 32 h -144 q -56 -160 -96 0 h -144 l -32 -32 v -96 Z" fill-rule="evenodd" /> glasses in big -->
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |