Initial commit

This commit is contained in:
2026-02-14 22:37:01 +03:00
commit e7c09c298d
14 changed files with 8902 additions and 0 deletions

27
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Create Release
on:
push:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 21
- name: Install dependencies
run: npm install
- name: Run semantic-release
id: release
run: npm run semantic-release
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/node_modules

20
README.md Normal file
View File

@@ -0,0 +1,20 @@
# Tolfin
CSS-тема для Jellyfin.
## Установка
1. Скачайте `theme/base.css`
2. Добавьте его в настройки Jellyfin: Dashboard → General → Custom CSS
3. Вставьте содержимое файла
## Особенности
- Модернизированный дизайн
- Кастомные шрифты
- Плавные анимации
- Адаптивность
## Лицензия
MIT

BIN
assets/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

BIN
assets/screenshots/home.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

7926
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

17
package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "jamfin",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"semantic-release": "semantic-release"
},
"devDependencies": {
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^10.0.5",
"axios": "^1.7.2",
"conventional-changelog-conventionalcommits": "^8.0.0",
"semantic-release": "^24.0.0"
}
}

63
release.config.js Normal file
View File

@@ -0,0 +1,63 @@
const branch = process.env.CURRENT_BRANCH || "main";
if (!branch) {
throw new Error("CURRENT_BRANCH not set");
}
/**
* @type {import("semantic-release").GlobalConfig}
*/
const config = {
branches: ["main"],
repositoryUrl: "https://github.com/JamsRepos/Jamfin.git",
plugins: [
[
"@semantic-release/commit-analyzer",
{
releaseRules: [
{ scope: "no-release", release: false },
{ type: "build", release: "patch" },
{ type: "ci", release: "patch" },
{ type: "chore", release: "patch" },
{ type: "docs", release: false },
{ type: "refactor", release: "patch" },
{ type: "style", release: "patch" },
{ breaking: true, release: "major" },
],
},
],
[
"@semantic-release/release-notes-generator",
{
preset: "conventionalcommits",
presetConfig: {
types: [
{ type: "feat", section: "New Features" },
{ type: "fix", section: "Bug Fixes" },
{ type: "perf", section: "Performance Improvements", hidden: false },
{ type: "revert", section: "Commit Reverts", hidden: false },
{ type: "build", section: "Build System", hidden: false },
{ type: "ci", section: "Continuous Integration", hidden: false },
{ type: "chore", section: "Chores", hidden: false },
{ type: "docs", section: "Documentation", hidden: false },
{ type: "style", section: "Style Changes", hidden: false },
{ type: "refactor", section: "Code Refactoring", hidden: false },
{ type: "test", section: "Test Cases", hidden: true },
],
},
},
],
[
"@semantic-release/exec",
{
successCmd: "node release.purge.js"
}
]
],
};
if (branch === "main") {
config.plugins.splice(-2, 0, "@semantic-release/github");
}
module.exports = config;

56
release.purge.js Normal file
View File

@@ -0,0 +1,56 @@
const axios = require('axios');
const fs = require('fs');
const path = require('path');
function getCssFiles(dir, fileList = []) {
const files = fs.readdirSync(dir);
files.forEach((file) => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
getCssFiles(filePath, fileList);
} else if (file.endsWith('.css')) {
const relativePath = path.relative(__dirname, filePath);
fileList.push(`/gh/JamsRepos/Jamfin@latest/${relativePath}`);
}
});
return fileList;
}
async function purgeCdnCache() {
try {
const cssDirectory = path.resolve(__dirname, './theme');
const paths = getCssFiles(cssDirectory);
const jsonPayload = {
path: paths,
};
const response = await axios.post('https://purge.jsdelivr.net/', jsonPayload, {
headers: {
'Content-Type': 'application/json',
},
});
const id = response.data.id;
let status = 'pending';
while (status === 'pending') {
await new Promise(resolve => setTimeout(resolve, 1000));
const statusResponse = await axios.get(`https://purge.jsdelivr.net/status/${id}`);
status = statusResponse.data.status;
}
if (status === 'finished') {
console.log('CDN cache purge finished successfully.');
} else {
console.error('CDN cache purge failed.');
process.exit(1);
}
} catch (error) {
console.error('Error during CDN cache purge:', error);
process.exit(1);
}
}
purgeCdnCache();

