Migration complete

This commit is contained in:
2026-06-23 19:24:38 +02:00
parent 279160c1cb
commit 64b26ef059
126 changed files with 8121 additions and 221 deletions

View File

@@ -7,6 +7,8 @@
@import './layout/news.css';
@import './layout/activity.css';
@import './layout/submit.css';
@import './layout/reviews.css';
@import './layout/responsive.css';
@import './components/common.css';
@import './components/grid.css';

View File

@@ -28,6 +28,9 @@
/* Menu settings */
--menu-size: 260px;
--menu-user-avatar-bg: #555;
/* Gap */
--gap: 15px;
}
.light-mode {

View File

@@ -29,6 +29,7 @@
background-color: var(--bg2);
border: 1px solid var(--border);
display: flex;
min-width: 0;
flex-direction: column;
transition: transform 0.2s, border-color 0.2s;
cursor: pointer;
@@ -41,6 +42,7 @@
.entry-cover-wrapper {
position: relative;
aspect-ratio: 4/3;
min-width: 0;
background-color: var(--bg);
border-bottom: 1px solid var(--border);
display: flex;
@@ -80,6 +82,7 @@
font-size: 1.1rem;
margin-bottom: 5px;
line-height: 1.3;
word-wrap: break-word;
}
.entry-card-author {
@@ -97,3 +100,83 @@
align-items: center;
}
}
@media (max-width: 1024px) {
.stats-grid {
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.stat-card {
padding: 15px;
gap: 12px;
}
}
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.stat-card {
padding: 12px;
gap: 10px;
font-size: 0.9rem;
}
.stat-card i {
width: 28px;
height: 28px;
}
.entry-card-info {
padding: 12px;
}
.entry-card-title {
font-size: 0.95rem;
margin-bottom: 4px;
}
.entry-card-author {
font-size: 0.8rem;
margin-bottom: 8px;
}
.entry-card-meta {
font-size: 0.75rem;
}
}
@media (max-width: 600px) {
.stats-grid {
grid-template-columns: 1fr;
gap: 10px;
margin-bottom: 15px;
}
.stat-card {
padding: 10px;
flex-direction: row;
}
.entry-card {
&:hover {
transform: none;
}
}
.entry-card-title {
font-size: 0.9rem;
}
.entry-badge {
top: 5px;
right: 5px;
padding: 3px 6px;
font-size: 0.65rem;
}
}

View File

@@ -146,6 +146,7 @@
.breadcrumb {
margin-bottom: 15px;
flex-shrink: 0;
}
/* PAGE */
@@ -155,6 +156,7 @@
font-weight: 300;
margin-bottom: 20px;
color: var(--text);
flex-shrink: 0;
}
/* TEXTS */
@@ -193,3 +195,88 @@
border: none;
cursor: pointer;
}
@media (max-width: 768px) {
.btn {
padding: 7px 12px;
font-size: 0.85rem;
gap: 6px;
}
.block {
padding: 15px;
margin-bottom: 15px;
}
.block-header {
font-size: 1.05rem;
margin-bottom: 12px;
padding-bottom: 8px;
}
.page-title {
font-size: 1.5rem;
margin-bottom: 15px;
}
.content-title {
margin: 20px 0 12px 0;
padding-left: 8px;
}
.quote {
padding: 12px;
margin-top: 20px;
font-size: 0.95rem;
}
.whisper {
margin-bottom: 12px;
font-size: 0.9rem;
}
.breadcrumb {
font-size: 0.85rem;
}
}
@media (max-width: 600px) {
.btn {
padding: 6px 10px;
font-size: 0.8rem;
gap: 4px;
justify-content: center;
}
.btn.primary, .btn.danger, .btn.success {
width: 100%;
}
.block {
padding: 12px;
margin-bottom: 12px;
}
.block-header {
font-size: 0.95rem;
margin-bottom: 10px;
padding-bottom: 6px;
}
.page-title {
font-size: 1.2rem;
margin-bottom: 12px;
}
.badge {
padding: 2px 6px;
font-size: 0.7rem;
}
.topbar-badge {
min-width: 16px;
height: 16px;
padding: 0 3px;
font-size: 0.6rem;
}
}

View File

@@ -259,6 +259,10 @@
flex-direction: column;
}
.database-wrapper {
flex-direction: column;
}
.database-filters {
width: 100%;
display: grid;
@@ -275,19 +279,63 @@
}
}
@media (max-width: 768px) {
.database-search {
gap: 8px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.database-wrapper {
flex-direction: column;
gap: 15px;
}
.database-filters {
width: 100%;
grid-template-columns: 1fr;
order: -1;
margin-bottom: 10px;
}
.database-filter-group {
padding: 12px 0;
}
.grid-entries {
grid-template-columns: repeat(3, 1fr);
gap: 15px;
}
}
@media (max-width: 600px) {
.database-search {
flex-direction: column;
}
.database-filters {
grid-template-columns: 1fr;
}
.grid-entries {
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.database-filter-group {
padding: 10px 0;
}
}
@media (max-width: 420px) {
.grid-entries {
grid-template-columns: 1fr;
gap: 10px;
}
.database-search input {
font-size: 0.85rem;
padding: 6px 8px;
}
}

View File

@@ -93,7 +93,7 @@
font-weight: 600;
color: var(--text);
margin-bottom: 6px;
white-space: nowrap;
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
}
@@ -158,3 +158,103 @@
white-space: nowrap;
}
}
@media (max-width: 768px) {
.drafts-count {
font-size: 0.8rem;
margin-bottom: 12px;
padding-bottom: 8px;
}
.drafts-item {
gap: 15px;
padding: 15px;
}
.drafts-cover {
width: 70px;
height: 70px;
}
.drafts-top {
gap: 12px;
}
.drafts-title {
font-size: 0.95rem;
}
.drafts-meta {
font-size: 0.8rem;
}
.drafts-actions {
gap: 6px;
}
.drafts-actions .btn {
padding: 6px 10px;
font-size: 0.8rem;
}
}
@media (max-width: 600px) {
.drafts-empty {
padding: 60px 15px;
gap: 12px;
}
.drafts-empty h3 {
font-size: 1rem;
}
.drafts-empty p {
font-size: 0.85rem;
}
.drafts-item {
flex-direction: column;
gap: 12px;
padding: 12px;
}
.drafts-cover {
width: 100%;
height: 150px;
}
.drafts-top {
flex-direction: column;
gap: 10px;
}
.drafts-title {
font-size: 0.9rem;
}
.drafts-meta {
font-size: 0.75rem;
}
.drafts-progress {
flex-direction: column;
gap: 8px;
}
.drafts-progress-bar {
width: 100%;
}
.drafts-actions {
flex-direction: row;
gap: 6px;
flex-wrap: wrap;
}
.drafts-actions .btn {
flex: 1;
min-width: 80px;
padding: 5px 8px;
font-size: 0.75rem;
}
}

View File

@@ -508,6 +508,17 @@
flex-direction: row;
gap: 15px;
}
@media (max-width: 600px) {
.upload-item-actions {
flex-direction: column;
gap: 8px;
}
.upload-item-actions .btn {
width: 100%;
}
}
.file-state-icon { width: 18px; height: 18px; }
.file-state-icon--public { color: var(--success); }
.file-state-icon--private { color: var(--text2); }
@@ -613,3 +624,86 @@
.game-selector-platform-only {
grid-column: span 1;
}
@media (max-width: 768px) {
.form-group.level {
padding: 20px;
margin-bottom: 25px;
}
.form-group-title {
font-size: 1rem;
margin-bottom: 15px;
padding-bottom: 8px;
}
.form-group label, .form-label {
margin-bottom: 6px;
font-size: 0.9rem;
}
.form-input, .form-select, .form-textarea, .form-field {
padding: 8px 10px;
font-size: 0.9rem;
}
.form-textarea {
min-height: 100px;
}
.game-selector-mode {
flex-direction: column;
gap: 0;
}
.game-selector-mode-btn {
padding: 10px 12px;
border-right: none;
border-bottom: 1px solid var(--border);
}
.game-selector-mode-btn:last-child {
border-bottom: none;
}
.submit, .submit-level, .main-image-grid {
flex-direction: column;
}
.grid-hashes {
grid-template-columns: 1fr;
}
.hash-first {
display: none;
}
}
@media (max-width: 600px) {
.form-group {
margin-bottom: 15px;
}
.form-group.level {
padding: 15px;
margin-bottom: 20px;
}
.form-group-title {
font-size: 0.95rem;
margin-bottom: 12px;
}
.form-group label, .form-label {
font-size: 0.85rem;
}
.form-input, .form-select, .form-textarea, .form-field {
padding: 6px 8px;
font-size: 0.85rem;
}
.form-error-text {
font-size: 0.8rem;
}
}

View File

