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

After

Width:  |  Height:  |  Size: 134 KiB

BIN
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

+7926
View File
File diff suppressed because it is too large Load Diff
+17
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
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
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
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;
}