792
theme/base.css Normal file
View File

@@ -0,0 +1,792 @@
/*
Jamfin - Another Jellyfin Theme aimed to please the masses.
Please note that this theme is still in development and may not be fully functional.
This theme is based on the Jellyfin default theme.
This theme is licensed under the MIT License.
*/
:root {
/* Colours */
--theme-background-colour: #101010;
--theme-sidebar-background-colour: #222222;
--theme-menu-background-colour: #3a3a3a80;
--theme-menu-shadow-colour: #fff3;
--theme-base-colour: #696969;
--theme-text-colour: #dbdbdb;
--theme-restart-colour: #da87287e;
--theme-shutdown-colour: #c21c1c9d;
--theme-progress-bar-colour: #cfcfcf;
--theme-progress-bar-background-colour: #2c2c2c;
--theme-progress-bar-transcoding-colour: #eb7e25;
--theme-chapter-marker-colour: #dbdbdb;
--theme-chapter-marker-watched-colour: #4a4a4a;
/* General Appearance */
--theme-roundness: .75rem;
--theme-blur: 16px;
}
/* Remove the Default Focus Outline */
*:focus-visible {
outline: none;
}
/* Change Background Colours */
.backgroundContainer,
.mainDrawer,
.drawer-open,
.nowPlayingPlaylist,
.nowPlayingContextMenu,
html {
background-color: var(--theme-background-colour);
}
/* Re-design the Header */
.layout-desktop [dir="ltr"] .pageTitle {
margin-left: 1.2em;
}
[dir="ltr"] .sidebarHeader {
padding-left: 2em;
font-weight: bold;
}
[dir="ltr"] .navMenuOption {
padding: .9em 1.2em !important;
}
.pageTitleWithLogo {
height: 50px;
}
.skinHeader {
background-color: transparent;
}
.layout-desktop .headerLeft,
.layout-desktop .headerRight {
margin-top: 1rem;
}
.layout-mobile .headerRight {
margin: 0 .29em;
}
.layout-mobile .libraryPage:not(.noSecondaryNavPage) {
padding-top: 8.5em !important;
}
.paper-icon-button-light > div {
width: 25px;
height: 25px;
transform: unset;
}
.skinHeader.semiTransparent {
background-color: unset;
}
.headerTabs,
.headerRight,
.dialog,
.raised,
.fab,
.paper-icon-button-light:not(
.headerRight,
.headerRight .paper-icon-button-light
),
.osdHeader .headerLeft .paper-icon-button-light,
.detailButton,
.sliderBubble,
.MuiDataGrid-root,
.MuiMenu-list,
.MuiButton-root,
.toast.toastVisible,
.guide-date-tab-button.emby-tab-button-active,
.guide-date-tab-button:focus,
.MuiTabs-centered .MuiTab-root,
#skipIntro .emby-button,
.chapterThumbContainer,
.chapterThumbTextContainer {
color: var(--theme-text-colour);
border-radius: var(--theme-roundness);
box-shadow: inset 0 1px var(--theme-menu-shadow-colour);
background-color: var(--theme-menu-background-colour);
backdrop-filter: blur(var(--theme-blur)) !important;
-webkit-backdrop-filter: blur(var(--theme-blur)) !important;
-moz-backdrop-filter: blur(var(--theme-blur)) !important;
-o-backdrop-filter: blur(var(--theme-blur)) !important;
-ms-backdrop-filter: blur(var(--theme-blur)) !important;
}
.dialogBackdrop {
background-color: #111;
}
.headerTabs .emby-tabs-slider {
display: flex;
align-items: center;
}
.headerTabs .emby-tab-button,
.headerRight .paper-icon-button-light,
.detailButton,
.sessionCardButton,
.guideOptions,
.MuiTabs-centered .MuiTab-root {
padding: .556em !important;
margin: 0 .29em !important;
border-radius: var(--theme-roundness);
}
.layout-desktop .headerTabs,
.layout-desktop .headerLeft,
.layout-desktop .headerRight,
.layout-desktop .emby-tabs-slider,
.layout-desktop .raised {
height: 50px;
}
.layout-mobile .headerTabs,
.layout-mobile .headerRight,
.layout-mobile .emby-tabs-slider,
.layout-mobile .raised {
height: 40px;
}
.layout-desktop .headerTabs {
margin-left: 250px;
}
@media (max-width: 1599px) {
.layout-desktop .sectionTabs {
width: auto;
align-self: center;
margin-top: -58px;
}
.layout-desktop .headerRight {
margin-right: .8em;
}
.layout-mobile .headerTabs {
margin: 20px auto;
width: auto;
}
.layout-mobile .pageTitleWithLogo {
height: 40px;
}
}
.layout-tv .sectionTabs {
width: unset;
}
@media (min-width: 100em) {
.layout-tv .headerTabs {
margin-top: unset;
}
.layout-mobile .headerTabs {
margin-top: -1.3em;
}
}
@media (max-width: 100em) {
.layout-tv .sectionTabs {
width: fit-content;
align-self: center;
font-size: 125%;
}
}
@media (max-width: 50em) {
.homeLibraryButton {
width: 100% !important;
}
}
/* Main Drawer */
.layout-desktop,
.touch-menu-la.transition {
transition: none;
}
.layout-mobile .mainDrawer {
padding-top: 1.2em;
}
.layout-desktop .mainDrawer-scrollContainer {
margin-top: 100px;
}
.navMenuOption,
.navMenuOption-selected,
.navMenuOption:hover,
.MuiListItem-root,
.MuiDrawer-paperAnchorLeft .MuiButtonBase-root {
border-radius: var(--theme-roundness) !important;
width: 80%;
margin: auto !important;
}
.navMenuOption:hover,
.listItem:hover,
.MuiButton-root:hover {
background-color: var(--theme-menu-shadow-colour);
}
.navMenuOption-selected,
.Mui-selected {
background: var(--theme-menu-shadow-colour) !important;
}
/* Main Cards */
.section0 .sectionTitle {
padding-top: 1em !important;
}
.layout-desktop .section0 .emby-scrollbuttons,
.layout-desktop .section1 .emby-scrollbuttons {
padding-top: unset;
}
.cardContent:not(.dashboardSection .cardContent),
.cardPadder:not(.dashboardSection .cardPadder),
.blurhash-canvas,
.dialog,
.itemSelectionPanel {
border-radius: var(--theme-roundness) !important;
box-shadow: inset 0 1px var(--theme-menu-shadow-colour) !important;
transition: 0.2s;
}
.cardOverlayContainer {
border-radius: var(--theme-roundness) !important;
box-shadow: inset 0 2px var(--theme-menu-shadow-colour) !important;
transition: unset;
}
.layout-mobile .cardOverlayButton {
padding: unset;
margin: 5px;
}
.layout-mobile .cardOverlayButtonIcon {
background: unset !important;
}
.layout-desktop .cardOverlayContainer > .cardOverlayButton-br .cardOverlayButton {
padding: unset;
margin: 5px;
}
.defaultCardBackground1,
.defaultCardBackground2,
.defaultCardBackground3,
.defaultCardBackground4,
.defaultCardBackground5,
.cardOverlayContainer > .cardOverlayFab-primary {
background-color: var(--theme-menu-background-colour);
font-size: 110%;
}
.button-submit:focus,
.paper-icon-button-light:hover:not(:disabled, .btnDelete),
.raised:hover,
.emby-tab-button:hover,
.detailButton:hover,
.emby-tab-button.show-focus:focus,
.paper-icon-button-light.show-focus:focus,
.emby-button.show-focus:focus,
.alphaPickerButton-tv:focus,
#skipIntro .emby-button:hover,
.multiSelectCheckboxOutline {
border-radius: var(--theme-roundness) !important;
transform: unset !important;
-webkit-transform: unset !important;
-moz-transform: unset !important;
-o-transform: unset !important;
-ms-transform: unset !important;
color: var(--theme-text-colour);
background-color: var(--theme-menu-shadow-colour) !important;
}
.layout-tv .itemDetailsGroup .emby-button.show-focus:focus {
padding: 5px 10px;
}
/* User Settings */
.readOnlyContent h2,
.sectionTitle,
.dashboardSection h3,
.MuiListSubheader-root {
font-weight: bold;
}
.listItem-border,
.itemSelectionPanel {
border: unset;
}
.listItem {
padding-left: 1.2em !important;
}
.listItem:hover,
.MuiButtonBase-root,
.MuiButtonBase-root:hover,
.emby-tab-button,
progress {
border-radius: var(--theme-roundness);
}
.collapseContent,
.formDialogFooter:not(.formDialogFooter-clear),
.formDialogHeader:not(.formDialogHeader-clear),
.paperList,
.visualCardBox,
.emby-select-withcolor,
.emby-input,
.emby-textarea {
background-color: var(--theme-menu-background-colour);
border-radius: var(--theme-roundness);
box-shadow: inset 0 1px var(--theme-menu-shadow-colour);
border: unset;
padding: 10px;
}
.emby-select[disabled] {
box-shadow: unset;
}
.trackSelections .selectContainer .detailTrackSelect {
padding: 0 10px;
}
/* Content Details */
.layout-desktop .detailImageContainer .card {
width: 250px;
max-width: unset;
top: 1.5em;
left: 0;
}
/* Backwards Compatibility for versions before 10.11.0 */
.layout-desktop .infoWrapper .detailImageContainer .card {
padding-top: 8.5em;
}
.layout-desktop .detailPagePrimaryContent {
padding-top: 135px;
padding-left: 300px;
min-height: 325px;
}
.layout-desktop .mainDetailButtons {
margin-top: 350px;
margin-left: 292px;
position: absolute;
}
.layout-mobile .mainDetailButtons {
display: flex;
flex-flow: wrap;
gap: 10px;
}
.mainDetailButtons .detailButton {
display: inline-flex;
align-items: center;
padding-left: 50px;
position: relative;
}
.btnPlay .detailButton-content:before {
content: "Play";
}
.btnReplay .detailButton-content:before {
content: "Replay";
}
.btnDownload .detailButton-content:before {
content: "Download";
}
.btnPlayTrailer .detailButton-content:before {
content: "Trailer";
}
.btnInstantMix .detailButton-content:before {
content: "Instant Mix";
}
.btnShuffle .detailButton-content:before {
content: "Shuffle";
}
.btnCancelSeriesTimer .detailButton-content:before {
content: "Cancel Programme";
}
.btnCancelTimer .detailButton-content:before {
content: "Stop Recording";
}
.btnPlaystate .detailButton-content:before {
content: "Watched";
}
.btnUserRating .detailButton-content:before {
content: "Favourite";
}
.btnSplitVersions .detailButton-content:before {
content: "Split Versions";
}
.btnMoreCommands .detailButton-content:before {
content: "Options";
}
.detailButton-content:before {
position: relative;
margin-left: 30px;
}
.detailButton-icon:before {
position: absolute;
top: 7px;
left: 7px;
}
.mainDetailButtons .material-icons {
height: unset;
}
.layout-desktop .detailPagePrimaryContainer,
.layout-desktop .detailPageContent {
padding-left: 3.3% !important;
}
.detailRibbon {
background: unset;
padding-left: 0 !important;
}
.layout-desktop .infoWrapper {
padding-top: 226px;
padding-left: 300px;
}
.itemBackdrop::before {
content: '';
position: absolute;
top: 0; /* cover the full element */
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(180deg, transparent 5%, var(--theme-background-colour));
pointer-events: none; /* ensures it's non-interactive */
z-index: 1; /* sits on top of the image */
}
.layout-mobile .itemBackdrop {
margin-top: unset;
-webkit-animation: backdrop-fadein .8s ease-in normal both;
animation: backdrop-fadein .8s ease-in normal both;
}
.layout-mobile .detailImageContainer .card {
filter: unset;
-webkit-filter: unset;
-moz-filter: unset;
-o-filter: unset;
-ms-filter: unset;
padding-left: 2em;
}
.layout-desktop .detailLogo {
left: 3%;
right: unset;
z-index: 1;
}
.layout-desktop .backgroundContainer.withBackdrop,
.MuiPaper-root,
.MuiTabs-indicator {
background-color: unset;
background-image: unset;
box-shadow: unset;
}
.layout-desktop .backdropImage {
filter: blur(5px);
-webkit-filter: blur(5px);
-moz-filter: blur(5px);
-o-filter: blur(5px);
-ms-filter: blur(5px);
}
.darkenContent {
backdrop-filter: blur(5px) brightness(0.75);
-webkit-backdrop-filter: blur(5px) brightness(0.75);
-moz-backdrop-filter: blur(5px) brightness(0.75);
-o-backdrop-filter: blur(5px) brightness(0.75);
-ms-backdrop-filter: blur(5px) brightness(0.75);
background: -webkit-linear-gradient(180deg, transparent, var(--theme-background-colour));
background: -moz-linear-gradient(180deg, transparent, var(--theme-background-colour));
background: -o-linear-gradient(180deg, transparent, var(--theme-background-colour));
background: -ms-linear-gradient(180deg, transparent, var(--theme-background-colour));
background: linear-gradient(180deg, transparent, var(--theme-background-colour));
}
.itemsContainer-tv {
margin-left: 10px;
}
/* Admin Settings */
#btnRestartServer {
box-shadow: inset 0 1px var(--theme-restart-colour) !important;
}
#btnRestartServer:hover,
.notifications,
.MuiChip-filledInfo {
color: var(--theme-text-colour) !important;
background-color: var(--theme-restart-colour) !important;
}
#btnShutdown,
.btnDelete {
box-shadow: inset 0 1px var(--theme-shutdown-colour) !important;
}
#btnShutdown:hover,
.btnDelete:hover,
.notification_important,
.MuiChip-filledError {
color: var(--theme-text-colour) !important;
background-color: var(--theme-shutdown-colour) !important;
}
.listItemIcon:not(.listItemIcon-transparent) {
border-radius: var(--theme-roundness);
}
.sessionCardButtons {
margin: .29em 0;
}
.dashboardSection .cardContent {
border-top-left-radius: var(--theme-roundness);
border-top-right-radius: var(--theme-roundness);
}
div[data-role="controlgroup"] a[data-role="button"]:first-child {
border-bottom-left-radius: var(--theme-roundness);
border-top-left-radius: var(--theme-roundness);
}
div[data-role="controlgroup"] a[data-role="button"]:last-child {
border-bottom-right-radius: var(--theme-roundness);
border-top-right-radius: var(--theme-roundness);
}
.dashboardColumn {
flex-shrink: inherit;
}
/* Base Colours */
.selectLabelFocused,
.textareaLabelFocused,
.inputLabelFocused,
.mdl-slider,
.metadataSidebarIcon,
.button-link,
.guide-date-tab-button.emby-tab-button-active,
.guide-date-tab-button:focus,
#divRunningTasks span,
.MuiAlert-icon,
.listItemBodyText span {
color: var(--theme-text-colour) !important;
}
.emby-checkbox:checked + span + .checkboxOutline,
.listItemIcon:not(
.listItemIcon-transparent,
.notification_important,
.notifications
),
.guide-channelHeaderCell:focus,
.programCell:focus,
.emby-button.show-focus:focus,
::selection,
div[data-role="controlgroup"] a.ui-btn-active,
.MuiAvatar-root,
.selectionCommandsPanel {
background-color: var(--theme-base-colour) !important;
}
.emby-checkbox + span + .checkboxOutline,
.emby-checkbox:checked + span + .checkboxOutline,
.emby-checkbox:focus:not(:checked) + span + .checkboxOutline,
.mdl-spinner__layer-1,
.mdl-spinner__layer-2,
.mdl-spinner__layer-3,
.mdl-spinner__layer-4 {
border-color: var(--theme-base-colour) !important;
}
.mdl-slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
background: transparent;
}
.mdl-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 0;
height: 0;
background: transparent;
cursor: pointer;
}
.mdl-slider::-moz-range-thumb {
width: 0;
height: 0;
background: transparent;
cursor: pointer;
}
.mdl-slider::-ms-thumb {
width: 0;
height: 0;
background: transparent;
cursor: pointer;
}
.mdl-slider-background-flex,
.mdl-slider-background-lower,
.mdl-slider-background-upper {
border-radius: var(--theme-roundness);
box-shadow: inset 0 1px var(--theme-menu-shadow-colour);
height: 10px;
}
/* Chapter Markers */
.sliderMarkerContainer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 10px;
pointer-events: none;
z-index: 1;
}
.sliderMarker {
position: absolute;
top: 0;
width: 2px;
height: 10px;
background-color: var(--theme-chapter-marker-colour);
opacity: 0.6;
border-radius: 1px;
pointer-events: auto;
cursor: pointer;
transition: opacity 0.2s ease, width 0.2s ease, background-color 0.2s ease;
}
.sliderMarker.unwatched {
background-color: var(--theme-chapter-marker-colour);
}
.sliderMarker.watched {
background-color: var(--theme-chapter-marker-watched-colour);
}
.mdl-slider-background-upper {
margin-left: -3px;
}
.mdl-slider-background-flex-container {
top: 5px;
}
.innerCardFooter .itemProgressBarForeground,
.playbackProgress > div,
.mdl-slider-background-lower,
.iconOsdProgressInner,
.taskProgressInner,
progress {
background-color: var(--theme-progress-bar-colour) !important;
}
/* These need to remain separate for browser compatibility */
progress::-moz-progress-bar {
background-color: var(--theme-progress-bar-colour);
}
progress::-webkit-progress-bar {
background-color: var(--theme-progress-bar-colour);
}
progress::-ms-thumb {
background-color: var(--theme-progress-bar-colour);
}
.transcodingProgress > div {
background-color: var(--theme-progress-bar-transcoding-colour) !important;
}
.innerCardFooter .itemProgressBar,
.backgroundProgress > div,
.mdl-slider-background-flex,
.taskProgressOuter,
progress {
background-color: var(--theme-progress-bar-background-colour) !important;
}
.layout-desktop .dashboardDocument .content-primary:not(.layout-desktop #dashboardPage .content-primary) {
padding-left: 2.5em;
padding-right: 2.5em;
}
[dir="ltr"] .formDialogHeaderTitle:first-child {
margin-left: unset;
}
/* Login Form */
#loginPage {
background-color: var(--theme-menu-background-colour);
border-radius: var(--theme-roundness);
box-shadow: inset 0 1px var(--theme-menu-shadow-colour);
padding: unset !important;
max-width: 750px;
max-height: 500px;
margin: 150px auto 0;
}
.manualLoginForm .raised.button-submit.block.emby-button {
margin-bottom: -3px;
}
.chapterThumbTextContainer {
width: fit-content;
margin: auto;
}
.chapterThumbText {
width: fit-content;
}