@@ -1,9 +1,10 @@
.hovercard-overlay {
position: absolute;
z-index: 2000;
position: fixed;
z-index: 3500;
background-color: var(--bg2);
border: 1px solid var(--border);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
pointer-events: auto;
}
.hovercard-overlay-loading {
@@ -117,3 +118,35 @@
justify-content: center;
font-size: 0.82rem;
}
@media (max-width: 768px) {
.hovercard {
width: 260px;
}
.hovercard-actions {
gap: 6px;
}
.hovercard-actions .btn {
font-size: 0.75rem;
padding: 6px 8px;
}
}
@media (max-width: 600px) {
.hovercard {
width: calc(100vw - 40px);
max-width: 280px;
}
.hovercard-actions {
flex-direction: column;
gap: 6px;
}
.hovercard-actions .btn {
width: 100%;
justify-content: center;
}
}

View File

@@ -547,3 +547,128 @@
padding: 14px 0 4px;
border-top: 1px solid var(--border);
}
@media (max-width: 1024px) {
.modcp-wrapper {
min-height: auto;
}
.modcp-sidebar {
width: 200px;
margin-right: 10px;
}
.modcp-content {
padding: 20px;
}
.modcp-page-title {
font-size: 1.15rem;
}
}
@media (max-width: 768px) {
.modcp-wrapper {
flex-direction: column;
gap: 0;
}
.modcp-sidebar {
width: 100%;
flex-shrink: 1;
position: relative;
top: auto;
align-self: auto;
margin-right: 0;
margin-bottom: 15px;
border: 1px solid var(--border);
max-height: 300px;
overflow-y: auto;
}
.modcp-sidebar-header {
padding: 12px 14px;
font-size: 0.8rem;
}
.modcp-nav-label {
padding: 6px 14px 3px;
font-size: 0.65rem;
}
.modcp-nav-item {
padding: 6px 14px;
font-size: 0.8rem;
gap: 8px;
}
.modcp-content {
padding: 15px;
}
.modcp-page-title {
font-size: 1rem;
margin-bottom: 12px;
}
.modcp-page-actions {
flex-direction: row;
gap: 8px;
}
.modcp-table {
font-size: 0.85rem;
}
.modcp-table th, .modcp-table td {
padding: 8px;
}
.modcp-table tbody tr {
height: auto;
}
}
@media (max-width: 600px) {
.modcp-sidebar {
max-height: 200px;
}
.modcp-sidebar-header {
padding: 10px 12px;
font-size: 0.75rem;
}
.modcp-nav-item {
padding: 5px 12px;
font-size: 0.75rem;
}
.modcp-content {
padding: 12px;
}
.modcp-page-title {
font-size: 0.95rem;
}
.modcp-table {
font-size: 0.8rem;
overflow-x: auto;
display: block;
}
.modcp-table th, .modcp-table td {
padding: 6px;
}
.log-diff-key {
width: auto;
white-space: normal;
}
.log-raw {
font-size: 0.7rem;
padding: 8px 10px;
}
}

View File

@@ -21,6 +21,28 @@
}
}
@media (max-width: 768px) {
.notifications, .conversations {
position: fixed;
width: calc(100% - 30px);
max-width: 340px;
right: 15px;
top: auto;
bottom: 15px;
max-height: calc(100vh - 130px);
z-index: 3000 !important;
}
}
@media (max-width: 600px) {
.notifications, .conversations {
width: calc(100% - 20px);
right: 10px;
bottom: 10px;
max-width: 100%;
}
}
@keyframes dropdown-enter {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: translateY(0); }

View File

@@ -185,3 +185,82 @@
border: 1px solid var(--border);
}
@media (max-width: 768px) {
.queue-item {
padding: 15px;
margin-bottom: 15px;
}
.queue-item-header {
flex-direction: column;
gap: 12px;
}
.queue-item-title {
font-size: 1rem;
}
.queue-item-meta {
font-size: 0.8rem;
}
.queue-item-actions-header {
gap: 6px;
flex-wrap: wrap;
width: 100%;
}
.timeline {
font-size: 0.85rem;
}
.timeline-container {
padding: 12px 15px;
}
.queue-mod-actions {
flex-direction: column;
gap: 6px;
}
}
@media (max-width: 600px) {
.queue-empty {
padding: 60px 15px;
font-size: 0.9rem;
}
.queue-item {
padding: 12px;
border-left-width: 3px;
}
.queue-item-title {
font-size: 0.95rem;
}
.queue-item-meta {
font-size: 0.75rem;
}
.queue-item-actions-header {
width: 100%;
}
.timeline {
font-size: 0.8rem;
}
.timeline-container {
padding: 10px 12px;
}
.queue-mod-actions {
flex-direction: column;
}
.queue-mod-actions .btn {
width: 100%;
}
}

View File

@@ -9,6 +9,29 @@
z-index: 2000;
}
@media (max-width: 768px) {
.settings-dropdown {
position: fixed;
width: calc(100% - 30px);
max-width: 240px;
right: 15px;
top: auto;
bottom: 15px;
max-height: calc(100vh - 130px);
overflow-y: auto;
z-index: 3000 !important;
}
}
@media (max-width: 600px) {
.settings-dropdown {
width: calc(100% - 20px);
right: 10px;
bottom: 10px;
max-width: 100%;
}
}
.settings-header {
padding: 12px 16px;
border-bottom: 1px solid var(--border);

View File

@@ -15,6 +15,62 @@
.patcher-grid {
grid-template-columns: 1fr;
}
.patcher-container {
padding: 20px;
}
.patcher-dropzone {
padding: 40px 15px;
gap: 12px;
}
.embed-patch-box {
padding: 20px;
height: auto;
}
.embed-patch-box-icon {
gap: 12px;
}
.embed-patch-box-icon-block {
width: 40px;
height: 40px;
}
}
@media (max-width: 600px) {
.patcher-container {
padding: 15px;
margin-bottom: 15px;
}
.patcher-grid {
gap: 15px;
}
.patcher-dropzone {
padding: 30px 12px;
gap: 10px;
font-size: 0.9rem;
}
.patcher-status-box {
margin-top: 15px;
padding: 12px;
font-size: 0.9rem;
}
.embed-patch-box {
padding: 15px;
gap: 12px;
}
.btn:disabled {
padding: 6px 8px;
font-size: 0.8rem;
}
}
.patcher-dropzone {

View File

@@ -112,7 +112,7 @@
color: var(--rhpz-orange);
}
.activity-tl-dot--news {
.activity-tl-dot--news, .activity-tl-dot--review {
background-color: rgba(129,199,132,0.1);
border-color: rgba(129,199,132,0.4);
color: var(--success);
@@ -198,7 +198,7 @@
border: 1px solid rgba(255,115,0,0.25);
}
.activity-tl-badge--news {
.activity-tl-badge--news, .activity-tl-badge--review {
background-color: rgba(129,199,132,0.1);
color: var(--success);
border: 1px solid rgba(129,199,132,0.25);
@@ -267,6 +267,32 @@
.activity-tl-thumb { display: none; }
.activity-day-sep { padding-left: 44px; }
.activity-tl-left { width: 44px; }
.activity-tl-date {
font-size: 0.75rem;
}
.activity-tl-content-title {
font-size: 0.9rem;
}
}
@media (max-width: 768px) {
.activity-timeline {
padding-left: 50px;
}
.activity-tl-left {
width: 40px;
}
.activity-tl-header {
gap: 10px;
}
.activity-tl-date {
font-size: 0.8rem;
}
}
.home-section {
@@ -478,8 +504,52 @@
.featured-entries-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 600px) {
.news-strip { grid-template-columns: repeat(2, 1fr); }
.featured-entries-grid { grid-template-columns: repeat(2, 1fr); }
.news-strip-cover { height: 80px; }
@media (max-width: 768px) {
.news-strip {
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
.featured-entries-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.home-section {
margin-bottom: 20px;
}
.news-strip-cover {
height: 100px;
}
.featured-entry-title {
font-size: 0.95rem;
}
}
@media (max-width: 600px) {
.news-strip { grid-template-columns: 1fr; }
.featured-entries-grid { grid-template-columns: 1fr; }
.news-strip-cover { height: 80px; }
.news-strip-item {
padding: 10px;
}
.news-strip-title {
font-size: 0.85rem;
}
.featured-entry-title {
font-size: 0.9rem;
}
.featured-entry-meta {
font-size: 0.7rem;
}
.home-section-title {
font-size: 0.95rem;
}
}

View File

@@ -53,6 +53,155 @@
}
}
.topbar-more-container {
display: none;
}
.topbar-more-menu {
position: fixed;
top: 60px;
right: 0;
background-color: var(--bg2);
border: 1px solid var(--border);
border-top: none;
border-right: none;
z-index: 2000;
min-width: 180px;
max-height: calc(100vh - 60px);
overflow-y: auto;
}
.topbar-more-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
color: var(--text2);
text-decoration: none;
font-size: 0.9rem;
border-bottom: 1px solid var(--border);
transition: all 0.15s;
&:last-child {
border-bottom: none;
}
&:hover {
background-color: var(--bg3);
color: var(--text);
}
i {
width: 16px;
height: 16px;
flex-shrink: 0;
}
span {
flex-grow: 1;
text-align: left;
}
}
@media (min-width: 769px) {
.topbar-more-container {
display: none !important;
}
.topbar-admin-btn,
.topbar-mod-btn {
display: flex !important;
}
}
@media (max-width: 768px) {
.topbar-more-container {
display: block;
}
.topbar-admin-btn,
.topbar-mod-btn {
display: none !important;
}
#topbar {
padding: 0 10px;
}
.search-bar {
display: none !important;
}
.topbar-actions {
gap: 8px !important;
}
.topbar-actions .btn {
padding: 8px 6px;
font-size: 0.85rem;
display: flex;
align-items: center;
justify-content: center;
}
.topbar-actions i {
width: 16px !important;
height: 16px !important;
}
.topbar-badge {
font-size: 0.65rem;
width: 18px;
height: 18px;
}
}
@media (max-width: 600px) {
#topbar {
padding: 0 8px;
}
.topbar-actions {
gap: 8px !important;
}
.topbar-actions .btn {
padding: 6px 4px;
font-size: 0.75rem;
display: flex;
align-items: center;
justify-content: center;
}
.topbar-actions i {
width: 14px !important;
height: 14px !important;
}
.topbar-badge {
font-size: 0.6rem;
width: 16px;
height: 16px;
}
}
.search-scope-select {
background-color: var(--bg2);
border: none;
border-right: 1px solid var(--border);
color: var(--text2);
font-size: 0.8rem;
padding: 8px 10px;
cursor: pointer;
outline: none;
appearance: none;
transition: color 0.15s;
}
.search-scope-select:hover,
.search-scope-select:focus {
color: var(--text);
}
#content {
flex-grow: 1;
padding: 30px;

