Sessions, death clock, automation
This commit is contained in:
parent
a446602cc7
commit
bd1e37ba34
21 changed files with 550 additions and 49 deletions
|
|
@ -1,8 +1,21 @@
|
|||
// Copyright (c) 2026, Vassili and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Character", {
|
||||
// refresh(frm) {
|
||||
frappe.ui.form.on("Character", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
// },
|
||||
type(frm) {
|
||||
frm.trigger('last_interaction');
|
||||
},
|
||||
last_interaction(frm) {
|
||||
frappe.db.get_value('Character Type', frm.doc.type, 'death_clock')
|
||||
.then(r => {
|
||||
if (r && r.message) {
|
||||
let last_int = moment(frm.doc.last_interaction);
|
||||
let death = last_int.clone().add(r.message.death_clock, 'days');
|
||||
frm.set_value('death', death.format('YYYY-MM-DD HH:mm:ss'));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@
|
|||
"field_order": [
|
||||
"character_name",
|
||||
"type",
|
||||
"profile_picture"
|
||||
"profile_picture",
|
||||
"last_interaction",
|
||||
"death"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -29,12 +31,24 @@
|
|||
"fieldname": "profile_picture",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Profile Picture"
|
||||
},
|
||||
{
|
||||
"fieldname": "last_interaction",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Last Interaction",
|
||||
"permlevel": 2
|
||||
},
|
||||
{
|
||||
"fieldname": "death",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Death",
|
||||
"permlevel": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-24 19:37:53.659927",
|
||||
"modified": "2026-03-31 19:09:46.094796",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Sei Time",
|
||||
"name": "Character",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class Character(Document):
|
|||
from frappe.types import DF
|
||||
|
||||
character_name: DF.Data
|
||||
death: DF.Datetime | None
|
||||
last_interaction: DF.Datetime | None
|
||||
profile_picture: DF.AttachImage | None
|
||||
type: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"type",
|
||||
"death_clock",
|
||||
"frame_image",
|
||||
"background_image"
|
||||
],
|
||||
|
|
@ -28,12 +29,17 @@
|
|||
"fieldname": "background_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Background Image"
|
||||
},
|
||||
{
|
||||
"fieldname": "death_clock",
|
||||
"fieldtype": "Int",
|
||||
"label": "Death Clock (days)"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-24 18:56:08.479742",
|
||||
"modified": "2026-03-31 19:03:42.942277",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Sei Time",
|
||||
"name": "Character Type",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class CharacterType(Document):
|
|||
from frappe.types import DF
|
||||
|
||||
background_image: DF.AttachImage | None
|
||||
death_clock: DF.Int
|
||||
frame_image: DF.AttachImage | None
|
||||
type: DF.Data
|
||||
# end: auto-generated types
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
// Copyright (c) 2026, Vassili and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Game", {
|
||||
// refresh(frm) {
|
||||
frappe.ui.form.on("Game", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
// },
|
||||
session_duration(frm) {
|
||||
frm.trigger('next_session_start');
|
||||
},
|
||||
next_session_start(frm) {
|
||||
let start = moment(frm.doc.next_session_start);
|
||||
let end = start.clone().add(frm.doc.session_duration, 'hours');
|
||||
frm.set_value('next_session_end', end.format('YYYY-MM-DD HH:mm:ss'));
|
||||
}
|
||||
});
|
||||
|
|
@ -8,11 +8,18 @@
|
|||
"field_order": [
|
||||
"title",
|
||||
"description",
|
||||
"section_break_nsqq",
|
||||
"next_session",
|
||||
"max_seats",
|
||||
"section_break_sial",
|
||||
"type",
|
||||
"select_vniu"
|
||||
"max_seats",
|
||||
"frequency",
|
||||
"session_duration",
|
||||
"section_break_next_session",
|
||||
"next_session_number",
|
||||
"next_session_start",
|
||||
"next_session_end",
|
||||
"schedule_session",
|
||||
"all_sessions_section",
|
||||
"all_sessions"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -27,15 +34,7 @@
|
|||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_nsqq",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "next_session",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Next Session"
|
||||
},
|
||||
{
|
||||
"default": "6",
|
||||
"fieldname": "max_seats",
|
||||
"fieldtype": "Int",
|
||||
"label": "Max Seats"
|
||||
|
|
@ -47,15 +46,67 @@
|
|||
"options": "Game Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "select_vniu",
|
||||
"fieldname": "section_break_sial",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "Weekly",
|
||||
"fieldname": "frequency",
|
||||
"fieldtype": "Select",
|
||||
"options": "January\nFebruary\nMarch"
|
||||
"label": "Frequency",
|
||||
"options": "Weekly\nBiweekly\nEtc."
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_next_session",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Next Session"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "next_session_number",
|
||||
"fieldtype": "Int",
|
||||
"label": "Next Session Number"
|
||||
},
|
||||
{
|
||||
"default": "now",
|
||||
"fieldname": "next_session_start",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Next Session Start"
|
||||
},
|
||||
{
|
||||
"default": "now",
|
||||
"fieldname": "next_session_end",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Next Session End",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "session_duration",
|
||||
"fieldtype": "Float",
|
||||
"label": "Session Duration (Hours)",
|
||||
"length": 3,
|
||||
"non_negative": 1,
|
||||
"precision": "1"
|
||||
},
|
||||
{
|
||||
"fieldname": "schedule_session",
|
||||
"fieldtype": "Button",
|
||||
"label": "Schedule Session"
|
||||
},
|
||||
{
|
||||
"fieldname": "all_sessions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "All Sessions"
|
||||
},
|
||||
{
|
||||
"fieldname": "all_sessions",
|
||||
"fieldtype": "HTML"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-24 19:02:39.174748",
|
||||
"modified": "2026-04-28 19:43:24.253848",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Sei Time",
|
||||
"name": "Game",
|
||||
|
|
@ -86,7 +137,7 @@
|
|||
],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "next_session",
|
||||
"sort_field": "next_session_start",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_datetime
|
||||
|
||||
from frappe.types import DF
|
||||
|
||||
class Game(Document):
|
||||
# begin: auto-generated types
|
||||
|
|
@ -16,9 +16,12 @@ class Game(Document):
|
|||
from frappe.types import DF
|
||||
|
||||
description: DF.SmallText | None
|
||||
frequency: DF.Literal["Weekly", "Biweekly", "Etc."]
|
||||
max_seats: DF.Int
|
||||
next_session: DF.Datetime | None
|
||||
select_vniu: DF.Literal["January", "February", "March"]
|
||||
next_session_end: DF.Datetime | None
|
||||
next_session_number: DF.Int
|
||||
next_session_start: DF.Datetime | None
|
||||
session_duration: DF.Float
|
||||
title: DF.Data | None
|
||||
type: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
|
@ -26,23 +29,22 @@ class Game(Document):
|
|||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_events(start, end, filters=None):
|
||||
def get_events(start:DF.Datetime, end:DF.Datetime, filters:str=None):
|
||||
event_docs = frappe.get_all(
|
||||
"Game",
|
||||
fields=["name", "title", "next_session"],
|
||||
fields=["name", "title", "next_session_start", "next_session_end"],
|
||||
filters=[
|
||||
["next_session", 'between', [start, end]],
|
||||
["next_session_start", 'between', [start, end]],
|
||||
]
|
||||
)
|
||||
|
||||
events = []
|
||||
for doc in event_docs:
|
||||
next_session = get_datetime(doc.next_session)
|
||||
events.append({
|
||||
"name": doc.name,
|
||||
"title": doc.title,
|
||||
"start": next_session,
|
||||
"end": next_session,
|
||||
"start": get_datetime(doc.next_session_start),
|
||||
"end": get_datetime(doc.next_session_end),
|
||||
})
|
||||
|
||||
return events
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
frappe.views.calendar["Game"] = {
|
||||
field_map: {
|
||||
"start": "next_session",
|
||||
"end": "next_session", // Optional
|
||||
"start": "start",
|
||||
"end": "end",
|
||||
"id": "name",
|
||||
"title": "title"
|
||||
},
|
||||
|
|
|
|||
0
st/sei_time/doctype/game_session/__init__.py
Normal file
0
st/sei_time/doctype/game_session/__init__.py
Normal file
8
st/sei_time/doctype/game_session/game_session.js
Normal file
8
st/sei_time/doctype/game_session/game_session.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2026, Vassili and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Game Session", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
98
st/sei_time/doctype/game_session/game_session.json
Normal file
98
st/sei_time/doctype/game_session/game_session.json
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:session_title",
|
||||
"creation": "2026-04-26 03:29:54.749610",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"game",
|
||||
"game_title",
|
||||
"session_title",
|
||||
"session_number",
|
||||
"session_start",
|
||||
"session_end",
|
||||
"notes_section",
|
||||
"notes"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "game",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Game",
|
||||
"options": "Game",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "game.title",
|
||||
"fieldname": "game_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Game Title",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "session_title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Session Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "session_number",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Session Number",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "session_start",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Session Start"
|
||||
},
|
||||
{
|
||||
"fieldname": "session_end",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Session End"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "notes_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Notes"
|
||||
},
|
||||
{
|
||||
"fieldname": "notes",
|
||||
"fieldtype": "Long Text"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-28 19:53:33.962004",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Sei Time",
|
||||
"name": "Game Session",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
26
st/sei_time/doctype/game_session/game_session.py
Normal file
26
st/sei_time/doctype/game_session/game_session.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright (c) 2026, Vassili and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class GameSession(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
game: DF.Link
|
||||
game_title: DF.Data | None
|
||||
notes: DF.LongText | None
|
||||
session_end: DF.Datetime | None
|
||||
session_number: DF.Int
|
||||
session_start: DF.Datetime | None
|
||||
session_title: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
22
st/sei_time/doctype/game_session/test_game_session.py
Normal file
22
st/sei_time/doctype/game_session/test_game_session.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright (c) 2026, Vassili and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
|
||||
class IntegrationTestGameSession(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for GameSession.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
@ -18,10 +18,131 @@
|
|||
}
|
||||
|
||||
.image-stack .fg {
|
||||
padding: 35px;
|
||||
padding: 50px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.image-stack .frame {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.deathclock-wrapper {
|
||||
min-height: 150px;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-family: "Courier New", monospace;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.deathclock {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.digit {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
height: 90px;
|
||||
perspective: 700px;
|
||||
}
|
||||
|
||||
.flipcard {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(#1d1d1d, #050505);
|
||||
color: #f2e7c9;
|
||||
font-size: 56px;
|
||||
font-weight: bold;
|
||||
line-height: 95px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flipcard::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
height: 2px;
|
||||
background: #000;
|
||||
box-shadow:
|
||||
0 -1px rgba(255,255,255,0.08),
|
||||
0 1px rgba(255,255,255,0.05);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.top, .bottom {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(#202020, #080808);
|
||||
color: #f2e7c9;
|
||||
font-size: 56px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.top {
|
||||
top: 0;
|
||||
line-height: 90px;
|
||||
border-radius: 10px 10px 0 0;
|
||||
transform-origin: bottom;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
bottom: 0;
|
||||
line-height: 0;
|
||||
border-radius: 0 0 10px 10px;
|
||||
transform-origin: top;
|
||||
}
|
||||
|
||||
.flip-top {
|
||||
animation: flipTop 0.28s ease-in forwards;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.flip-bottom {
|
||||
animation: flipBottom 0.28s ease-out forwards;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
@keyframes flipTop {
|
||||
from {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotateX(-90deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flipBottom {
|
||||
from {
|
||||
transform: rotateX(90deg);
|
||||
}
|
||||
to {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.shine {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 10px;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(
|
||||
115deg,
|
||||
rgba(255,255,255,0.12),
|
||||
transparent 35%,
|
||||
transparent 70%,
|
||||
rgba(255,255,255,0.05)
|
||||
);
|
||||
z-index: 10;
|
||||
}
|
||||
|
|
@ -6,24 +6,117 @@ frappe.pages['characters'].on_page_load = function(wrapper) {
|
|||
});
|
||||
|
||||
frappe.db.get_list('Character', {
|
||||
fields: ['name', 'type', 'profile_picture', 'type.frame_image', 'type.background_image'],
|
||||
fields: ['name', 'type', 'profile_picture', 'type.frame_image', 'type.background_image', 'death'],
|
||||
filters: {
|
||||
owner: frappe.session.user
|
||||
//owner: frappe.session.user
|
||||
}
|
||||
}).then(docs => {
|
||||
console.log(docs);
|
||||
render_cards(docs, wrapper);
|
||||
|
||||
const deathclocks = document.getElementsByClassName("deathclock");
|
||||
|
||||
function createDigit() {
|
||||
const digit = document.createElement("div");
|
||||
digit.className = "digit";
|
||||
digit.dataset.value = "0";
|
||||
|
||||
digit.innerHTML = `
|
||||
<div class="flipcard">0</div>
|
||||
<div class="shine"></div>
|
||||
`;
|
||||
|
||||
return digit;
|
||||
}
|
||||
|
||||
function flipDigit(digit, nextValue) {
|
||||
const currentValue = digit.dataset.value;
|
||||
if (currentValue === String(nextValue)) return;
|
||||
|
||||
const flipcard = digit.querySelector(".flipcard");
|
||||
|
||||
const topFlip = document.createElement("div");
|
||||
topFlip.className = "top flip-top";
|
||||
topFlip.textContent = currentValue;
|
||||
|
||||
const bottomFlip = document.createElement("div");
|
||||
bottomFlip.className = "bottom flip-bottom";
|
||||
bottomFlip.textContent = nextValue;
|
||||
|
||||
digit.appendChild(topFlip);
|
||||
|
||||
setTimeout(() => {
|
||||
flipcard.textContent = nextValue;
|
||||
digit.appendChild(bottomFlip);
|
||||
}, 140);
|
||||
|
||||
setTimeout(() => {
|
||||
topFlip.remove();
|
||||
bottomFlip.remove();
|
||||
digit.dataset.value = String(nextValue);
|
||||
}, 320);
|
||||
}
|
||||
|
||||
function countUpDigit(digit, target, delay = 0) {
|
||||
let value = 0;
|
||||
|
||||
setTimeout(() => {
|
||||
const timer = setInterval(() => {
|
||||
if (value >= target) {
|
||||
clearInterval(timer);
|
||||
return;
|
||||
}
|
||||
|
||||
value++;
|
||||
flipDigit(digit, value % 10);
|
||||
}, 360);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
function initDisplay(clock) {
|
||||
const value = clock.dataset.value || "0000";
|
||||
clock.innerHTML = "";
|
||||
|
||||
const digits = value.padStart(4, "0").slice(-4).split("");
|
||||
|
||||
digits.forEach((targetDigit, index) => {
|
||||
const digit = createDigit();
|
||||
clock.appendChild(digit);
|
||||
|
||||
countUpDigit(
|
||||
digit,
|
||||
Number(targetDigit),
|
||||
index * 160
|
||||
);
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < deathclocks.length; i++) {
|
||||
initDisplay(deathclocks.item(i));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function render_cards(docs, wrapper) {
|
||||
const container = $('<div class="row m-2"></div>');
|
||||
|
||||
function days_until(deathdate) {
|
||||
const targetDate = new Date(deathdate.replace(' ', 'T'));
|
||||
const now = new Date();
|
||||
|
||||
const diffMs = targetDate - now;
|
||||
|
||||
const diffDays = diffMs / (1000 * 60 * 60 * 24);
|
||||
|
||||
return Math.round(diffDays);
|
||||
}
|
||||
|
||||
docs.forEach(doc => {
|
||||
const card = $(`
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card p-3">
|
||||
<h5>${doc.name}</h5>
|
||||
<div class="deathclock-wrapper">
|
||||
<div class="deathclock" data-value="${days_until(doc.death)}"></div>
|
||||
</div>
|
||||
<h5><a href="/desk/character/${doc.name}">${doc.name}</a></h5>
|
||||
<p>${doc.type}</p>
|
||||
<div class="image-stack">
|
||||
<img src="${doc.background_image}" class="layer bg">
|
||||
|
|
|
|||
0
st/sei_time/web_template/__init__.py
Normal file
0
st/sei_time/web_template/__init__.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"__unsaved": 1,
|
||||
"creation": "2026-03-31 21:28:46.529250",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Template",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "subtitle",
|
||||
"fieldtype": "Data",
|
||||
"label": "Subtitle",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "bullets",
|
||||
"fieldtype": "Table Break",
|
||||
"label": "Bullets",
|
||||
"options": "",
|
||||
"reqd": 0
|
||||
}
|
||||
],
|
||||
"idx": 0,
|
||||
"modified": "2026-03-31 21:34:31.754127",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Sei Time",
|
||||
"name": "Section with Bullets",
|
||||
"owner": "Administrator",
|
||||
"standard": 1,
|
||||
"template": "",
|
||||
"type": "Section"
|
||||
}
|
||||
|
|
@ -6,16 +6,16 @@
|
|||
"creation": "2026-03-17 20:43:59.339705",
|
||||
"custom": 0,
|
||||
"custom_overrides": "",
|
||||
"custom_scss": ".web-footer {\r\n background-color: #2c003e;\r\n color: gold;\r\n padding: 1rem;\r\n text-align: center;\r\n margin-top: auto;\r\n}\r\n\r\n.footer-link {\r\n margin: 0 0.5rem;\r\n}\r\n\r\n.footer-separator {\r\n margin: 0 0.5rem;\r\n}\r\n\r\n.footer-info {\r\n font-size: 0.9rem;\r\n}",
|
||||
"dark_color": "Primary",
|
||||
"custom_scss": "body {\n background-color: $body-bg !important;\n}\n\n.navbar,\n.navbar-main,\n.web-footer,\n.navbar .container,\n.web-footer .container {\n background-color: $dark !important;\n}\n\n.navbar, .web-footer {\n color: $body-text-color;\n text-align: center;\n}\n.web-footer {\n margin-top: auto;\n padding: 1rem;\n}\n.footer-link {\n margin: 0 0.5rem;\n}\n.footer-separator {\n margin: 0 0.5rem;\n}\n.footer-info {\n font-size: 0.9rem;\n}\n\n.navbar-nav .nav-link.active,\n.navbar .nav-item.active > a,\n.nav-item a.current,\n.navbar-nav .nav-link[aria-current=\"page\"],\na.nav-link.active {\n color: #fefce8 !important;\n text-shadow: \n 0 0 8px $light,\n 0 0 16px $light,\n 0 0 24px $light !important;\n transition: all 0.4s ease;\n position: relative;\n font-weight: 600;\n}\n\n.navbar-nav .nav-link.active::after {\n content: '';\n position: absolute;\n bottom: -2px;\n left: 50%;\n transform: translateX(-50%);\n width: 60%;\n height: 2px;\n background: linear-gradient(to right, transparent, $light, transparent);\n box-shadow: 0 0 12px $light;\n opacity: 0.9;\n}\n\n.navbar-nav .nav-link.active:hover,\n.navbar-nav .nav-link[aria-current=\"page\"]:hover {\n animation: gold-shimmer 2.2s infinite linear;\n text-shadow: \n 0 0 10px $light,\n 0 0 20px $light,\n 0 0 30px $light;\n}\n\n@keyframes gold-shimmer {\n 0% { text-shadow: 0 0 8px $light, 0 0 16px $light, 0 0 24px $light; }\n 50% { text-shadow: 0 0 12px $light, 0 0 25px $light, 0 0 40px $light; }\n 100% { text-shadow: 0 0 8px $light, 0 0 16px $light, 0 0 24px $light; }\n}\n\n.navbar-nav .nav-link:hover:not(.active) {\n color: $light !important;\n text-shadow: 0 0 8px $light;\n}\n\nh1 {\n color: #e0b030 !important;\n}\n\nh2, h3, h4, h5, h6,\n.lead, .subtitle, .welcome-text,\nstrong, b {\n color: #c0262e !important;\n}\n\np, span, div, td, th,\n.text-muted, .text-secondary, .text-light,\n.small, small, .muted {\n color: $light !important;\n}\n\n.user-dropdown,\n.dropdown-menu,\n.user-dropdown-menu,\n.nav-item .dropdown-menu {\n background-color: $dark !important;\n border: 2px solid $light !important;\n border-radius: 8px !important;\n box-shadow: 0 4px 15px rgba(252, 211, 77, 0.3) !important;\n padding: 0.5rem 0 !important;\n}\n\n.user-dropdown a,\n.user-dropdown button,\n.dropdown-menu a,\n.dropdown-menu button,\n.user-dropdown-menu a {\n color: $light !important;\n background-color: transparent !important;\n padding: 0.65rem 1.25rem !important;\n font-weight: 500;\n}\n\n.user-dropdown a:hover,\n.user-dropdown button:hover,\n.dropdown-menu a:hover,\n.dropdown-menu button:hover {\n background-color: $primary !important;\n color: #fefce8 !important;\n border-radius: 4px;\n}\n\nli,\nul li,\nol li {\n color: #d1d5db !important;\n}\n\n.navbar-nav .nav-link,\n.navbar .nav-link,\n.navbar a {\n color: $body-text-color !important;\n}\n\na:not(.nav-link, .footer-link) {\n color: lighten($body-text-color, 30%) !important;\n font-weight: bolder;\n font-variant: small-caps;\n}\n\na:not(.nav-link, .footer-link):hover {\n color: #fefce8 !important;\n text-shadow: 0 0 8px $light;\n}",
|
||||
"dark_color": "Dark Purple",
|
||||
"docstatus": 0,
|
||||
"doctype": "Website Theme",
|
||||
"font_properties": "wght@400..900",
|
||||
"google_font": "Orbitron",
|
||||
"idx": 0,
|
||||
"ignored_apps": [],
|
||||
"light_color": "Gold Text",
|
||||
"modified": "2026-03-24 19:13:02.880572",
|
||||
"light_color": "Light Gold",
|
||||
"modified": "2026-04-14 21:27:58.487894",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Sei Time",
|
||||
"name": "Custom",
|
||||
|
|
@ -23,6 +23,6 @@
|
|||
"primary_color": "Primary",
|
||||
"text_color": "Gold Text",
|
||||
"theme": "Custom",
|
||||
"theme_scss": "\n@import url(\"https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap\");\n// backward compatibility. deprecated in v15\n$font-family-sans-serif: \"Orbitron\", \"InterVariable\", \"Inter\", -apple-system, BlinkMacSystemFont,\n\t\"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\",\n\t\"Droid Sans\", \"Helvetica Neue\", sans-serif;\n\n// override font stack if custom font is set in website theme\n:root {\n\t--font-stack: \"Orbitron\", \"InterVariable\", \"Inter\", -apple-system, BlinkMacSystemFont,\n\t\"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\",\n\t\"Droid Sans\", \"Helvetica Neue\", sans-serif !important;\n}\n$primary: #8139c4;$dark: #8139c4;$body-text-color: #FFD700;$body-bg: #4e3269;$enable-shadows: false;\n$enable-gradients: false;\n$enable-rounded: true;\n\n// Bootstrap Variable Overrides\n\n\n// Import themes from installed apps\n@import \"frappe/public/scss/website.bundle\";\n\n\n\n// Custom Theme\n.web-footer {\r\n background-color: #2c003e;\r\n color: gold;\r\n padding: 1rem;\r\n text-align: center;\r\n margin-top: auto;\r\n}\r\n\r\n.footer-link {\r\n margin: 0 0.5rem;\r\n}\r\n\r\n.footer-separator {\r\n margin: 0 0.5rem;\r\n}\r\n\r\n.footer-info {\r\n font-size: 0.9rem;\r\n}\n\n:root {\n\t\n\t--primary: #{$primary};\n\t--primary-color: #{$primary};\n\t\n\t--bg-color: #{$body-bg};\n\t\n\t--text-color: #{$body-text-color};\n\t--text-light: #{$body-text-color};\n\t}\n",
|
||||
"theme_scss": "\n@import url(\"https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap\");\n// backward compatibility. deprecated in v15\n$font-family-sans-serif: \"Orbitron\", \"InterVariable\", \"Inter\", -apple-system, BlinkMacSystemFont,\n\t\"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\",\n\t\"Droid Sans\", \"Helvetica Neue\", sans-serif;\n\n// override font stack if custom font is set in website theme\n:root {\n\t--font-stack: \"Orbitron\", \"InterVariable\", \"Inter\", -apple-system, BlinkMacSystemFont,\n\t\"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\",\n\t\"Droid Sans\", \"Helvetica Neue\", sans-serif !important;\n}\n$primary: #4c1d95;$body-text-color: #facc15;$light: #fcd34d;$dark: #2c003e;$body-bg: #1f002b;$enable-shadows: false;\n$enable-gradients: false;\n$enable-rounded: true;\n\n// Bootstrap Variable Overrides\n\n\n// Import themes from installed apps\n@import \"frappe/public/scss/website.bundle\";\n\n\n\n// Custom Theme\nbody {\n background-color: $body-bg !important;\n}\n\n.navbar,\n.navbar-main,\n.web-footer,\n.navbar .container,\n.web-footer .container {\n background-color: $dark !important;\n}\n\n.navbar, .web-footer {\n color: $body-text-color;\n text-align: center;\n}\n.web-footer {\n margin-top: auto;\n padding: 1rem;\n}\n.footer-link {\n margin: 0 0.5rem;\n}\n.footer-separator {\n margin: 0 0.5rem;\n}\n.footer-info {\n font-size: 0.9rem;\n}\n\n.navbar-nav .nav-link.active,\n.navbar .nav-item.active > a,\n.nav-item a.current,\n.navbar-nav .nav-link[aria-current=\"page\"],\na.nav-link.active {\n color: #fefce8 !important;\n text-shadow: \n 0 0 8px $light,\n 0 0 16px $light,\n 0 0 24px $light !important;\n transition: all 0.4s ease;\n position: relative;\n font-weight: 600;\n}\n\n.navbar-nav .nav-link.active::after {\n content: '';\n position: absolute;\n bottom: -2px;\n left: 50%;\n transform: translateX(-50%);\n width: 60%;\n height: 2px;\n background: linear-gradient(to right, transparent, $light, transparent);\n box-shadow: 0 0 12px $light;\n opacity: 0.9;\n}\n\n.navbar-nav .nav-link.active:hover,\n.navbar-nav .nav-link[aria-current=\"page\"]:hover {\n animation: gold-shimmer 2.2s infinite linear;\n text-shadow: \n 0 0 10px $light,\n 0 0 20px $light,\n 0 0 30px $light;\n}\n\n@keyframes gold-shimmer {\n 0% { text-shadow: 0 0 8px $light, 0 0 16px $light, 0 0 24px $light; }\n 50% { text-shadow: 0 0 12px $light, 0 0 25px $light, 0 0 40px $light; }\n 100% { text-shadow: 0 0 8px $light, 0 0 16px $light, 0 0 24px $light; }\n}\n\n.navbar-nav .nav-link:hover:not(.active) {\n color: $light !important;\n text-shadow: 0 0 8px $light;\n}\n\nh1 {\n color: #e0b030 !important;\n}\n\nh2, h3, h4, h5, h6,\n.lead, .subtitle, .welcome-text,\nstrong, b {\n color: #c0262e !important;\n}\n\np, span, div, td, th,\n.text-muted, .text-secondary, .text-light,\n.small, small, .muted {\n color: $light !important;\n}\n\n.user-dropdown,\n.dropdown-menu,\n.user-dropdown-menu,\n.nav-item .dropdown-menu {\n background-color: $dark !important;\n border: 2px solid $light !important;\n border-radius: 8px !important;\n box-shadow: 0 4px 15px rgba(252, 211, 77, 0.3) !important;\n padding: 0.5rem 0 !important;\n}\n\n.user-dropdown a,\n.user-dropdown button,\n.dropdown-menu a,\n.dropdown-menu button,\n.user-dropdown-menu a {\n color: $light !important;\n background-color: transparent !important;\n padding: 0.65rem 1.25rem !important;\n font-weight: 500;\n}\n\n.user-dropdown a:hover,\n.user-dropdown button:hover,\n.dropdown-menu a:hover,\n.dropdown-menu button:hover {\n background-color: $primary !important;\n color: #fefce8 !important;\n border-radius: 4px;\n}\n\nli,\nul li,\nol li {\n color: #d1d5db !important;\n}\n\n.navbar-nav .nav-link,\n.navbar .nav-link,\n.navbar a {\n color: $body-text-color !important;\n}\n\na:not(.nav-link, .footer-link) {\n color: lighten($body-text-color, 30%) !important;\n font-weight: bolder;\n font-variant: small-caps;\n}\n\na:not(.nav-link, .footer-link):hover {\n color: #fefce8 !important;\n text-shadow: 0 0 8px $light;\n}\n\n:root {\n\t\n\t--primary: #{$primary};\n\t--primary-color: #{$primary};\n\t\n\t--bg-color: #{$body-bg};\n\t\n\t--text-color: #{$body-text-color};\n\t--text-light: #{$body-text-color};\n\t}\n",
|
||||
"theme_url": "/files/website_theme/custom_style.css"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue