commit 7d0c459b77297d63623c53489b9c8f809a912536 Author: OleSTEEP Date: Fri Sep 12 01:04:32 2025 +0300 frontend sketch on pure web diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68729d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +.DS_Store \ No newline at end of file diff --git a/vnshed/types.py b/vnshed/types.py new file mode 100644 index 0000000..a878c78 --- /dev/null +++ b/vnshed/types.py @@ -0,0 +1,21 @@ +from datetime import datetime + +class Novel: + title: str + description: str + + vndb: int + hours_to_read: int + + tags: list[str] + genres: list[str] + + tg_post: str #url::Url + post_at: datetime + +class FullNovel: + data: Novel + + upload_queue: list[str] + files: list[str] + screenshots: list[str] \ No newline at end of file diff --git a/www/css/calendar.css b/www/css/calendar.css new file mode 100644 index 0000000..9545bf0 --- /dev/null +++ b/www/css/calendar.css @@ -0,0 +1,75 @@ +ul {list-style-type: none;} + +.calendar * { + margin: 0; +} + +.month { + width: 100%; + text-align: center; + align-content: center; +} + +.month ul { + margin: 0; + padding: 0.5rem; +} + +.month ul li { + display: inline; + text-transform: uppercase; + letter-spacing: 3px; +} + +.month .prev { + float: inline-start; + cursor: pointer; +} + +.month .next { + float: inline-end; + cursor: pointer; +} + +.weekdays { + padding: 10px 0 0 0; +} + +.weekdays li { + display: inline-block; + width: 13.6%; + text-align: center; +} + +.days { + padding: 10px 0; + margin: 0; +} + +.days li { + list-style-type: none; + display: inline-block; + width: 13.6%; + text-align: center; + margin-bottom: 5px; + font-size: smaller; + cursor: pointer; +} + +.days li .active { + padding: 5px; + border-radius: 30%; +} + +@media screen and (max-width: 720px) { + .weekdays li, .days li {width: 13.1%;} +} + +@media screen and (max-width: 420px) { + .weekdays li, .days li {width: 12.5%;} + .days li .active {padding: 2px;} +} + +@media screen and (max-width: 290px) { + .weekdays li, .days li {width: 12.2%;} +} \ No newline at end of file diff --git a/www/css/global.css b/www/css/global.css new file mode 100644 index 0000000..1da034c --- /dev/null +++ b/www/css/global.css @@ -0,0 +1,195 @@ +@font-face { + font-family: 'Inter'; + src: url('../font/Inter-Light.woff2') format('woff2'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Lobster'; + src: url('../font/Lobster-Regular.ttf') format('ttf'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +/* Шрифт для всей страницы */ +* { + font-family: Inter; + font-size: large; +} + +body { + margin: 0; +} + +button { + border: 0; +} + +/* Все элементы для ввода данных (input, textarea, собственные) */ +.input { + border: 0; + resize: none; + padding: 0.5rem; +} + +/* Круглый элемент */ +.round { + border-radius: 100%; + overflow: hidden; +} + +/* Закруглённый элемент */ +.rounded { + border-radius: 0.75rem; + overflow: hidden; +} + +/* Вертикальное изображение */ +.vertical { + aspect-ratio: 11 / 16; +} + +/* Горизонтальное изображение */ +.horisontal { + aspect-ratio: 16 / 11; +} + +.image { + margin-left: auto; + margin-right: auto; + align-content: center; + text-align: center; + width: 11rem; +} + +/* Кнопка "плюс" */ +.plus { + font-family: Arial, Helvetica, sans-serif; + aspect-ratio: 1 / 1; + height: 32px; + font-size: x-large; + cursor: pointer; +} + +/* Кнопка "плюс" на изображении должна быть больше */ +.image .plus { + height: 48px; +} + +/* Поле с обложкой */ +.cover { + padding: 2rem; + text-align: center; + align-content: center; +} + +/* Поле с кнопкой "Создать" */ +.create { + width: 100%; + position: fixed; + bottom: 0; + text-align: center; + display: flex; + flex-direction: column; +} + +.create button { + margin: 0.5rem; + padding: 1rem; + cursor: pointer; +} + +/* Поле с заполняемыми полями */ +.fields { + width: 100%; + height: fit-content; + display: flex; + flex-direction: column; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + padding-bottom: 5rem; +} + +/* Отступ между полями */ +.fields * { + margin: 1rem; + margin-bottom: 0; +} + +/* Вертикальный список */ +.horisontal-bar { + display: flex; + overflow-x: scroll; + align-content: center; + padding: 0.5rem; +} + +.horisontal-bar * { + margin: 0; + margin-right: 0.5rem; + align-content: center; +} + +/* Элемент вертикального списка (тег, бадж, жанр) */ +.horisontal-item { + padding: 0.25rem; + cursor: pointer; +} + +/* Элемент в очереди */ +.queue-item { + margin: 0; + padding: 0; +} + +.queue-item + .queue-item { + margin-top: 0.5rem; +} + +.queue-item p { + margin: 0.5rem; +} + +/* Текст с троеточием в конце */ +.ellipsis { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Кнопка с изображением */ +.imgbutton { + height: 100%; + display: flex; + flex-direction: column; + margin: 0; + align-content: center; + text-align: center; + font-size: small; + padding: 0.5rem; +} + +.imgbutton * { + margin: 0; +} + +.imgbutton img { + margin-left: auto; + margin-right: auto; + width: 2rem; +} + +/* Кнопка "Удалить" */ +.remove { + cursor: pointer; +} + +/* Кнопка "Изменить" */ +.edit { + cursor: pointer; + margin-left: auto; +} \ No newline at end of file diff --git a/www/css/themes.css b/www/css/themes.css new file mode 100644 index 0000000..f63c4a6 --- /dev/null +++ b/www/css/themes.css @@ -0,0 +1,233 @@ +@media (prefers-color-scheme: light) { + body { + background-color: #EFEFF3; + } + + p { + color: black; + } + + .input { + background-color: #F4F4F4; + color: black; + } + + .input::placeholder { + color: black; + } + + .plus { + background-color: #5C8DC0; + color: white; + } + + .plus:hover { + background-color: #567aa1; + } + + .horisontal-item { + background-color: #5C8DC0; + color: white; + } + + .horisontal-item:hover { + background-color: #567aa1; + } + + .cover .image { + background-color: white; + } + + .create { + background-color: #D9D9D9; + } + + .create button { + background-color: #3CAA36; + color: white; + } + + .create button:hover { + background-color: #3b8d37; + color: white; + } + + .fields { + background-color: white; + } + + .queue-item { + background-color: #5C8DC0; + color: white; + } + + .queue-item p { + color: white; + } + + .imgbutton { + color: white; + } + + .month { + background: #5C8DC0; + } + + .month ul li { + color: white; + } + + .weekdays { + background-color: #F4F4F4; + } + + .weekdays li { + color: black; + } + + .days { + background: #F4F4F4; + } + + .days li { + color: black; + } + + .days li .active { + color: white !important; + background: #5C8DC0; + } + + .remove { + background-color: #FC1F1C; + } + + .remove:hover { + background-color: #d32522; + } + + .edit { + background-color: #1BC304; + } + + .edit:hover { + background-color: #1c9d0b; + } +} + +@media (prefers-color-scheme: dark) { + body { + background-color: #0F1011; + } + + p { + color: white; + } + + .input { + background-color: #192431; + color: white; + } + + .input::placeholder { + color: white; + } + + .plus { + background-color: #2791FF; + color: white; + } + + .plus:hover { + background-color: #2c7dd4; + } + + .horisontal-item { + background-color: #2791FF; + color: white; + } + + .horisontal-item:hover { + background-color: #2c7dd4; + } + + .cover .image { + background-color: #131A22; + } + + .create { + background-color: #192431; + } + + .create button { + background-color: #3CAA36; + color: white; + } + + .create button:hover { + background-color: #3b8d37; + color: white; + } + + .fields { + background-color: #131A22; + } + + .queue-item { + background-color: #2791FF; + color: white; + } + + .queue-item p { + color: white; + } + + .imgbutton { + color: white; + } + + .month { + background: #2791FF; + } + + .month ul li { + color: white; + } + + .weekdays { + background-color: #192431; + } + + .weekdays li { + color: white; + } + + .days { + background: #192431; + } + + .days li { + color: white; + } + + .days li .active { + color: white !important; + background: #2791FF; + } + + .remove { + background-color: #FC1F1C; + } + + .remove:hover { + background-color: #d32522; + } + + .edit { + background-color: #1BC304; + } + + .edit:hover { + background-color: #1c9d0b; + } +} \ No newline at end of file diff --git a/www/font/Inter-Light.woff2 b/www/font/Inter-Light.woff2 new file mode 100644 index 0000000..f3e012a Binary files /dev/null and b/www/font/Inter-Light.woff2 differ diff --git a/www/img/delete.svg b/www/img/delete.svg new file mode 100644 index 0000000..3a9a308 --- /dev/null +++ b/www/img/delete.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/www/img/edit.svg b/www/img/edit.svg new file mode 100644 index 0000000..e0a8cb9 --- /dev/null +++ b/www/img/edit.svg @@ -0,0 +1,109 @@ + + diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..4643eb9 --- /dev/null +++ b/www/index.html @@ -0,0 +1,95 @@ + + + Отложка + + + + + + + + + + + + +
+
+ +
+