View File

@@ -268,36 +268,6 @@
color: var(--text);
line-height: 1.5;
word-wrap: break-word;
p {
margin-bottom: 10px;
&:last-child { margin-bottom: 0; }
}
a {
color: var(--rhpz-orange);
&:hover {
color: var(--rhpz-orange-hover);
text-decoration: underline;
}
}
blockquote, .bbCodeBlock-blockquote {
background-color: var(--bg);
border-left: 3px solid var(--info);
padding: 12px 16px;
margin: 12px 0;
font-style: italic;
color: var(--text2);
}
code {
font-family: monospace;
background-color: var(--bg3);
border: 1px solid var(--border);
padding: 2px 5px;
font-size: 0.9rem;
}
}
}
}
@@ -381,3 +351,242 @@
margin-right: 4px;
}
}
.markdown-body {
p {
margin-bottom: 10px;
&:last-child { margin-bottom: 0; }
}
a {
color: var(--rhpz-orange);
&:hover {
color: var(--rhpz-orange-hover);
text-decoration: underline;
}
}
blockquote, .bbCodeBlock-blockquote {
background-color: var(--bg);
border-left: 3px solid var(--info);
padding: 12px 16px;
margin: 12px 0;
font-style: italic;
color: var(--text2);
}
code {
font-family: monospace;
background-color: var(--bg3);
border: 1px solid var(--border);
padding: 2px 5px;
font-size: 0.9rem;
}
}
.markdown-body h1, .markdown-body h2, .markdown-body h3,
.markdown-body h4, .markdown-body h5, .markdown-body h6 {
color: var(--text);
font-weight: 600;
margin: 16px 0 8px;
line-height: 1.3;
}
.markdown-body h1 { font-size: 1.4rem; }
.markdown-body h2 { font-size: 1.2rem; }
.markdown-body h3 { font-size: 1.05rem; }
.markdown-body strong { color: var(--text); font-weight: 700; }
.markdown-body em { color: var(--text2); }
.markdown-body ul, .markdown-body ol {
margin: 0 0 12px 20px;
color: var(--text);
}
.markdown-body li { margin-bottom: 4px; line-height: 1.5; }
.markdown-body hr {
border: none;
border-top: 1px solid var(--border);
margin: 16px 0;
}
.markdown-body table {
width: 100%;
border-collapse: collapse;
margin: 12px 0;
font-size: 0.9rem;
}
.markdown-body th, .markdown-body td {
border: 1px solid var(--border);
padding: 6px 10px;
text-align: left;
}
.markdown-body th {
background-color: var(--bg3);
font-weight: 600;
color: var(--text);
}
.markdown-body del {
color: var(--text2);
text-decoration: line-through;
}
.markdown-body img {
max-width: 100%;
border: 1px solid var(--border);
margin: 8px 0;
}
.hack-actions {
display: flex;
gap: 10px;
}
@media (max-width: 768px) {
.entry-header {
flex-direction: column;
padding: 20px;
gap: 20px;
.entry-cover {
width: 100%;
height: 280px;
max-width: 300px;
margin: 0 auto;
}
.entry-info {
.entry-title {
font-size: 1.6rem;
}
.entry-authors {
font-size: 0.95rem;
}
.entry-meta-grid {
grid-template-columns: 1fr;
gap: 12px;
margin-bottom: 20px;
}
.entry-actions {
flex-direction: column;
gap: 10px;
.btn {
width: 100%;
}
}
}
}
.entry-content {
padding: 20px;
.entry-section-title {
font-size: 1.1rem;
}
.entry-gallery {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 12px;
margin-bottom: 20px;
}
}
.comment-block {
gap: 12px;
padding: 15px 0;
.comment-avatar {
width: 40px;
height: 40px;
}
.comment-content {
.comment-body {
font-size: 0.9rem;
}
}
}
.video-thumbnail-wrapper {
max-width: 100%;
}
.gallery-modal-close {
top: 10px;
right: 15px;
font-size: 30px;
}
.hack-actions {
flex-direction: column;
}
}
@media (max-width: 600px) {
.entry-header {
padding: 15px;
gap: 15px;
.entry-cover {
height: 240px;
}
.entry-info {
.entry-title {
font-size: 1.3rem;
margin-bottom: 8px;
}
.entry-authors {
font-size: 0.85rem;
margin-bottom: 15px;
}
.entry-actions {
gap: 8px;
.btn {
padding: 8px 12px;
font-size: 0.85rem;
}
}
}
}
.entry-content {
padding: 15px;
.entry-gallery {
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 8px;
margin-bottom: 15px;
}
}
.comment-block {
padding: 10px 0;
.comment-avatar {
width: 36px;
height: 36px;
}
}
.markdown-body h1 { font-size: 1.15rem; }
.markdown-body h2 { font-size: 1rem; }
.markdown-body h3 { font-size: 0.95rem; }
.hack-actions {
flex-direction: column;
}
}

View File

@@ -77,6 +77,21 @@
color: var(--text);
margin-bottom: 12px;
text-shadow: 0 2px 4px rgba(0,0,0,0.6);
word-wrap: break-word;
overflow-wrap: break-word;
word-break: break-word;
}
@media (max-width: 768px) {
.news-header .news-title {
font-size: 1.8rem;
}
}
@media (max-width: 600px) {
.news-header .news-title {
font-size: 1.4rem;
}
}
.news-header .news-meta {
@@ -298,7 +313,6 @@
color: #e57373;
}
/* ── Hero ────────────────────────────────────────────────── */
.news-hero {
display: block;
position: relative;
@@ -484,3 +498,106 @@
.news-hero-title { font-size: 1.4rem; }
.news-grid { grid-template-columns: 1fr; }
}
@media (max-width: 768px) {
.news-header .news-meta {
gap: 12px;
font-size: 0.85rem;
}
.news-layout {
flex-direction: column;
gap: 20px;
padding: 20px;
}
.news-sidebar {
width: 100%;
}
.news-content {
padding: 25px;
font-size: 1rem;
}
.news-body-text {
font-size: 1rem;
}
.sidebar-block {
padding: 15px;
}
}
@media (max-width: 600px) {
.news-header .news-meta {
gap: 8px;
font-size: 0.8rem;
flex-wrap: wrap;
}
.news-header .meta-item {
padding: 3px 8px;
font-size: 0.75rem;
}
.news-layout {
gap: 15px;
padding: 15px;
}
.news-main-content {
min-width: 0;
}
.news-content {
padding: 15px;
font-size: 0.95rem;
}
.news-body-text {
font-size: 0.95rem;
margin-bottom: 12px;
}
.news-body-text p {
margin-bottom: 15px;
}
.news-sidebar {
width: 100%;
gap: 15px;
}
.sidebar-block {
padding: 12px;
}
.sidebar-block h3 {
font-size: 0.95rem;
}
.sidebar-block p {
font-size: 0.9rem;
}
.news-card-title {
font-size: 0.9rem;
}
}
@media (max-width: 420px) {
.news-header .news-meta {
font-size: 0.75rem;
}
.news-layout {
padding: 12px;
}
.news-content {
padding: 12px;
}
}

View File

