diff --git a/public/css/styles.css b/public/css/styles.css index 6f4dd5a..8f17cb4 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -11,43 +11,33 @@ @layer base { /* Sticky footer base styles */ html { - height: 100%; + @apply h-full; } body { - min-height: 100%; - display: flex; - flex-direction: column; + @apply min-h-full flex flex-col; } h1, h2, h3, h4, h5, h6 { - font-family: 'Minecraft', sans-serif; /* Apply Minecraft font to headers */ + font-family: 'Minecraft', sans-serif; } #app { - flex: 1 0 auto; /* Make #app grow to fill available space */ - display: flex; - flex-direction: column; + @apply flex-1 flex flex-col; } main { - flex-grow: 1; /* Ensure main grows within #app */ + @apply flex-grow; } footer { - flex-shrink: 0; /* Prevent footer from shrinking */ + @apply flex-shrink-0; } } @layer components { .spinner { - border: 4px solid rgba(255, 225, 225, 0.3); - border-top: 4px solid #ffffff; - border-radius: 50%; - width: 24px; - height: 24px; - animation: spin 1s linear infinite; - display: inline-block; + @apply border-4 border-gray-700 border-t-white rounded-full w-6 h-6 animate-spin inline-block; } @keyframes spin { @@ -56,234 +46,208 @@ } #notificationContainer { - position: fixed; - bottom: 20px; - right: 20px; - z-index: 1000; - display: flex; - flex-direction: column-reverse; - gap: 8px; + @apply fixed bottom-5 right-5 z-[1000] flex flex-col-reverse gap-2; } .notification { - background-color: #1f2937; - color: white; - padding: 16px; - border-radius: 8px; - display: flex; - align-items: center; - gap: 12px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - transition: opacity 0.3s ease; + @apply bg-gray-800 text-white p-4 rounded-lg flex items-center gap-3 shadow-lg transition-opacity duration-300; } .notification.success { - background-color: #158106; + @apply bg-green-700; } .notification.error { - background-color: #b91c1c; + @apply bg-red-700; } #dockerLogsTerminal { - background-color: #1f2937; - padding: 1rem; - border-radius: 0.5rem; - max-height: 12rem; - width: 100%; + @apply bg-gray-800 p-4 rounded-lg max-h-48 w-full; } .xterm .xterm-viewport { - overflow-y: auto; + @apply overflow-y-auto; scrollbar-width: thin; - scrollbar-color: #4b5563 #1f2937; + scrollbar-color: #4B5563 #1F2937; } .xterm .xterm-viewport::-webkit-scrollbar { - width: 8px; + @apply w-2; } .xterm .xterm-viewport::-webkit-scrollbar-track { - background: #1f2937; + @apply bg-gray-800; } .xterm .xterm-viewport::-webkit-scrollbar-thumb { - background: #4b5563; - border-radius: 4px; + @apply bg-gray-600 rounded; } .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { - background: #6b7280; + @apply bg-gray-500; } .xterm .xterm-screen { - width: 100% !important; + @apply w-full; } .control-btn { - padding: 0.5rem 1rem; - font-size: 0.875rem; - line-height: 1.25rem; - transition: all 0.2s ease; - min-width: 80px; - text-align: center; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + @apply px-4 py-2 text-sm font-medium transition-all duration-200 min-w-[80px] text-center shadow-sm; } .control-btn:hover:not(.disabled-btn) { - transform: translateY(-1px); + @apply -translate-y-px; } .control-btn:active:not(.disabled-btn) { - transform: translateY(0); + @apply translate-y-0; } - /* Existing player button styles */ + /* Player button styles */ .tell-player { - @apply bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded text-sm; - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; + @apply bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200; } .give-player { - @apply bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-sm; - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; + @apply bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200; } .op-player, .deop-player { - @apply bg-purple-600 hover:bg-purple-700 px-2 py-1 rounded text-sm; - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; + @apply bg-purple-600 hover:bg-purple-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200; } .kick-player, .ban-player { - @apply bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-sm; - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; + @apply bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200; } - /* New player button styles */ .teleport-player { - @apply bg-cyan-600 hover:bg-cyan-700 px-2 py-1 rounded text-sm; - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; + @apply bg-cyan-600 hover:bg-cyan-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200; } .teleport-player:hover:not(.disabled-btn) { - transform: translateY(-1px); + @apply -translate-y-px; } .teleport-player:active:not(.disabled-btn) { - transform: translateY(0); + @apply translate-y-0; } .effect-player { - @apply bg-teal-600 hover:bg-teal-700 px-2 py-1 rounded text-sm; - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; + @apply bg-teal-600 hover:bg-teal-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200; } .effect-player:hover:not(.disabled-btn) { - transform: translateY(-1px); + @apply -translate-y-px; } .effect-player:active:not(.disabled-btn) { - transform: translateY(0); + @apply translate-y-0; } .modal { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.75); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; + @apply fixed inset-0 bg-black/75 flex items-center justify-center z-[1000]; } .modal-content { - background: #1f2937; - padding: 1.5rem; - border-radius: 0.5rem; - width: 100%; - max-width: 600px; - position: relative; - max-height: 80vh; - overflow-y: auto; + @apply bg-gray-800 p-6 rounded-lg w-full max-w-xl relative max-h-[80vh] overflow-y-auto; scrollbar-width: thin; - scrollbar-color: #4b5563 #1f2937; + scrollbar-color: #4B5563 #1F2937; } .modal-content::-webkit-scrollbar { - width: 8px; + @apply w-2; } .modal-content::-webkit-scrollbar-track { - background: #1f2937; + @apply bg-gray-800; } .modal-content::-webkit-scrollbar-thumb { - background: #4b5563; - border-radius: 4px; + @apply bg-gray-600 rounded; } .modal-content::-webkit-scrollbar-thumb:hover { - background: #6b7280; + @apply bg-gray-500; } .modal-close { - position: absolute; - top: 0.5rem; - right: 0.5rem; - background: none; - border: none; - color: white; - font-size: 1.25rem; - cursor: pointer; + @apply absolute top-2 right-2 bg-transparent border-none text-white text-xl cursor-pointer; } .disabled-btn { - opacity: 0.5; - cursor: not-allowed; - pointer-events: none; + @apply opacity-50 cursor-not-allowed pointer-events-none; } .disabled-btn:hover { - cursor: not-allowed; + @apply cursor-not-allowed; + } + + /* Search box and properties fields */ + #searchContainer { + @apply mb-4 block; + } + + #propertiesSearch { + @apply bg-gray-700 px-4 py-2 rounded text-white w-full block; + } + + #propertiesList { + @apply space-y-2; + } + + /* Toggle switch styles */ + [role="switch"] { + @apply inline-block relative w-10 h-6 cursor-pointer z-10; + } + + [role="switch"] > div:first-child { + @apply w-full h-full rounded-full transition-colors duration-200 ease-in-out; + } + + [role="switch"] > div:last-child { + @apply absolute w-4 h-4 rounded-full bg-white top-1 transition-all duration-200 ease-in-out z-[11]; + } + + [role="switch"][aria-checked="true"] > div:first-child { + @apply bg-green-600; + } + + [role="switch"][aria-checked="false"] > div:first-child { + @apply bg-gray-600; + } + + [role="switch"][aria-checked="true"] > div:last-child { + @apply left-6; + } + + [role="switch"][aria-checked="false"] > div:last-child { + @apply left-1; } } @layer utilities { - /* Add any custom utilities if needed */ + /* Custom utilities */ } /* Media queries */ @media (max-width: 640px) { .bg-gray-800.p-6.rounded-lg.shadow-lg .grid { - grid-template-columns: 1fr; + @apply grid-cols-1; } .bg-gray-800.p-6.rounded-lg.shadow-lg p { - display: flex; - flex-direction: column; - gap: 0.5rem; - word-break: break-all; + @apply flex flex-col gap-2 break-all; } .bg-gray-800.p-6.rounded-lg.shadow-lg a, .bg-gray-800.p-6.rounded-lg.shadow-lg span { - word-break: break-all; - overflow-wrap: anywhere; - white-space: normal; + @apply break-words whitespace-normal; } .bg-gray-800.p-6.rounded-lg.shadow-lg button { - width: 100%; - margin-top: 0.5rem; + @apply w-full mt-2; } - /* Ensure player buttons stack nicely on mobile */ .tell-player, .give-player, .teleport-player, @@ -292,17 +256,15 @@ .deop-player, .kick-player, .ban-player { - width: 100%; - text-align: center; - margin-top: 0.25rem; + @apply w-full text-center mt-1; } } /* Additional styles */ .bg-gray-800.p-6.rounded-lg.shadow-lg .grid { - overflow-x: hidden; + @apply overflow-x-hidden; } -.bg-gray-800.p-6.rounded-lg.shadow p { - max-width: 100%; +.bg-gray-800.p-6.rounded-lg.shadow-lg p { + @apply max-w-full; } \ No newline at end of file diff --git a/public/css/styles.min.css b/public/css/styles.min.css index 905c326..51e252d 100644 --- a/public/css/styles.min.css +++ b/public/css/styles.min.css @@ -32,6 +32,7 @@ --color-purple-600: oklch(55.8% 0.288 302.321); --color-purple-700: oklch(49.6% 0.265 301.924); --color-gray-400: oklch(70.7% 0.022 261.325); + --color-gray-500: oklch(55.1% 0.027 264.364); --color-gray-600: oklch(44.6% 0.03 256.802); --color-gray-700: oklch(37.3% 0.034 259.733); --color-gray-800: oklch(27.8% 0.033 256.848); @@ -40,6 +41,7 @@ --color-white: #fff; --spacing: 0.25rem; --container-md: 28rem; + --container-xl: 36rem; --text-sm: 0.875rem; --text-sm--line-height: calc(1.25 / 0.875); --text-lg: 1.125rem; @@ -53,6 +55,10 @@ --font-weight-bold: 700; --leading-relaxed: 1.625; --radius-lg: 0.5rem; + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --animate-spin: spin 1s linear infinite; + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-font-family: var(--font-sans); --default-mono-font-family: var(--font-mono); } @@ -203,9 +209,15 @@ } } @layer utilities { + .absolute { + position: absolute; + } .fixed { position: fixed; } + .relative { + position: relative; + } .static { position: static; } @@ -263,6 +275,9 @@ .hidden { display: none; } + .inline-block { + display: inline-block; + } .h-24 { height: calc(var(--spacing) * 24); } @@ -326,6 +341,9 @@ .flex-wrap { flex-wrap: wrap; } + .items-baseline { + align-items: baseline; + } .items-center { align-items: center; } @@ -358,6 +376,13 @@ margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); } } + .space-x-1 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse))); + } + } .space-x-2 { :where(& > :not(:last-child)) { --tw-space-x-reverse: 0; @@ -384,6 +409,9 @@ .rounded { border-radius: 0.25rem; } + .rounded-full { + border-radius: calc(infinity * 1px); + } .rounded-lg { border-radius: var(--radius-lg); } @@ -432,6 +460,9 @@ .bg-teal-600 { background-color: var(--color-teal-600); } + .bg-white { + background-color: var(--color-white); + } .bg-yellow-600 { background-color: var(--color-yellow-600); } @@ -528,6 +559,18 @@ --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .filter { + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); + } + .transition { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .ease-in-out { + --tw-ease: var(--ease-in-out); + transition-timing-function: var(--ease-in-out); + } .hover\:bg-blue-700 { &:hover { @media (hover: hover) { @@ -627,16 +670,16 @@ height: 100%; } body { - min-height: 100%; display: flex; + min-height: 100%; flex-direction: column; } h1, h2, h3, h4, h5, h6 { font-family: 'Minecraft', sans-serif; } #app { - flex: 1 0 auto; display: flex; + flex: 1; flex-direction: column; } main { @@ -648,13 +691,15 @@ } @layer components { .spinner { - border: 4px solid rgba(255, 225, 225, 0.3); - border-top: 4px solid #ffffff; - border-radius: 50%; - width: 24px; - height: 24px; - animation: spin 1s linear infinite; display: inline-block; + height: calc(var(--spacing) * 6); + width: calc(var(--spacing) * 6); + animation: var(--animate-spin); + border-radius: calc(infinity * 1px); + border-style: var(--tw-border-style); + border-width: 4px; + border-color: var(--color-gray-700); + border-top-color: var(--color-white); } @keyframes spin { 0% { @@ -666,258 +711,373 @@ } #notificationContainer { position: fixed; - bottom: 20px; - right: 20px; + right: calc(var(--spacing) * 5); + bottom: calc(var(--spacing) * 5); z-index: 1000; display: flex; flex-direction: column-reverse; - gap: 8px; + gap: calc(var(--spacing) * 2); } .notification { - background-color: #1f2937; - color: white; - padding: 16px; - border-radius: 8px; display: flex; align-items: center; - gap: 12px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - transition: opacity 0.3s ease; + gap: calc(var(--spacing) * 3); + border-radius: var(--radius-lg); + background-color: var(--color-gray-800); + padding: calc(var(--spacing) * 4); + color: var(--color-white); + --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + transition-property: opacity; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + --tw-duration: 300ms; + transition-duration: 300ms; } .notification.success { - background-color: #158106; + background-color: var(--color-green-700); } .notification.error { - background-color: #b91c1c; + background-color: var(--color-red-700); } #dockerLogsTerminal { - background-color: #1f2937; - padding: 1rem; - border-radius: 0.5rem; - max-height: 12rem; + max-height: calc(var(--spacing) * 48); width: 100%; + border-radius: var(--radius-lg); + background-color: var(--color-gray-800); + padding: calc(var(--spacing) * 4); } .xterm .xterm-viewport { overflow-y: auto; scrollbar-width: thin; - scrollbar-color: #4b5563 #1f2937; + scrollbar-color: #4B5563 #1F2937; } .xterm .xterm-viewport::-webkit-scrollbar { - width: 8px; + width: calc(var(--spacing) * 2); } .xterm .xterm-viewport::-webkit-scrollbar-track { - background: #1f2937; + background-color: var(--color-gray-800); } .xterm .xterm-viewport::-webkit-scrollbar-thumb { - background: #4b5563; - border-radius: 4px; + border-radius: 0.25rem; + background-color: var(--color-gray-600); } .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { - background: #6b7280; + background-color: var(--color-gray-500); } .xterm .xterm-screen { - width: 100% !important; + width: 100%; } .control-btn { - padding: 0.5rem 1rem; - font-size: 0.875rem; - line-height: 1.25rem; - transition: all 0.2s ease; min-width: 80px; + padding-inline: calc(var(--spacing) * 4); + padding-block: calc(var(--spacing) * 2); text-align: center; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium); + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + --tw-duration: 200ms; + transition-duration: 200ms; } .control-btn:hover:not(.disabled-btn) { - transform: translateY(-1px); + --tw-translate-y: -1px; + translate: var(--tw-translate-x) var(--tw-translate-y); } .control-btn:active:not(.disabled-btn) { - transform: translateY(0); + --tw-translate-y: calc(var(--spacing) * 0); + translate: var(--tw-translate-x) var(--tw-translate-y); } .tell-player { border-radius: 0.25rem; background-color: var(--color-blue-600); padding-inline: calc(var(--spacing) * 2); padding-block: calc(var(--spacing) * 1); + font-family: Minecraft; font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + --tw-duration: 200ms; + transition-duration: 200ms; &:hover { @media (hover: hover) { background-color: var(--color-blue-700); } } - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; } .give-player { border-radius: 0.25rem; background-color: var(--color-green-600); padding-inline: calc(var(--spacing) * 2); padding-block: calc(var(--spacing) * 1); + font-family: Minecraft; font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + --tw-duration: 200ms; + transition-duration: 200ms; &:hover { @media (hover: hover) { background-color: var(--color-green-700); } } - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; } .op-player, .deop-player { border-radius: 0.25rem; background-color: var(--color-purple-600); padding-inline: calc(var(--spacing) * 2); padding-block: calc(var(--spacing) * 1); + font-family: Minecraft; font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + --tw-duration: 200ms; + transition-duration: 200ms; &:hover { @media (hover: hover) { background-color: var(--color-purple-700); } } - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; } .kick-player, .ban-player { border-radius: 0.25rem; background-color: var(--color-red-600); padding-inline: calc(var(--spacing) * 2); padding-block: calc(var(--spacing) * 1); + font-family: Minecraft; font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + --tw-duration: 200ms; + transition-duration: 200ms; &:hover { @media (hover: hover) { background-color: var(--color-red-700); } } - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; } .teleport-player { border-radius: 0.25rem; background-color: var(--color-cyan-600); padding-inline: calc(var(--spacing) * 2); padding-block: calc(var(--spacing) * 1); + font-family: Minecraft; font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + --tw-duration: 200ms; + transition-duration: 200ms; &:hover { @media (hover: hover) { background-color: var(--color-cyan-700); } } - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; } .teleport-player:hover:not(.disabled-btn) { - transform: translateY(-1px); + --tw-translate-y: -1px; + translate: var(--tw-translate-x) var(--tw-translate-y); } .teleport-player:active:not(.disabled-btn) { - transform: translateY(0); + --tw-translate-y: calc(var(--spacing) * 0); + translate: var(--tw-translate-x) var(--tw-translate-y); } .effect-player { border-radius: 0.25rem; background-color: var(--color-teal-600); padding-inline: calc(var(--spacing) * 2); padding-block: calc(var(--spacing) * 1); + font-family: Minecraft; font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + --tw-duration: 200ms; + transition-duration: 200ms; &:hover { @media (hover: hover) { background-color: var(--color-teal-700); } } - font-family: 'Minecraft', sans-serif; - transition: all 0.2s ease; } .effect-player:hover:not(.disabled-btn) { - transform: translateY(-1px); + --tw-translate-y: -1px; + translate: var(--tw-translate-x) var(--tw-translate-y); } .effect-player:active:not(.disabled-btn) { - transform: translateY(0); + --tw-translate-y: calc(var(--spacing) * 0); + translate: var(--tw-translate-x) var(--tw-translate-y); } .modal { position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.75); + inset: calc(var(--spacing) * 0); + z-index: 1000; display: flex; align-items: center; justify-content: center; - z-index: 1000; + background-color: color-mix(in srgb, #000 75%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-black) 75%, transparent); + } } .modal-content { - background: #1f2937; - padding: 1.5rem; - border-radius: 0.5rem; - width: 100%; - max-width: 600px; position: relative; max-height: 80vh; + width: 100%; + max-width: var(--container-xl); overflow-y: auto; + border-radius: var(--radius-lg); + background-color: var(--color-gray-800); + padding: calc(var(--spacing) * 6); scrollbar-width: thin; - scrollbar-color: #4b5563 #1f2937; + scrollbar-color: #4B5563 #1F2937; } .modal-content::-webkit-scrollbar { - width: 8px; + width: calc(var(--spacing) * 2); } .modal-content::-webkit-scrollbar-track { - background: #1f2937; + background-color: var(--color-gray-800); } .modal-content::-webkit-scrollbar-thumb { - background: #4b5563; - border-radius: 4px; + border-radius: 0.25rem; + background-color: var(--color-gray-600); } .modal-content::-webkit-scrollbar-thumb:hover { - background: #6b7280; + background-color: var(--color-gray-500); } .modal-close { position: absolute; - top: 0.5rem; - right: 0.5rem; - background: none; - border: none; - color: white; - font-size: 1.25rem; + top: calc(var(--spacing) * 2); + right: calc(var(--spacing) * 2); cursor: pointer; + --tw-border-style: none; + border-style: none; + background-color: transparent; + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + color: var(--color-white); } .disabled-btn { - opacity: 0.5; - cursor: not-allowed; pointer-events: none; + cursor: not-allowed; + opacity: 50%; } .disabled-btn:hover { cursor: not-allowed; } + #searchContainer { + margin-bottom: calc(var(--spacing) * 4); + display: block; + } + #propertiesSearch { + display: block; + width: 100%; + border-radius: 0.25rem; + background-color: var(--color-gray-700); + padding-inline: calc(var(--spacing) * 4); + padding-block: calc(var(--spacing) * 2); + color: var(--color-white); + } + #propertiesList { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse))); + } + } + [role="switch"] { + position: relative; + z-index: 10; + display: inline-block; + height: calc(var(--spacing) * 6); + width: calc(var(--spacing) * 10); + cursor: pointer; + } + [role="switch"] > div:first-child { + height: 100%; + width: 100%; + border-radius: calc(infinity * 1px); + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + --tw-duration: 200ms; + transition-duration: 200ms; + --tw-ease: var(--ease-in-out); + transition-timing-function: var(--ease-in-out); + } + [role="switch"] > div:last-child { + position: absolute; + top: calc(var(--spacing) * 1); + z-index: 11; + height: calc(var(--spacing) * 4); + width: calc(var(--spacing) * 4); + border-radius: calc(infinity * 1px); + background-color: var(--color-white); + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + --tw-duration: 200ms; + transition-duration: 200ms; + --tw-ease: var(--ease-in-out); + transition-timing-function: var(--ease-in-out); + } + [role="switch"][aria-checked="true"] > div:first-child { + background-color: var(--color-green-600); + } + [role="switch"][aria-checked="false"] > div:first-child { + background-color: var(--color-gray-600); + } + [role="switch"][aria-checked="true"] > div:last-child { + left: calc(var(--spacing) * 6); + } + [role="switch"][aria-checked="false"] > div:last-child { + left: calc(var(--spacing) * 1); + } } @layer utilities; @media (max-width: 640px) { .bg-gray-800.p-6.rounded-lg.shadow-lg .grid { - grid-template-columns: 1fr; + grid-template-columns: repeat(1, minmax(0, 1fr)); } .bg-gray-800.p-6.rounded-lg.shadow-lg p { display: flex; flex-direction: column; - gap: 0.5rem; + gap: calc(var(--spacing) * 2); word-break: break-all; } .bg-gray-800.p-6.rounded-lg.shadow-lg a, .bg-gray-800.p-6.rounded-lg.shadow-lg span { - word-break: break-all; - overflow-wrap: anywhere; + overflow-wrap: break-word; white-space: normal; } .bg-gray-800.p-6.rounded-lg.shadow-lg button { + margin-top: calc(var(--spacing) * 2); width: 100%; - margin-top: 0.5rem; } .tell-player, .give-player, .teleport-player, .effect-player, .op-player, .deop-player, .kick-player, .ban-player { + margin-top: calc(var(--spacing) * 1); width: 100%; text-align: center; - margin-top: 0.25rem; } } .bg-gray-800.p-6.rounded-lg.shadow-lg .grid { overflow-x: hidden; } -.bg-gray-800.p-6.rounded-lg.shadow p { +.bg-gray-800.p-6.rounded-lg.shadow-lg p { max-width: 100%; } @property --tw-rotate-x { @@ -1028,6 +1188,82 @@ inherits: false; initial-value: 0 0 #0000; } +@property --tw-blur { + syntax: "*"; + inherits: false; +} +@property --tw-brightness { + syntax: "*"; + inherits: false; +} +@property --tw-contrast { + syntax: "*"; + inherits: false; +} +@property --tw-grayscale { + syntax: "*"; + inherits: false; +} +@property --tw-hue-rotate { + syntax: "*"; + inherits: false; +} +@property --tw-invert { + syntax: "*"; + inherits: false; +} +@property --tw-opacity { + syntax: "*"; + inherits: false; +} +@property --tw-saturate { + syntax: "*"; + inherits: false; +} +@property --tw-sepia { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-drop-shadow-size { + syntax: "*"; + inherits: false; +} +@property --tw-ease { + syntax: "*"; + inherits: false; +} +@property --tw-duration { + syntax: "*"; + inherits: false; +} +@property --tw-translate-x { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-y { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-z { + syntax: "*"; + inherits: false; + initial-value: 0; +} @keyframes spin { to { transform: rotate(360deg); @@ -1060,6 +1296,24 @@ --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; + --tw-blur: initial; + --tw-brightness: initial; + --tw-contrast: initial; + --tw-grayscale: initial; + --tw-hue-rotate: initial; + --tw-invert: initial; + --tw-opacity: initial; + --tw-saturate: initial; + --tw-sepia: initial; + --tw-drop-shadow: initial; + --tw-drop-shadow-color: initial; + --tw-drop-shadow-alpha: 100%; + --tw-drop-shadow-size: initial; + --tw-ease: initial; + --tw-duration: initial; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-translate-z: 0; } } } diff --git a/public/js/app.js b/public/js/app.js index 0738923..e3c23a0 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -501,78 +501,78 @@ document.addEventListener('DOMContentLoaded', () => { const sftpBrowserSection = elements.sftpBrowserSection; if (startBtn) { - if (status.toLowerCase() === 'running') { - startBtn.disabled = true; - startBtn.classList.add('disabled-btn'); - } else { - startBtn.disabled = false; - startBtn.classList.remove('disabled-btn'); - } + if (status.toLowerCase() === 'running') { + startBtn.disabled = true; + startBtn.classList.add('disabled-btn'); + } else { + startBtn.disabled = false; + startBtn.classList.remove('disabled-btn'); + } } if (status.toLowerCase() !== 'running') { - sections.forEach(section => { - if (section !== serverStatusSection) { - section.classList.add('hidden'); - } - }); - if (editPropertiesBtn) { - editPropertiesBtn.classList.add('hidden'); - } - if (updateModsBtn) { - updateModsBtn.classList.add('hidden'); - } - if (backupBtn) { - backupBtn.classList.add('hidden'); - } - if (sftpBtn) { - sftpBtn.classList.add('hidden'); - } - if (stopBtn) { - stopBtn.disabled = true; - stopBtn.classList.add('disabled-btn'); - } - if (restartBtn) { - restartBtn.disabled = true; - restartBtn.classList.add('disabled-btn'); - } - if (sftpBrowserSection) { - sftpBrowserSection.style.display = 'none'; - } - if (!state.hasShownStartNotification) { - showNotification('Server is stopped. Click "Start" to enable all features.', 'error', 'server-stopped'); - state.hasShownStartNotification = true; + sections.forEach(section => { + if (section !== serverStatusSection) { + section.classList.add('hidden'); } + }); + if (editPropertiesBtn) { + editPropertiesBtn.classList.add('hidden'); + } + if (updateModsBtn) { + updateModsBtn.classList.add('hidden'); + } + if (backupBtn) { + backupBtn.classList.add('hidden'); + } + if (sftpBtn) { + sftpBtn.classList.add('hidden'); + } + if (stopBtn) { + stopBtn.disabled = true; + stopBtn.classList.add('disabled-btn'); + } + if (restartBtn) { + restartBtn.disabled = true; + restartBtn.classList.add('disabled-btn'); + } + if (sftpBrowserSection) { + sftpBrowserSection.style.display = 'none'; + } + if (!state.hasShownStartNotification) { + showNotification('Server is stopped. Click "Start" to enable all features.', 'error', 'server-stopped'); + state.hasShownStartNotification = true; + } } else { - sections.forEach(section => { - section.classList.remove('hidden'); - }); - if (editPropertiesBtn) { - editPropertiesBtn.classList.remove('hidden'); - } - if (updateModsBtn) { - updateModsBtn.classList.remove('hidden'); - } - if (backupBtn) { - backupBtn.classList.remove('hidden'); - } - if (sftpBtn) { - sftpBtn.classList.remove('hidden'); - } - if (stopBtn) { - stopBtn.disabled = false; - stopBtn.classList.remove('disabled-btn'); - } - if (restartBtn) { - restartBtn.disabled = false; - restartBtn.classList.remove('disabled-btn'); - } - if (sftpBrowserSection) { - sftpBrowserSection.style.display = 'block'; - } - state.hasShownStartNotification = false; + sections.forEach(section => { + section.classList.remove('hidden'); + }); + if (editPropertiesBtn) { + editPropertiesBtn.classList.remove('hidden'); + } + if (updateModsBtn) { + updateModsBtn.classList.remove('hidden'); + } + if (backupBtn) { + backupBtn.classList.remove('hidden'); + } + if (sftpBtn) { + sftpBtn.classList.remove('hidden'); + } + if (stopBtn) { + stopBtn.disabled = false; + stopBtn.classList.remove('disabled-btn'); + } + if (restartBtn) { + restartBtn.disabled = false; + restartBtn.classList.remove('disabled-btn'); + } + if (sftpBrowserSection) { + sftpBrowserSection.style.display = 'block'; + } + state.hasShownStartNotification = false; } -} + } function updateDockerLogsUI(message) { if (message.error) { @@ -1410,6 +1410,8 @@ document.addEventListener('DOMContentLoaded', () => { } } + let displayProperties = {}; // Store original properties for filtering + function parseServerProperties(content) { const properties = {}; const lines = content.split('\n'); @@ -1424,50 +1426,174 @@ document.addEventListener('DOMContentLoaded', () => { return properties; } - function renderPropertiesFields(properties) { + function renderPropertiesFields(properties, filter = '') { const fieldsContainer = elements.propertiesFields; - fieldsContainer.innerHTML = ''; - Object.entries(properties).forEach(([key, value]) => { + // Create search box only if not already present + let searchContainer = fieldsContainer.querySelector('#searchContainer'); + if (!searchContainer) { + searchContainer = document.createElement('div'); + searchContainer.id = 'searchContainer'; + searchContainer.className = 'mb-4'; + searchContainer.style.display = 'block'; + + const searchLabel = document.createElement('label'); + searchLabel.textContent = 'Search Properties'; + searchLabel.className = 'block text-sm font-medium mb-1 text-white'; + searchLabel.setAttribute('for', 'propertiesSearch'); + + const searchInput = document.createElement('input'); + searchInput.type = 'text'; + searchInput.id = 'propertiesSearch'; + searchInput.placeholder = 'Search properties...'; + searchInput.className = 'bg-gray-700 px-4 py-2 rounded text-white w-full'; + searchInput.setAttribute('aria-label', 'Search server properties'); + + searchContainer.appendChild(searchLabel); + searchContainer.appendChild(searchInput); + fieldsContainer.appendChild(searchContainer); + + // Add input event listener + searchInput.addEventListener('input', (e) => { + renderPropertiesList(displayProperties, e.target.value); + }); + + // Store reference to search input + elements.searchInput = searchInput; + } + + // Create or update properties list container + let propertiesList = fieldsContainer.querySelector('#propertiesList'); + if (!propertiesList) { + propertiesList = document.createElement('div'); + propertiesList.id = 'propertiesList'; + propertiesList.className = 'space-y-2'; + fieldsContainer.appendChild(propertiesList); + } + + renderPropertiesList(properties, filter); + + // Add modal close handler + const closeButton = elements.editPropertiesModal.querySelector('.close-button'); + if (closeButton && !closeButton.dataset.closeHandlerAdded) { + closeButton.addEventListener('click', () => { + elements.searchInput.value = ''; + renderPropertiesList(displayProperties, ''); + elements.editPropertiesModal.classList.add('hidden'); + }); + closeButton.dataset.closeHandlerAdded = 'true'; + } + } + + function renderPropertiesList(properties, filter = '') { + const propertiesList = elements.propertiesFields.querySelector('#propertiesList'); + propertiesList.innerHTML = ''; + + // Filter properties based on search input + const filteredProperties = Object.entries(properties).filter(([key]) => + key.toLowerCase().includes(filter.toLowerCase()) + ); + + // Render filtered properties + filteredProperties.forEach(([key, value]) => { if (filteredSettings.includes(key)) { return; } + console.log(`Rendering field for ${key}: ${value}`); // Debug log + const fieldDiv = document.createElement('div'); fieldDiv.className = 'flex items-center space-x-2'; + fieldDiv.style.display = 'flex'; let inputType = 'text'; let isBoolean = value.toLowerCase() === 'true' || value.toLowerCase() === 'false'; if (isBoolean) { - inputType = 'checkbox'; + inputType = 'switch'; } else if (/^\d+$/.test(value) && !isNaN(parseInt(value))) { inputType = 'number'; } const label = document.createElement('label'); label.textContent = key; - label.className = 'w-1/3 text-sm font-medium'; + label.className = 'w-1/3 text-sm font-medium text-white'; label.setAttribute('for', `prop-${key}`); - const input = document.createElement('input'); - input.id = `prop-${key}`; - input.name = key; - input.className = 'bg-gray-700 px-4 py-2 rounded text-white w-2/3'; + if (inputType === 'switch') { + // Hidden text input for accessibility and form association + const hiddenInput = document.createElement('input'); + hiddenInput.type = 'text'; + hiddenInput.id = `prop-${key}`; + hiddenInput.name = key; + hiddenInput.value = value.toLowerCase(); + hiddenInput.style.display = 'none'; - if (inputType === 'checkbox') { - input.type = 'checkbox'; - input.checked = value.toLowerCase() === 'true'; + const switchContainer = document.createElement('div'); + switchContainer.className = 'relative inline-block'; + switchContainer.setAttribute('role', 'switch'); + switchContainer.setAttribute('aria-checked', value.toLowerCase()); + switchContainer.setAttribute('tabindex', '0'); + switchContainer.dataset.name = key; + switchContainer.style.width = '40px'; + switchContainer.style.height = '24px'; + switchContainer.style.display = 'inline-block'; + switchContainer.style.position = 'relative'; + switchContainer.style.cursor = 'pointer'; + switchContainer.style.zIndex = '10'; + + const switchTrack = document.createElement('div'); + switchTrack.className = 'block w-full h-full rounded-full'; + switchTrack.style.backgroundColor = value.toLowerCase() === 'true' ? '#10B981' : '#4B5563'; + switchTrack.style.transition = 'background-color 0.2s ease-in-out'; + + const switchHandle = document.createElement('div'); + switchHandle.className = 'absolute rounded-full bg-white'; + switchHandle.style.width = '16px'; + switchHandle.style.height = '16px'; + switchHandle.style.top = '4px'; + switchHandle.style.left = value.toLowerCase() === 'true' ? '20px' : '4px'; + switchHandle.style.transition = 'left 0.2s ease-in-out'; + switchHandle.style.position = 'absolute'; + switchHandle.style.zIndex = '11'; + + // Handle click and keyboard events + const toggleSwitch = () => { + const currentValue = hiddenInput.value === 'true'; + const newValue = !currentValue; + hiddenInput.value = newValue.toString(); + switchContainer.setAttribute('aria-checked', newValue.toString()); + switchTrack.style.backgroundColor = newValue ? '#10B981' : '#4B5563'; + switchHandle.style.left = newValue ? '20px' : '4px'; + }; + + switchContainer.addEventListener('click', toggleSwitch); + switchContainer.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggleSwitch(); + } + }); + + switchContainer.appendChild(switchTrack); + switchContainer.appendChild(switchHandle); + fieldDiv.appendChild(label); + fieldDiv.appendChild(hiddenInput); + fieldDiv.appendChild(switchContainer); } else { + const input = document.createElement('input'); + input.id = `prop-${key}`; + input.name = key; + input.className = 'bg-gray-700 px-4 py-2 rounded text-white w-2/3'; input.type = inputType; input.value = value; if (inputType === 'number') { input.min = '0'; } + fieldDiv.appendChild(label); + fieldDiv.appendChild(input); } - fieldDiv.appendChild(label); - fieldDiv.appendChild(input); - fieldsContainer.appendChild(fieldDiv); + propertiesList.appendChild(fieldDiv); }); } @@ -1492,7 +1618,7 @@ document.addEventListener('DOMContentLoaded', () => { return; } allProperties = parseServerProperties(response.content || ''); - const displayProperties = Object.fromEntries( + displayProperties = Object.fromEntries( Object.entries(allProperties).filter(([key]) => !filteredSettings.includes(key)) ); renderPropertiesFields(displayProperties); @@ -1509,15 +1635,18 @@ document.addEventListener('DOMContentLoaded', () => { const key = `action-save-properties`; const notification = showNotification('Saving server properties...', 'loading', key); const properties = {}; - const inputs = elements.propertiesFields.querySelectorAll('input'); + const inputs = elements.propertiesFields.querySelectorAll('input:not(#propertiesSearch)'); + + // Collect modified properties inputs.forEach(input => { const key = input.name; - let value = input.type === 'checkbox' ? input.checked.toString() : input.value.trim(); + let value = input.value.trim(); if (value !== '') { properties[key] = value; } }); + // Merge with allProperties to include all properties, even if not displayed const fullProperties = { ...allProperties, ...properties }; const content = propertiesToString(fullProperties); const response = await wsRequest('/server-properties', 'POST', { content }); @@ -1525,6 +1654,11 @@ document.addEventListener('DOMContentLoaded', () => { updateNotification(notification, `Failed to save server properties: ${response.error}`, 'error', key); return; } + // Clear search input and reset properties list + if (elements.searchInput) { + elements.searchInput.value = ''; + renderPropertiesList(displayProperties, ''); + } elements.editPropertiesModal.classList.add('hidden'); updateNotification(notification, 'Server properties saved successfully', 'success', key); } catch (error) {