Обложка

+
+ +
+ + + + + + + +
+

Время на чтение

+ +
+ + + + + + + + + + + +

Скриншоты

+ + + +

Дата публикации

+ + + +

Очередь публикации

+
+ + +
+
+ + +
+ +
+ + + + + \ No newline at end of file diff --git a/www/js/calendar.js b/www/js/calendar.js new file mode 100644 index 0000000..d1f6189 --- /dev/null +++ b/www/js/calendar.js @@ -0,0 +1,8 @@ + +document.getElementById("cal-prev").addEventListener("click", (event) => { + console.log("cal-prev"); +}) + +document.getElementById("cal-next").addEventListener("click", (event) => { + console.log("cal-next"); +}) \ No newline at end of file diff --git a/www/js/functions.js b/www/js/functions.js new file mode 100644 index 0000000..5ca1bd3 --- /dev/null +++ b/www/js/functions.js @@ -0,0 +1,46 @@ +function makeid(length) { + var result = ''; + var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var charactersLength = characters.length; + for ( var i = 0; i < length; i++ ) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +const imageTypes = { + types: [ + { + description: "Images", + accept: { + "image/*": [".png", ".gif", ".jpeg", ".jpg"], + }, + }, + ], + excludeAcceptAllOption: true, + multiple: false, +}; + +let fileHandle; +async function loadImage() { + [fileHandle] = await window.showOpenFilePicker(imageTypes); + return fileHandle; +} + +function addImage(initiator, target) { + loadImage().then(async (result) => { + let img = document.createElement("img"); + img.src = URL.createObjectURL(await result.getFile()); + document.getElementById(target).appendChild(img); + initiator = document.getElementById(initiator); + if (initiator) initiator.remove(); + //document.getElementById(target).addEventListener("click", addImage("", target)); + }); +} + +function json_filter(key,value) +{ + if (key=="html") return undefined; + //else if (key=="id") return undefined; + else return value; +} \ No newline at end of file diff --git a/www/js/index.js b/www/js/index.js new file mode 100644 index 0000000..c4df95a --- /dev/null +++ b/www/js/index.js @@ -0,0 +1,12 @@ + +document.getElementById("cover-plus").addEventListener("click", (event) => { + addImage("cover-plus", "cover-div"); +}) + +// document.getElementById("screen-plus").addEventListener("click", (event) => { +// addImage("screen-plus", "scr-1"); +// }) + +document.getElementById("create").addEventListener("click", (event) => { + console.log("create"); +}) \ No newline at end of file diff --git a/www/js/types.js b/www/js/types.js new file mode 100644 index 0000000..41a898e --- /dev/null +++ b/www/js/types.js @@ -0,0 +1,249 @@ +class AddButton { + + id = ""; + html = undefined; + + constructor() { + this.html = document.createElement('button'); + this.html.classList.add("plus"); + this.html.classList.add("round"); + this.html.appendChild(document.createTextNode("+")); + this.id = makeid(8); + this.html.id = this.id; + } + + addHadler(func) { + this.html.addEventListener("click", func); + return this; + } + +} + +class HorisontalItem { + + id = undefined; + html = undefined; + text = undefined; + + constructor(text) { + this.text = text; + this.html = document.createElement('div'); + this.html.classList.add("horisontal-item"); + this.html.classList.add("rounded"); + this.html.appendChild(document.createTextNode(text)); + this.id = makeid(8); + this.html.id = this.id; + this.html.addEventListener("click", this.remove); + } + + remove() { + document.getElementById(this.id).remove(); + } +} + +class HorisontalBar { + + html = undefined; + plus_id = undefined; + elements = []; + + constructor(text, id) { + this.html = document.createElement('div'); + this.html.classList.add("horisontal-bar"); + this.html.classList.add("input"); + this.html.classList.add("rounded"); + let label = document.createElement('p'); + label.appendChild(document.createTextNode(text)) + this.html.appendChild(label); + const plus = new AddButton().addHadler( + () => this.append(new HorisontalItem('test')) + ); + this.html.appendChild(plus.html); + this.plus_id = plus.id; + } + + append(elem) { + this.elements.push(elem); + this.html.insertBefore(elem.html, document.getElementById(this.plus_id)); + return this; + } + + json() { + return JSON.stringify(this.elements, json_filter); + } +} + +class HorisontalImage { + + id = undefined; + html = undefined; + + constructor() { + this.html = document.createElement('div'); + this.html.classList.add("horisontal"); + this.html.classList.add("image"); + this.html.classList.add("input"); + this.html.classList.add("rounded"); + this.id = makeid(8); + this.html.id = this.id; + + let plus = new AddButton(); + plus.addHadler(() => { + addImage(plus.id, this.id); + document.getElementById("screenshots").appendChild(new HorisontalImage().html); + }); + this.html.appendChild(plus.html); + } + + remove() { + document.getElementById(this.id).remove(); + } +} + +class HorisontalImageBar { + + html = undefined; + first_id = undefined; + elements = []; + + constructor(id) { + this.html = document.createElement('div'); + this.html.classList.add("horisontal-bar"); + const first = new HorisontalImage(); + this.html.appendChild(first.html); + this.first_id = first.id; + } + + append(elem) { + this.elements.push(elem); + this.html.insertBefore(elem.html, document.getElementById(this.first_id)); + return this; + } + + json() { + return JSON.stringify(this.elements, json_filter); + } +} + +class ImgButton { + + html = undefined; + id = undefined; + + constructor(name, img) { + this.html = document.createElement('div'); + this.html.classList.add("imgbutton"); + this.id = makeid(8); + this.html.id = this.id; + + let btn_img = document.createElement('img'); + btn_img.src = img; + btn_img.innerHTML = name; + this.html.appendChild(btn_img); + } + + addHadler(func) { + this.html.addEventListener("click", func); + return this; + } + + setCssClass(name) { + this.html.classList.add(name); + } + +} + +class QueueItem { + + html = undefined; + id = undefined; + + constructor(date, name) { + this.html = document.createElement('div'); + this.html.classList.add("queue-item"); + this.html.classList.add("horisontal-bar"); + this.html.classList.add("rounded"); + this.id = makeid(8); + this.html.id = this.id; + + var date_p = document.createElement('p'); + date_p.innerHTML = date; + this.html.appendChild(date_p); + + var text = document.createElement('p'); + text.classList.add("ellipsis"); + text.innerHTML = name; + this.html.appendChild(text); + + let edit_btn = new ImgButton("Изменить", "./img/edit.svg"); + edit_btn.setCssClass("edit"); + this.html.appendChild(edit_btn.html); + let rem_btn = new ImgButton("Удалить", "./img/delete.svg"); + rem_btn.setCssClass("remove"); + this.html.appendChild(rem_btn.html); + } + +} + +class Calendar { + + html = undefined; + id = undefined; + + constructor() { + this.html = document.createElement('div'); + this.html.classList.add("calendar"); + this.html.classList.add("rounded"); + this.id = makeid(8); + this.html.id = this.id; + + // Calendar Header + let month = document.createElement('div'); + month.classList.add("month"); + let m_header = document.createElement('ul'); + let prev_btn = document.createElement('li'); + prev_btn.classList.add("prev"); + prev_btn.id = this.id + "-prev"; + prev_btn.innerHTML = "❮"; + m_header.appendChild(prev_btn); + let next_btn = document.createElement('li'); + next_btn.classList.add("next"); + next_btn.id = this.id + "-next"; + next_btn.innerHTML = "❯"; + m_header.appendChild(next_btn); + let month_text = document.createElement('li'); + month_text.innerHTML = "Август 2025"; + m_header.appendChild(month_text); + month.appendChild(m_header); + this.html.appendChild(month); + + // Week header + let weekdays = document.createElement('ul'); + weekdays.classList.add("weekdays"); + const days = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]; + for (const day of days) { + let day_li = document.createElement('li'); + day_li.innerHTML = day; + weekdays.appendChild(day_li); + } + this.html.appendChild(weekdays); + + let day_table = document.createElement('ul'); + day_table.classList.add("days"); + const current = 10; + for (const day of [...Array(31).keys()]) { + let day_li = document.createElement('li'); + if (day+1 == current) { + let day_sp = document.createElement('span'); + day_sp.classList.add("active"); + day_sp.innerHTML = day+1; + day_li.appendChild(day_sp); + } else { + day_li.innerHTML = day+1; + } + day_table.appendChild(day_li); + } + this.html.appendChild(day_table); + } + +} \ No newline at end of file