@@ -0,0 +1,270 @@
@media (max-width: 768px) {
:root {
--menu-size: 280px;
}
#menu {
position: fixed;
left: 0;
top: 60px;
height: calc(100vh - 60px);
transform: translateX(-100%);
transition: transform 0.3s ease-in-out;
z-index: 999;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.5);
}
#menu.mobile-open {
transform: translateX(0);
}
#app.menu-open::before {
content: '';
position: fixed;
top: 60px;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 998;
}
.mobile-toggle {
display: flex !important;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
padding: 0;
}
#main-wrapper {
width: 100%;
}
#content {
padding: 20px;
}
#topbar {
padding: 0 10px;
gap: 10px;
}
.search-bar {
width: 100%;
max-width: 250px;
}
.search-scope-select {
font-size: 0.7rem;
padding: 6px 8px;
}
.topbar-actions {
gap: 4px;
overflow-x: auto;
flex-shrink: 1;
}
.topbar-actions .btn {
flex-shrink: 0;
padding: 6px 8px;
}
.vertical-separator {
height: 30px;
}
}
@media (max-width: 600px) {
:root {
--menu-size: 240px;
}
#content {
padding: 15px;
}
#topbar {
padding: 0 8px;
height: 55px;
}
#topbar {
flex-wrap: wrap;
gap: 8px;
}
.search-bar {
max-width: 100%;
order: 3;
width: 100%;
margin-top: 8px;
}
.topbar-actions {
gap: 2px;
max-width: 100%;
}
.topbar-actions .btn {
padding: 4px 6px;
font-size: 0.9rem;
}
.search-scope-select {
display: none;
}
.search-bar input {
padding: 4px;
font-size: 0.9rem;
}
#menu {
width: 240px;
}
.menu-title {
display: none;
}
.menu-logo {
width: 40px;
height: 40px;
}
.menu-header {
padding: 8px;
justify-content: center;
}
.menu-user-info .username {
font-size: 0.9rem;
}
}
@media (max-width: 420px) {
:root {
--menu-size: 200px;
}
#content {
padding: 12px;
}
#topbar {
padding: 0 6px;
height: 50px;
}
.mobile-toggle {
width: 35px;
height: 35px;
}
.topbar-actions .btn {
padding: 3px 4px;
font-size: 0.8rem;
}
.vertical-separator {
display: none;
}
.menu-item {
padding: 8px 12px;
font-size: 0.9rem;
}
.menu-group-title {
padding: 0 12px;
font-size: 0.65rem;
}
.menu-user-info {
display: none;
}
}
@media (max-height: 500px) and (max-width: 768px) {
#topbar {
height: 50px;
}
#content {
padding: 12px;
}
.menu-header {
padding: 8px;
}
}
@media (max-width: 1024px) and (min-width: 769px) {
:root {
--menu-size: 240px;
}
#content {
padding: 25px;
}
.search-bar {
width: 250px;
}
}
@media (min-width: 769px) {
#menu {
transform: translateX(0) !important;
position: relative !important;
top: auto !important;
height: auto !important;
box-shadow: none !important;
}
#app.menu-open::before {
display: none;
}
.mobile-toggle {
display: none !important;
}
#app {
display: flex;
}
#main-wrapper {
width: calc(100% - var(--menu-size));
flex-grow: 1;
}
}
@media (hover: none) and (pointer: coarse) {
.btn,
.menu-item,
button {
min-height: 44px;
min-width: 44px;
}
.btn {
padding: 8px 12px;
}
.menu-item:hover {
background-color: var(--bg2);
}
}

View File

@@ -0,0 +1,156 @@
.review-section-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 10px;
}
.review-header-right {
display: flex;
align-items: center;
gap: 10px;
}
.review-avg-badge {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 0.85rem;
font-weight: 600;
color: var(--rhpz-orange);
background-color: rgba(255,115,0,.1);
border: 1px solid rgba(255,115,0,.3);
padding: 4px 10px;
}
.review-avg-badge--lg {
font-size: 1rem;
padding: 8px 16px;
}
.review-avg-count {
color: var(--text2);
font-weight: 400;
font-size: 0.85rem;
}
.star-rating-display {
display: inline-flex;
align-items: center;
gap: 1px;
}
.star-rating-display .star-filled { color: var(--rhpz-orange); fill: var(--rhpz-orange); }
.star-rating-display .star-empty { color: var(--border); }
.review-title {
font-size: 0.98rem;
font-weight: 700;
color: var(--text);
margin-bottom: 4px;
}
.star-input {
display: flex;
gap: 4px;
}
.star-input-icon.star-filled svg { color: var(--rhpz-orange); fill: var(--rhpz-orange); }
.star-input-icon.star-empty svg { color: var(--border); }
.star-input-icon {
cursor: pointer;
transition: transform 0.1s;
}
.star-input-icon:hover {
transform: scale(1.15);
}
.reviews-page-header {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 25px;
padding-bottom: 20px;
border-bottom: 1px solid var(--border);
}
.reviews-back-link {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.82rem;
color: var(--text2);
text-decoration: none;
width: fit-content;
}
.reviews-back-link:hover { color: var(--rhpz-orange); }
.reviews-page-title {
font-size: 1.4rem;
font-weight: 600;
color: var(--text);
}
@media (max-width: 768px) {
.review-section-header {
gap: 8px;
margin-bottom: 8px;
}
.review-header-right {
gap: 8px;
flex-wrap: wrap;
}
.review-avg-badge {
font-size: 0.8rem;
padding: 3px 8px;
}
.review-avg-badge--lg {
font-size: 0.95rem;
padding: 6px 12px;
}
.reviews-page-header {
gap: 8px;
margin-bottom: 20px;
padding-bottom: 15px;
}
.reviews-page-title {
font-size: 1.15rem;
}
.review-title {
font-size: 0.9rem;
}
}
@media (max-width: 600px) {
.review-avg-badge {
font-size: 0.75rem;
padding: 2px 6px;
}
.reviews-page-title {
font-size: 1rem;
}
.review-title {
font-size: 0.85rem;
}
.star-rating-display {
gap: 0;
}
.star-input {
gap: 3px;
}
}

View File

@@ -289,9 +289,60 @@
.submit-rule:last-child { border-bottom: none; }
}
@media (max-width: 768px) {
.submit-hero {
flex-direction: column;
gap: 20px;
padding: 25px 20px;
}
.submit-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.submit-body {
padding: 25px 20px;
}
.submit-rules {
gap: 0;
}
.submit-rule {
padding: 15px;
}
}
@media (max-width: 600px) {
.submit-hero, .submit-body { padding-left: 20px; padding-right: 20px; }
.submit-hero, .submit-body {
padding-left: 15px;
padding-right: 15px;
}
.submit-grid { grid-template-columns: 1fr; }
.submit-news-row { grid-template-columns: 1fr; }
.submit-review-note { max-width: 100%; }
.submit-hero {
gap: 15px;
padding: 15px;
}
.submit-body {
padding: 15px;
}
.submit-rule {
padding: 12px;
font-size: 0.9rem;
}
.submit-hero-title {
font-size: 1.3rem;
}
.submit-grid > * {
margin-bottom: 10px;
}
}

View File

@@ -0,0 +1,47 @@
import { calculate as calculateHashes } from "./hashes.js";
window.HashesChecker = function( wire ) {
return {
/**
* Wire variable instance.
*/
$wire: wire,
/**
* If a file hash is currently calculated or not.
* @type {boolean}
*/
isCalculating: false,
/**
* An error on hash calculation.
* @type {any|null}
*/
error: null,
async handleSubmitFile(e){
if( this.isCalculating === true ) // Calculation already done for another file.
return;
this.error = null; // Reset.
const FILE = e.target.files[0];
if( !FILE )
return; // No file sent.
this.isCalculating = true;
try {
const RESULT = await calculateHashes(FILE);
await this.$wire.addHash(RESULT.filename, RESULT.crc32, RESULT.sha1); // Send a signal to livewire.
window.refreshIcons();
} catch(err) {
this.error = err.message;
} finally {
this.isCalculating = false;
}
}
}
}

View File

@@ -7,6 +7,7 @@ import hovercard from "./hovercard.js";
import notifications from "./notifications.js";
import conversations from "./conversations.js";
import settings from "./settings.js";
import { initMobileMenu } from "./mobile-menu.js";
/**
* Get config defined in meta.blade.php
@@ -43,3 +44,6 @@ Alpine.store('conversations', conversations() );
// Settings
Alpine.store('settings', settings() );
// Mobile Menu
document.addEventListener('DOMContentLoaded', initMobileMenu);

View File

@@ -0,0 +1,46 @@
export function initMobileMenu() {
const menuToggle = document.querySelector('.mobile-toggle');
const menu = document.getElementById('menu');
const app = document.getElementById('app');
const content = document.getElementById('content');
if (!menuToggle || !menu) return;
menuToggle.addEventListener('click', (e) => {
e.stopPropagation();
menu.classList.toggle('mobile-open');
app.classList.toggle('menu-open');
});
const menuItems = menu.querySelectorAll('.menu-item');
menuItems.forEach(item => {
item.addEventListener('click', () => {
menu.classList.remove('mobile-open');
app.classList.remove('menu-open');
});
});
document.addEventListener('click', (e) => {
const isClickInsideMenu = menu.contains(e.target);
const isClickOnToggle = menuToggle.contains(e.target);
if (!isClickInsideMenu && !isClickOnToggle && menu.classList.contains('mobile-open')) {
menu.classList.remove('mobile-open');
app.classList.remove('menu-open');
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && menu.classList.contains('mobile-open')) {
menu.classList.remove('mobile-open');
app.classList.remove('menu-open');
}
});
window.addEventListener('resize', () => {
if (window.innerWidth > 768) {
menu.classList.remove('mobile-open');
app.classList.remove('menu-open');
}
});
}

View File

@@ -27,7 +27,7 @@ const ERROR_TABLE = {
noGame: "Please provide a game or create a new one and fill all the required fields.",
noLanguages: "Please select at least a language.",
noAuthors: "Please provide at least an author or create a new one and fill all the required fields.",
noMainImage: "Please select a main image.",
noMainImage: "Please upload a main image.",
noGalleryImages: "Please select at least a gallery image.",
isSubmitting: "The entry is already during submission."
}
@@ -269,75 +269,75 @@ window.Submission = function(){
*/
verifyForm(){
console.log( "Step 1" );
console.info( "Step 1: During File upload" );
if( !SubmissionVerifications.step1_DuringFSUpload( this.Uploader ) ){
this.errorKey = "isUploading";
return false;
}
console.log( "Step 2" );
console.info( "Step 2: No files uploaded" );
if( !SubmissionVerifications.step2_NoFilesFSUpload( this.Uploader ) ){
this.errorKey = "noFiles";
return false;
}
console.log( "Step 3" );
console.info( 'Step 3: Error in file upload')
if( !SubmissionVerifications.step3_ErrorsFSUpload( this.Uploader ) ){
this.errorKey = "uploadError";
return false;
}
console.log( "Step 4" );
console.info("Step 4: All files uploaded");
if( !SubmissionVerifications.step4_AllFilesUploadedFSUpload( this.Uploader ) ){
this.errorKey = "notAllFilesDone";
return false;
}
if( SECTION() === "romhacks" || SECTION() === "lua-scripts" ){
console.log( "Step 5" );
console.info( "Step 5: Verify modifications")
if( !SubmissionVerifications.step5_RomhacksModificationsCheckboxes()){
this.errorKey = "noModifications";
return false;
}
} else if( SECTION() === "utilities" ){
console.log( "Step 5" );
console.info( "Step 5: Verify systems");
if( !SubmissionVerifications.step5_UtilitiesSystemsCheckboxes()){
this.errorKey = "noSystems";
return false;
}
}
console.log( "Step 6" );
console.info( "Step 6: Verify description");
if( !SubmissionVerifications.step6_VerifyDescription() ){
this.errorKey = "noDescription";
return false;
}
console.log( "Step 7" );
console.info( "Step 7: Verify game");
if( !SubmissionVerifications.step7_VerifyGame( this.$el ) ){
this.errorKey = "noGame";
return false;
}
console.log( "Step 8" );
console.info("Step 8: Verify languages");
if( !SubmissionVerifications.step8_LanguagesCheckboxes()){
this.errorKey = "noLanguages";
return false;
}
console.log( "Step 9" );
console.info( "Step 9: Verify authors" );
if( !SubmissionVerifications.step9_verifyAuthors()){
this.errorKey = "noAuthors";
return false;
}
console.log( "Step 10" );
console.info( "Step 10: Verify Main image" );
if( !SubmissionVerifications.step10_verifyMainImage( this.$el )){
this.errorKey = "noMainImage";
return false;
}
console.log( "Step 11" );
console.info( "Step 11: Verify gallery images" );
if( !SubmissionVerifications.step11_verifyGallery( this.$el )){
this.errorKey = "noGalleryImages";
return false;
@@ -367,9 +367,13 @@ window.Submission = function(){
isSubmitting: 'submitButton'
};
const target = this.$refs[refMap[this.errorKey]]
|| this.$el.querySelector('.upload-list')
|| this.$el.querySelector('.form-upload');
const targetKey = refMap[this.errorKey];
const target = this.$refs[targetKey]
|| this.$el.querySelector(`[data-target="${targetKey}"]`)
|| this.$el.querySelector(`[x-ref="${targetKey}"]`)
|| this.$el.querySelector('.upload-list')
|| this.$el.querySelector('.form-upload');
if (target) {
target.scrollIntoView({behavior: 'smooth', block: 'center'});

View File

@@ -31,6 +31,8 @@
<i data-lucide="messages-square" size="14"></i>
@elseif($item->type === 'club')
<i data-lucide="balloon" size="14"></i>
@elseif($item->type === 'review')
<i data-lucide="star" size="14"></i>
@else
<i data-lucide="target" size="14"></i>
@endif

View File

@@ -1,4 +1,4 @@
<div class="filter-group" x-data="{open:false,search:''}">
<div class="filter-group" x-data="{open:false}">
<div class="filter-title-row" @click="open = !open">
<div class="filter-title-left">
<h4 class="filter-title">{{ $title }}</h4>
@@ -18,11 +18,11 @@
<div x-show="open" x-transition>
<div class="internal-filter-search">
<i data-lucide="search" size="13"></i>
<input type="text" x-model="search" placeholder="Search...">
<input type="text" wire:model.live.debounce.300ms="{{ $searchModel }}" placeholder="Search...">
</div>
<div class="filter-options">
@foreach($items as $item)
<label class="filter-option" x-show="search.length >= 3 && '{{strtolower($item->{$nameProperty}) }}'.includes(search.toLowerCase())">
<label class="filter-option">
<input type="checkbox" wire:model.live="{{ $model }}" value="{{ $item->{$idProperty} }}">
{{ $item->{$nameProperty} }}
</label>

View File

@@ -1,4 +1,4 @@
<div class="filter-group" x-data="{open: false,search:''}">
<div class="filter-group" x-data="{open: false}">
<div class="filter-title-row" @click="open = !open">
<div class="filter-title-left">
<h4 class="filter-title">{{ $title }}</h4>
@@ -14,11 +14,11 @@
<div x-show="open" x-transition>
<div class="internal-filter-search">
<i data-lucide="search" size="13"></i>
<input type="text" x-model="search" placeholder="Search...">
<input type="text" wire:model.live.debounce.300ms="{{ $searchModel }}" placeholder="Search...">
</div>
<div class="filter-options">
@foreach($items as $item)
<label class="filter-option" x-show="search.length >= 3 && '{{strtolower($item->{$nameProperty}) }}'.includes(search.toLowerCase())">
<label class="filter-option">
<input type="checkbox" wire:model.live="{{ $model }}" value="{{ $item->{$idProperty} }}">
{{ $item->{$nameProperty} }}
</label>

View File

@@ -8,7 +8,7 @@
@endif
</div>
<div class="entry-card-info">
<a href="{{ route('entries.show', [ 'section' => $entry->type, 'entry' => $entry ] ) }}" class="entry-card-title">{{ $entry->title }}</a>
<a href="{{ route('entries.show', [ 'section' => $entry->type, 'entry' => $entry ] ) }}" class="entry-card-title">{{ $entry->title ?? $entry->complete_title }}</a>
<div class="entry-card-author">
@forelse( $entry->authors as $author)
@if($loop->first)By @endif

View File

@@ -1,3 +1,3 @@
<div class="block-error">
<i data-lucide="{{ $errorArray['icon'] ?? '' }}"></i> {{ sprintf( $errorArray['message'], $message ) }}
<i data-lucide="{{ $errorArray['icon'] ?? '' }}"></i> {!! sprintf( $errorArray['message'], $message ) !!}
</div>

View File

@@ -1,4 +1,4 @@
<div x-data="GalleryManager()" x-init="init(@js($oldPaths))">
<div x-data="GalleryManager()" x-init="init(@js($oldPaths))" x-ref="gallery-field">
<x-form-field-title name="Screenshots" helper="At least 1 Screenshot required, Maximum 20." required="{{ $required ? 'true' : 'false' }}" />
<div class="form-group main-image-grid">
<div class="form-upload" style="flex:1;" :class="{ 'disabled': isFull }">

View File

@@ -18,12 +18,11 @@
<i data-lucide="x"></i>
</button>
</div>
<div class="language-list" id="languages-group">
<div class="language-list" x-ref="languagesGroup" id="languages-group">
@foreach( $languages as $language )
<label class="language-item" x-show="'{{ strtolower($language->name) }}'.includes(search.toLowerCase())">
<input type="checkbox" name="languages[]" value="{{ $language->id }}" x-model="selected" :value="{{ $language->id }}" {{ in_array($language->id, $selected) ? 'checked' : '' }}> {{ $language->name }}
</label>
@endforeach
</div>
</div>

View File

@@ -1,4 +1,5 @@
<div x-data="MainImageManager()" x-init="init('{{$oldPath}}')">
<div x-data="MainImageManager()" x-init="init('{{$oldPath}}')" x-ref="main-image-field">
<x-form-field-title name="Main image" helper="This will show up on the index and on top of the entry. A screenshot or custom cover is prefered else all entries of same game will look the same." required="{{ $required ? 'true' : 'false' }}" />
<div class="form-group main-image-grid">
<div class="form-upload" style="flex:4;">

View File

@@ -3,12 +3,7 @@
window.mde_{{ $name }} = new EasyMDE({
element: $el.querySelector('#field_{{ $name }}'),
minHeight: '{{ $minHeight }}',
toolbar: {{ Js::from( $toolbar ) }},
autosave: {
enabled: true,
uniqueId: '{{ $name }}',
delay: 1000,
},
toolbar: {{ Js::from( $toolbar ) }}
})
"
>

View File

@@ -0,0 +1,23 @@
<div class="comment-block">
<x-xen-foro-avatar :user="$review->user_id" />
<div class="comment-content">
<div class="comment-meta">
<span class="comment-author">{{ $review->xenforoUser()?->username }}</span>
<span class="comment-separator"></span>
<x-review-star-rating :rating="$review->rating" />
<span class="comment-separator"></span>
<span class="comment-date">{{ $review->created_at->format('Y-m-d') }}</span>
@if( $entryShow && $review->entry()->exists() )
<span class="comment-separator"></span>
<a href="{{ route('entries.show', ['section' => $review->entry?->type, 'entry' => $review->entry ]) }}">{{ $review->entry->complete_title ?? $review->entry->title }}</a>
@endif
</div>
<div class="review-title">{{ $review->title }}</div>
<div class="comment-body markdown-body">
{!! $review->description_html !!}
</div>
</div>
</div>

View File

@@ -0,0 +1,5 @@
<span class="star-rating-display">
@for($i = 1; $i <= 5; $i++)
<i data-lucide="star" size="13" class="{{ $i <= $rating ? 'star-filled' : 'star-empty' }}"></i>
@endfor
</span>

View File

@@ -4,49 +4,121 @@
<i data-lucide="menu"></i>
</button>
<form class="search-bar" action="{{ route('entries.index') }}">
<input type="text" name="s" placeholder="Search" />
<button type="submit" class="search-button">
<i data-lucide="search" size="18" color="var(--text2)"></i>
</button>
</form>
<form id="topbar-search-form" class="search-bar" method="GET">
<select id="search-scope" class="search-scope-select" name="search_scope">
<option value="entries" selected>Entries</option>
<option value="news">News</option>
<option value="forum">Forum</option>
</select>
<input type="text" id="search-input" placeholder="Search" autocomplete="off">
<button type="submit" class="search-button">
<i data-lucide="search" size="18" color="var(--text2)"></i>
</button>
</form>
<script>
(function () {
const form = document.getElementById('topbar-search-form');
const select = document.getElementById('search-scope');
const input = document.getElementById('search-input');
const scopes = {
entries: { action: '{{ route('entries.index') }}', param: 's' },
news: { action: '{{ route('news.index') }}', param: 's' },
forum: {
action: '{{ xfRoute('search/2147483647/') }}',
param: 'q',
extra: { o: 'relevance' },
},
};
form.addEventListener('submit', function (e) {
e.preventDefault();
const term = input.value.trim();
if (!term) return;
const cfg = scopes[select.value];
const params = new URLSearchParams({ [cfg.param]: term, ...(cfg.extra || {}) });
window.location = cfg.action + '?' + params.toString();
});
})();
</script>
<div class="topbar-actions">
@can('is-admin')
@php $topbarAdminSeparator = true; @endphp
<a href="{{ config('app.forum_url') . '/admin.php' }}" class="btn">
<a href="{{ config('app.forum_url') . '/admin.php' }}" class="btn topbar-admin-btn" title="Admin">
<i data-lucide="landmark" size="18"></i>
</a>
<a href="{{ config('app.url') . '/manage' }}" class="btn">
<a href="{{ config('app.url') . '/manage' }}" class="btn topbar-admin-btn" title="Manage">
<i data-lucide="shield-cog" size="18"></i>
</a>
@endcan
@if( $topbarAdminSeparator )
<div class="vertical-separator"></div>
<div class="vertical-separator topbar-admin-btn"></div>
@endif
@can('is-mod')
@php $topbarModSeparator = true; @endphp
<a href="{{ route('modcp.index') }}" class="btn">
<a href="{{ route('modcp.index') }}" class="btn topbar-mod-btn" title="Moderation">
<i data-lucide="siren" size="18"></i>
</a>
<a href="{{ xfRoute('approval-queue') }}" class="btn">
<a href="{{ xfRoute('approval-queue') }}" class="btn topbar-mod-btn" title="Approvals">
<i data-lucide="message-circle-check" size="18"></i>
</a>
<a href="{{ xfRoute('reports') }}" class="btn">
<a href="{{ xfRoute('reports') }}" class="btn topbar-mod-btn" title="Reports">
<i data-lucide="triangle-alert" size="18"></i>
</a>
@endcan
@if( $topbarModSeparator )
<div class="vertical-separator"></div>
<div class="vertical-separator topbar-mod-btn"></div>
@endif
{{-- Users --}}
<div x-data="{ open: false }" class="topbar-more-container" style="position: relative;">
@canany(['is-admin','is-mod'])
<button @click="open = !open" @click.outside="open = false" class="btn topbar-btn-more" title="More actions">
<i data-lucide="more-vertical" size="18"></i>
</button>
<div x-show="open" class="topbar-more-menu">
@can('is-admin')
<a href="{{ config('app.forum_url') . '/admin.php' }}" class="topbar-more-item" title="Admin">
<i data-lucide="landmark" size="16"></i>
<span>Admin</span>
</a>
<a href="{{ config('app.url') . '/manage' }}" class="topbar-more-item" title="Manage">
<i data-lucide="shield-cog" size="16"></i>
<span>Manage</span>
</a>
@endcan
@can('is-mod')
<a href="{{ route('modcp.index') }}" class="topbar-more-item" title="Moderation">
<i data-lucide="siren" size="16"></i>
<span>Mod CP</span>
</a>
<a href="{{ xfRoute('approval-queue') }}" class="topbar-more-item" title="Approvals">
<i data-lucide="message-circle-check" size="16"></i>
<span>Approval Queue</span>
</a>
<a href="{{ xfRoute('reports') }}" class="topbar-more-item" title="Reports">
<i data-lucide="triangle-alert" size="16"></i>
<span>Reports</span>
</a>
@endcan
</div>
@endcanany
</div>
@can('create','\App\Models\Entry')
<a href="{{ route('submit.index') }}" class="btn">
<a href="{{ route('submit.index') }}" class="btn" title="Submit">
<i data-lucide="hard-drive-upload" size="18"></i>
</a>
@endcan

View File

@@ -1,6 +1,7 @@
<span x-data class="userlink"
@mouseenter.debounce.300ms="$store.hovercard.open($el,'{{ route('dynamic.hovercard', ['user_id' => $user?->user_id ?? 0 ]) }}')"
@mouseleave="setTimeout(() => { const C = document.querySelector('.hovercard'); if(!C?.matches(':hover')) $store.hovercard.close(); }, 200)"
@click="window.location.href = '{{ xfRoute('members/.') . $user?->user_id }}'"
>
{{ $user->username }}
</span>

View File

@@ -1,9 +1,36 @@
<?php /** @var \App\Models\EntryReview $review */ ?>
@php if( !isset($entry) && isset($news) ){ $entry = $news; $newsMode = true; } else { $newsMode = false; } @endphp
<div class="{{ !$newsMode ? 'grid-c2' : '' }}" style="margin-top:1%;">
@if( !$newsMode )
<div id="reviews-section">
<div id="reviews-section" x-data="{reviewModalOpen: false}">
<div class="entry-content">
<x-entry-section-title label="Reviews" icon="star" />
<div class="review-header-right">
@if($entry->reviews_count_cached > 0)
<div class="review-avg-badge">
<i data-lucide="star" size="13"></i>
{{ $entry->average_rating }}
<span class="review-avg-count">({{ $entry->reviews_count_cached }})</span>
</div>
@endif
@auth
<button @click="reviewModalOpen = true" class="btn primary">
<i data-lucide="pen-line" size="13"></i> Write a review
</button>
@endauth
</div>
@forelse( $reviews as $review )
<x-review-card :review="$review" />
@empty
<span style="text-align:center" class="whisper">Be the first to post a review</span>
@endforelse
<a href="{!! reviewsRoute( ['entryId' => $entry->id ]) !!}" class="modcp-list-see-all">
See all {{ $entry->reviews_count_cached }} reviews
</a>
@include('reviews.submit')
</div>
</div>
@endif
@@ -11,7 +38,7 @@
<div class="entry-content">
<x-entry-section-title label="Last comments" icon="message-circle" />
@forelse( $comments as $comment )
@if($comment['user_id'] === config('xenforo.bot_user_id') && $comment['position'] == 0)
@if(( $comment['user_id'] === config('xenforo.bot_user_id') || $comment['username'] === 'Romhack Plaza' ) && $comment['position'] == 0)
@continue
@else
<div class="comment-block">
@@ -19,7 +46,7 @@
<div class="comment-content">
<div class="comment-meta">
<span class="comment-author">{{ $comment['User']['username'] }}</span>
<span class="comment-author">{{ $comment['User']['username'] ?? $comment['username'] ?? 'Guest' }}</span>
<span class="comment-separator"></span>
<span class="comment-date">{{ date('Y-m-d', $comment['post_date']) }}</span>
</div>

View File

@@ -19,7 +19,7 @@
<div class="entry-info">
<h1 class="entry-title">
{{ $entry->title }}
{{ $entry->title ?? $entry->complete_title }}
@if( $entry->state === 'pending' )
<div style="display:inline;color:var(--rhpz-orange);">
-
@@ -53,17 +53,7 @@
@endif
</h1>
<div class="entry-authors">
@forelse( $entry->authors as $author)
@if($loop->first)
By
@endif
{{ $author->name }}
@if( !$loop->last )
,
@endif
@empty
No authors
@endforelse
By <a href="{!! databaseRoute( [ 'authors' => $entry->authors->pluck('id')->toArray() ] ) !!}">{{ $entry->authors->pluck('name')->implode(', ') }}</a>
</div>
<div class="entry-submission-byline">
@if($entry->user_id)
@@ -223,7 +213,7 @@
@if( $entry->description )
<x-entry-section-title label="Description" icon="file-text"/>
<div class="entry-description">
{{ $entry->description }}
{!! $entry->description_html !!}
</div>
@endif
@if( $entry->hashes->isNotEmpty() )

View File

@@ -4,9 +4,13 @@
@section('content')
{{ dd( $VISITOR ) }}
@include('activity.latest-news')
@include('activity.featured-entries')
<div class="activity-tl-header" x-data x-init="$store.settings.currentActivityFilters = {{ Js::from($activeFilters) }};">
<h2 class="activity-tl-title">
<i data-lucide="radio-tower" size="18"></i>
Latest activity

View File

@@ -12,7 +12,7 @@
<body>
<div id="app">
@include( 'components.menu' )
@include( 'layouts.menu' )
<main id="main-wrapper">
@include('components.topbar')
@@ -29,6 +29,7 @@
</main>
</div>
@include('components.hovercard')
@livewireScripts
@stack('scripts')

View File

@@ -7,17 +7,24 @@
<div class="menu-navigation">
@foreach( config('menu') as $menu )
<div class="menu-group">
<div class="menu-group-title">{{ $menu['name'] }}</div>
<div class="menu-group-title">
{{ $menu['name'] }}
</div>
@foreach( $menu['items'] as $item )
@if( !isset( $item['requires_auth'] ) || $item['requires_auth'] === true && !\Auth::guest() )
@if( !isset( $item['requires_auth'] ) || $item['requires_auth'] === true && $VISITOR->loggedIn() )
<a href="{{ isset($item['xf_route']) ? xfRoute($item['xf_route']) : route($item['route']) }}"
@class(['menu-item', 'active' => request()->routeIs( $item['route'] ?? '' )]) >
<i data-lucide="{{ $item['icon'] }}"></i><span>{{ $item['name'] }}</span>
</a>
@endif
@endforeach
</div>
@endforeach
</div>
@@ -28,11 +35,11 @@
</div>
<div class="menu-user-info">
<span class="username">
{{ \Auth::user()?->username ?? "Guest" }}
{{ $VISITOR->username ?? "Guest" }}
</span>
<span class="user_role">
<a href="{{ \Auth::guest() ? xfRoute('login') : xfRoute('logout') . '?t=' . xfCsrfToken() }}">
{{ \Auth::guest() ? 'Login' : 'Logout' }}
<a href="{{ $VISITOR->guest() ? xfRoute('login') : xfRoute('logout') . '?t=' . xfCsrfToken() }}">
{{ $VISITOR->guest() ? 'Login' : 'Logout' }}
</a>
</span>
</div>

View File

@@ -1,5 +1,5 @@
<div>
<div class="form-group grid-c2">
<div class="form-group grid-c2" x-ref="authorsSelector">
<div>
@if( $newAuthor === false)
<div class="game-selector">

View File

@@ -6,7 +6,7 @@
placeholder="Search..."
class="form-input filter-bar-search"
>
@if( $search || $types || $platforms || $games || $statuses || $authors || $languages || $modifications || $categories || $levels || $systems )
@if( $search || $types || $platforms || $games || $statuses || $authors || $languages || $modifications || $categories || $levels || $systems || $userId )
<button type="button" wire:click="clearFilters" class="btn">
<i data-lucide="x"></i> Clear filters
</button>
@@ -32,7 +32,7 @@
</div>
{{-- Games --}}
<x-database-filter-without-mode-search title="Game" :items="$allGames" model="games" />
<x-database-filter-without-mode-search title="Game" :items="$allGames" model="games" search-model="gameSearch" />
{{-- Platforms --}}
<x-database-filter-without-mode title="Platform" :items="$allPlatforms" model="platforms"/>
@@ -44,7 +44,7 @@
<x-database-filter-without-mode title="Status" :items="$allStatuses" model="statuses"/>
{{-- Authors --}}
<x-database-filter-with-mode-search title="Authors" :items="$allAuthors" model="authors" mode-model="authorsMode" :selected-mode="$authorsMode" />
<x-database-filter-with-mode-search title="Authors" :items="$allAuthors" search-model="authorSearch" model="authors" mode-model="authorsMode" :selected-mode="$authorsMode" />
{{-- Languages --}}
<x-database-filter-with-mode title="Languages" :items="$allLanguages" model="languages" mode-model="languagesMode" :selected-mode="$languagesMode" />

View File

@@ -84,7 +84,7 @@
class="dropdown-item" {{ $gameId === $game->id ? 'selected' : '' }} >
<span class="dropdown-item-name">{{ $game->name }}</span>
@if($game->platform)
<span class="badge">{{ $game->platform->short_name }}</span>
<span class="badge">{{ $game->platform->short_name ?? $game->platform->name }}</span>
@endif
@if($game->genre)
<span class="badge">{{ $game->genre->name }}</span>

View File

@@ -0,0 +1,68 @@
<div class="patcher-container" x-data="HashesChecker($wire)"
x-ref="hashRoot" @refresh.window="refreshIcons($el)">
<div class="form-group">
<label class="form-label">ROM</label>
<div class="form-upload">
<input type="file" id="rom-field" @change="handleSubmitFile($event)" :disabled="isCalculating">
<div class="form-upload-placeholder level">
<i data-lucide="file-archive" size="36" style="margin-bottom:15px;color:var(--text2)"></i>
<div style="font-size: 1.1rem;color:var(--text);margin-bottom:5px;">
<span x-text="isCalculating ? 'Please wait' : 'Click or drag\'n drop files here.'"></span>
</div>
</div>
</div>
</div>
@if($hash)
<div>
<x-form-field-title name="Hash" />
<div class="form-group grid-hashes">
<div class="hash-filename hash-first">
<x-form-field-title name="Filename" />
</div>
<div class="hash-crc32 hash-first">
<x-form-field-title name="CRC32" />
</div>
<div class="hash-sha1 hash-first">
<x-form-field-title name="SHA-1" />
</div>
<div class="hash-verified hash-first">
<x-form-field-title name="Verified" />
</div>
<div class="hash-remove hash-first">
<x-form-field-title name="Actions" />
</div>
<div class="hash-filename">
<input class="form-input" type="text" autocomplete="off" value="{{ $hash['filename'] }}" disabled>
</div>
<div class="hash-crc32">
<input class="form-input" type="text" autocomplete="off" value="{{ $hash['hash_crc32'] }}" disabled>
</div>
<div class="hash-sha1">
<input class="form-input" type="text" autocomplete="off" value="{{ $hash['hash_sha1'] }}" disabled>
</div>
<div class="hash-verified">
<input class="form-input" type="text" autocomplete="off" value="{{ $hash['verified'] }}" disabled>
</div>
<div class="hash-remove">
<button type="button" class="btn" wire:click="removeHash()">
Remove
</button>
</div>
</div>
<x-form-field-title name="Related entries" />
<div class="form-group">
<div class="grid-entries">
@forelse( $entries as $entry )
<x-entry-card :entry="$entry" />
@empty
No entries related.
@endforelse
</div>
</div>
</div>
@endif
</div>

View File

@@ -3,25 +3,24 @@
@if( count( $hashes ) > 0)
<div class="form-group grid-hashes">
<div class="hash-filename">
<div class="hash-filename hash-first">
<x-form-field-title name="Filename" />
</div>
<div class="hash-crc32">
<div class="hash-crc32 hash-first">
<x-form-field-title name="CRC32" />
</div>
<div class="hash-sha1">
<div class="hash-sha1 hash-first">
<x-form-field-title name="SHA-1" />
</div>
<div class="hash-verified">
<div class="hash-verified hash-first">
<x-form-field-title name="Verified" />
</div>
<div class="hash-remove">
<div class="hash-remove hash-first">
<x-form-field-title name="Actions" />
</div>
@foreach( $hashes as $i => $hash )

View File

@@ -0,0 +1,51 @@
<div @filters-updated.window="refreshIcons($el)">
<div class="database-search filter-bar">
@if($rating || $entryId)
<button type="button" wire:click="clearFilters" class="btn">
<i data-lucide="x"></i> Clear filters
</button>
@endif
</div>
<div class="database-wrapper">
<aside class="database-filters">
<div class="filter-group" x-data="{open: true}">
<div class="filter-title-row" @click="open = !open">
<h4 class="filter-title">Rating</h4>
<i data-lucide="chevron-down" size="14" class="filter-chevron" :class="{ 'rotated': !open }"></i>
</div>
<div class="filter-options" x-show="open" x-transition>
@for($i=1;$i<=5;$i++)
<label class="filter-option">
<input type="radio" wire:model.live="rating" value="{{ $i }}">
{{ $i }}
</label>
@endfor
</div>
</div>
</aside>
<div class="database-results">
<div class="database-sort">
@foreach( \App\Livewire\Reviews::SORT_OPTIONS as $k => $v )
<button type="button" wire:click="setSort('{{ $k }}')" class="btn {{ $sortBy === $k ? 'active' : '' }}">
{{ $v }}
</button>
@if( $sortBy === $k )
<i data-lucide="{{ $sortDir === 'asc' ? 'arrow-up' : 'arrow-down' }}"></i>
@endif
@endforeach
<span class="database-results-count">{{ $reviews->total() }} results</span>
</div>
<div class="entry-content">
@forelse($reviews as $review)
<x-review-card :review="$review" :entry-show="true" />
@empty
<p>No reviews found.</p>
@endforelse
</div>
{{ $reviews->links() }}
</div>
</div>
</div>

View File

@@ -44,7 +44,7 @@
</h1>
<div class="news-meta">
@if($news->category_id)
<a href="#" class="meta-item">
<a href="{{ newsRoute( ['categories' => [ $news->category_id ] ]) }}" class="meta-item">
<i data-lucide="book" size="16"></i>
{{ $news->category->name }}
</a>
@@ -131,7 +131,7 @@
<div class="news-main-content entry-content">
@if( $news->description )
<div class="news-body-text">
{{ $news->description }}
{!! $news->description_html !!}
</div>
@endif
@if( $news->gallery->isNotEmpty() )
@@ -156,7 +156,7 @@
</div>
<aside class="news-sidebar">
@if($news->entry->exists())
@if($news->entry()->exists())
<div class="sidebar-block">
<h3 class="sidebar-title">
<i data-lucide="content" size="18"></i>

View File

@@ -0,0 +1,7 @@
@extends('layouts.app')
@section('page-title', "Forbidden - " . config('app.name') )
@section('content')
<x-error-block error-type="user-state-not-valid" message="{{ $reason }}" />
@endsection

View File

@@ -0,0 +1,11 @@
@extends('layouts.app')
@section('page-title', "Database - " . config('app.name') )
@section('content')
{{ \Diglactic\Breadcrumbs\Breadcrumbs::render() }}
<div class="page-title">
Reviews
</div>
@livewire('reviews')
@endsection

View File

@@ -0,0 +1,70 @@
<div
class="modal-overlay"
x-show="reviewModalOpen"
x-effect="reviewModalOpen && $nextTick(() => window.refreshIcons($el))"
x-cloak
x-transition.opacity.duration.300ms
@keydown.escape.window="reviewModalOpen = false"
@focus="window.refreshIcons($el)"
>
<div @click.outside="reviewModalOpen = false" class="modal-window">
<div class="modal-header">
<span class="modal-title">Write a review</span>
<button @click="reviewModalOpen = false" class="modal-close">
<i data-lucide="x" size="18"></i>
</button>
</div>
<form method="POST" action="{{ route('reviews.store', $entry) }}" class="modal-content">
@csrf
<div class="form-group" x-data="{ rating: {{ old('rating', 0) }} }">
<label class="form-label">Your rating</label>
<div class="star-input">
<template x-for="i in 5" :key="i">
<span
@click="rating = i"
:class="i <= rating ? 'star-filled' : 'star-empty'"
class="star-input-icon"
>
<i data-lucide="star" size="22"></i>
</span>
</template>
<input type="hidden" name="rating" :value="rating">
</div>
@error('rating')
<span class="form-error">{{ $message }}</span>
@enderror
</div>
<div class="form-group">
<label class="form-label" for="review-title">Title</label>
<input
type="text"
id="review-title"
name="title"
class="form-input"
maxlength="100"
value="{{ old('title') }}"
>
@error('title')
<span class="form-error">{{ $message }}</span>
@enderror
</div>
<div class="form-group">
<label class="form-label" for="review-description">Review</label>
<x-markdown-textarea name="description" value="{{ old('description', '') }}" />
@error('description')
<span class="form-error">{{ $message }}</span>
@enderror
</div>
<button type="submit" class="btn primary" style="width: 100%; justify-content: center;">
<i data-lucide="send" size="14"></i> Post review
</button>
</form>
</div>
</div>

View File

@@ -110,6 +110,7 @@
@if( section_must_be( [ 'translations', 'utilities', 'documents'] , $section ) )
<x-form-field-title name="Languages" required="true" />
<x-languages-selector :selected="$oldLanguages" />
<div class="form-error-text" x-show="errorKey === 'noLanguages'" x-text="errorMessage"></div>
@error('languages')
<x-form-error-text message="{{ $message }}" />
@enderror
@@ -158,6 +159,7 @@
@if( section_must_not_be( [ 'translations', 'utilities', 'documents' ], $section ) )
<x-form-field-title name="Languages" required="true" />
<x-languages-selector :selected="$oldLanguages" />
<div class="form-error-text" x-show="errorKey === 'noLanguages'" x-text="errorMessage"></div>
@error('languages')
<x-form-error-text message="{{ $message }}" />
@enderror
@@ -168,7 +170,9 @@
<x-form-group-title label="{{ $words['attachments'] }}" icon="paperclip" />
<x-main-image-field :old-path="old('main-image', $entry->main_image ?? '') ?? ''" />
<div class="form-error-text" x-show="errorKey === 'noMainImage'" x-text="errorMessage"></div>
<x-gallery-field :old-paths="old('gallery', $entry->gallery->pluck('image')->toArray() ?? [] )"/>
<div class="form-error-text" x-show="errorKey === 'noGalleryImages'" x-text="errorMessage"></div>
@error('gallery')
<x-form-error-text message="{{ $message }}" />
@enderror
@@ -178,6 +182,7 @@
<x-form-group-title label="{{ $words['authors'] }}" icon="users" />
<livewire:authors-selector :old-authors="old('authors', $entry->authors->map(fn($a) => ['id' => $a->id, 'name' => $a->name ])->toArray() ?? [])" :old-new-authors="old('new-authors', [])" />
<div class="form-error-text" x-show="errorKey === 'noAuthors'" x-text="errorMessage"></div>
@error('authors')
<x-form-error-text message="{{ $message }}" />
@enderror
@@ -225,6 +230,7 @@
<x-form-field-title name="Metadata" required="true" />
<div class="form-type-of-checkboxes form-group level" id="entry-metadata">
<label><input class="form-checkbox" type="checkbox" name="featured" value="1" {{ old('featured', $entry->featured ) ? 'checked' : '' }}>Featured entry</label>
<label><input class="form-checkbox" type="checkbox" name="refresh_created_at" value="1">Refresh created at</label>
</div>
</div>
@endcan
@@ -235,9 +241,10 @@
@csrf
<div class="submit">
<div class="submit" x-ref="submitButton">
<x-submit-entry-status :section="$section" :is-edit="$isEdit" :current-state="$entry->state ?? null" :entry="$entry" />
<button id="submit-button" type="submit" class="btn primary" style="padding:1%;">Submit</button>
</div>
<div class="form-error-text" x-show="errorKey === 'isSubmitting'" x-text="errorMessage"></div>
</form>
</div>

View File

@@ -0,0 +1,16 @@
@extends('layouts.app')
@section('page-title', "ROM Patcher - " . config('app.name'))
@push('scripts')
@vite('resources/js/HashesChecker.js')
@endpush
@section('content')
<div class="page-title">
<span>ROM Hasher</span>
</div>
<livewire:hashes-checker />
@endsection