From 74d2ada067bd2fd9436710ab7ac260e0eddf1314 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 29 Jul 2026 01:03:24 +0200 Subject: Add per-instance mod management with client-side detection - New mod validation engine (src/mods.rs) with constant-pool class-file scanning: detects server-side content (block, tileentity, command, worldgen) vs client-only API (gui, renderer, forge/client) - 7 client-only mods correctly detected (AppleSkin, CraftingTweaks, Durability101, EnchantmentDescriptions, InventoryHUD, MouseTweaks, TrashSlot); 51 server-capable; 1 unknown - Routes + templating for mod upload (jar/zip), listing, delete, rescan - Multipart upload with 500MB limit (bypasses axum's default 2MB) - Per-instance mod summary card on edit page - Whitelist toggle checkbox on edit page (persisted, drives ENABLE_WHITELIST) - Dynamic Java image tag (java8/java17/java21) based on MC version - Class scan is informational-only; rejection based on metadata side=CLIENT - has_modpack flag cleanup on mod delete - Remove hx-boost from (was corrupting multipart uploads) --- static/style.css | 492 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 261 insertions(+), 231 deletions(-) (limited to 'static/style.css') diff --git a/static/style.css b/static/style.css index 61046b2..583b9fb 100644 --- a/static/style.css +++ b/static/style.css @@ -1,424 +1,454 @@ *, *::before, *::after { - box-sizing: border-box; - margin: 0; - padding: 0; + box-sizing: border-box; + margin: 0; + padding: 0; } :root { - --bg: #1a1b1e; - --bg-card: #25262b; - --bg-input: #2c2e33; - --fg: #c1c2c5; - --fg-muted: #909296; - --accent: #4c9aff; - --ok: #51cf66; - --warn: #fcc419; - --err: #ff6b6b; - --border: #373a40; - --radius: 8px; + --bg: #1a1b1e; + --bg-card: #25262b; + --bg-input: #2c2e33; + --fg: #c1c2c5; + --fg-muted: #909296; + --accent: #4c9aff; + --ok: #51cf66; + --warn: #fcc419; + --err: #ff6b6b; + --border: #373a40; + --radius: 8px; } body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - background: var(--bg); - color: var(--fg); - line-height: 1.5; - min-height: 100vh; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + background: var(--bg); + color: var(--fg); + line-height: 1.5; + min-height: 100vh; } /* Navigation */ nav { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.75rem 1.5rem; - background: var(--bg-card); - border-bottom: 1px solid var(--border); - flex-wrap: wrap; - gap: 0.5rem; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem 1.5rem; + background: var(--bg-card); + border-bottom: 1px solid var(--border); + flex-wrap: wrap; + gap: 0.5rem; } nav .brand { - font-weight: 700; - font-size: 1.1rem; - color: var(--accent); + font-weight: 700; + font-size: 1.1rem; + color: var(--accent); } nav .nav-links { - display: flex; - align-items: center; - gap: 1rem; - flex-wrap: wrap; + display: flex; + align-items: center; + gap: 1rem; + flex-wrap: wrap; } nav a { - color: var(--fg); - text-decoration: none; - font-size: 0.9rem; + color: var(--fg); + text-decoration: none; + font-size: 0.9rem; } nav a:hover { - color: var(--accent); + color: var(--accent); } nav .nav-user { - color: var(--fg-muted); - font-size: 0.85rem; + color: var(--fg-muted); + font-size: 0.85rem; } /* Buttons */ -.btn, button { - display: inline-block; - padding: 0.5rem 1rem; - border: 1px solid var(--border); - border-radius: var(--radius); - background: var(--bg-input); - color: var(--fg); - cursor: pointer; - font-size: 0.9rem; - text-decoration: none; - transition: background 0.15s; +.btn, +button { + display: inline-block; + padding: 0.5rem 1rem; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--bg-input); + color: var(--fg); + cursor: pointer; + font-size: 0.9rem; + text-decoration: none; + transition: background 0.15s; } -.btn:hover, button:hover { - background: var(--border); +.btn:hover, +button:hover { + background: var(--border); } -.btn-ok, button.btn-ok { - background: var(--ok); - color: #000; - border-color: var(--ok); +.btn-ok, +button.btn-ok { + background: var(--ok); + color: #000; + border-color: var(--ok); } .btn-ok:hover { - opacity: 0.85; + opacity: 0.85; } -.btn-danger, button.btn-danger { - background: var(--err); - color: #000; - border-color: var(--err); +.btn-danger, +button.btn-danger { + background: var(--err); + color: #000; + border-color: var(--err); } .btn-danger:hover { - opacity: 0.85; + opacity: 0.85; } .btn-sm { - padding: 0.25rem 0.6rem; - font-size: 0.8rem; + padding: 0.25rem 0.6rem; + font-size: 0.8rem; } .btn-link { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - padding: 0; + background: none; + border: none; + color: var(--fg-muted); + cursor: pointer; + padding: 0; } .btn-link:hover { - color: var(--err); + color: var(--err); } /* Cards */ .card { - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 1.25rem; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1.25rem; } .card-ok { - border-color: var(--ok); + border-color: var(--ok); } .card-err { - border-color: var(--err); + border-color: var(--err); } .card-muted { - opacity: 0.7; + opacity: 0.7; } .card-active { - border-color: var(--accent); - position: relative; + border-color: var(--accent); + position: relative; } .active-badge { - position: absolute; - top: -8px; - right: -8px; - background: var(--accent); - color: #000; - font-size: 0.7rem; - font-weight: 700; - padding: 0.15rem 0.5rem; - border-radius: 100px; + position: absolute; + top: -8px; + right: -8px; + background: var(--accent); + color: #000; + font-size: 0.7rem; + font-weight: 700; + padding: 0.15rem 0.5rem; + border-radius: 100px; } /* Forms */ form.form-col { - display: flex; - flex-direction: column; - gap: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; } form.form-col:not(.form-wide) { - max-width: 600px; + max-width: 600px; } form.form-row { - display: flex; - align-items: center; - gap: 0.5rem; + display: flex; + align-items: center; + gap: 0.5rem; } label { - display: flex; - flex-direction: column; - gap: 0.25rem; - font-size: 0.9rem; - color: var(--fg-muted); + display: flex; + flex-direction: column; + gap: 0.25rem; + font-size: 0.9rem; + color: var(--fg-muted); } label:has(input[type="radio"]), label:has(input[type="checkbox"]) { - flex-direction: row; - align-items: center; - gap: 0.5rem; - color: var(--fg); + flex-direction: row; + align-items: center; + gap: 0.5rem; + color: var(--fg); } -input, select, textarea { - padding: 0.5rem 0.75rem; - border: 1px solid var(--border); - border-radius: var(--radius); - background: var(--bg-input); - color: var(--fg); - font-size: 0.9rem; +input, +select, +textarea { + padding: 0.5rem 0.75rem; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--bg-input); + color: var(--fg); + font-size: 0.9rem; } -input:focus, select:focus { - outline: none; - border-color: var(--accent); +input:focus, +select:focus { + outline: none; + border-color: var(--accent); } fieldset { - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 1rem; + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1rem; } legend { - font-weight: 600; - padding: 0 0.5rem; - color: var(--fg-muted); - font-size: 0.85rem; + font-weight: 600; + padding: 0 0.5rem; + color: var(--fg-muted); + font-size: 0.85rem; } small { - color: var(--fg-muted); - font-size: 0.8rem; + color: var(--fg-muted); + font-size: 0.8rem; } /* Layout */ main { - max-width: 1000px; - margin: 0 auto; - padding: 1.5rem; + max-width: 1000px; + margin: 0 auto; + padding: 1.5rem; } -h1, h2, h3 { - color: var(--fg); - margin-bottom: 0.75rem; +h1, +h2, +h3 { + color: var(--fg); + margin-bottom: 0.75rem; } .page-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 1.5rem; - flex-wrap: wrap; - gap: 1rem; + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1.5rem; + flex-wrap: wrap; + gap: 1rem; } .instance-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 1rem; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 1rem; } .tab-row { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 1rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1rem; } /* Status badges */ .status-badge { - display: inline-block; - padding: 0.25rem 0.75rem; - border-radius: 100px; - font-size: 0.85rem; - font-weight: 600; + display: inline-block; + padding: 0.25rem 0.75rem; + border-radius: 100px; + font-size: 0.85rem; + font-weight: 600; } .status-running { - background: var(--ok); - color: #000; + background: var(--ok); + color: #000; } -.status-starting, .status-booting, .status-pulling { - background: var(--warn); - color: #000; +.status-starting, +.status-booting, +.status-pulling { + background: var(--warn); + color: #000; } .status-stopped { - background: var(--fg-muted); - color: #000; + background: var(--fg-muted); + color: #000; } -.status-error, .status-not_found { - background: var(--err); - color: #000; +.status-warning { + background: var(--warn); + color: #000; +} + +.status-error, +.status-not_found { + background: var(--err); + color: #000; } .status-row { - display: flex; - align-items: center; - gap: 1rem; - margin: 0.75rem 0; + display: flex; + align-items: center; + gap: 1rem; + margin: 0.75rem 0; } /* Tags */ .tag { - display: inline-block; - padding: 0.1rem 0.5rem; - border-radius: 100px; - font-size: 0.75rem; - background: var(--border); - color: var(--fg-muted); + display: inline-block; + padding: 0.1rem 0.5rem; + border-radius: 100px; + font-size: 0.75rem; + background: var(--border); + color: var(--fg-muted); } .tag-admin { - background: var(--warn); - color: #000; + background: var(--warn); + color: #000; +} + +.tag-ok { + background: var(--ok); + color: #000; +} + +.tag-err { + background: var(--err); + color: #000; } /* Instance meta */ -.instance-meta, .meta { - display: flex; - gap: 0.75rem; - flex-wrap: wrap; - font-size: 0.85rem; - color: var(--fg-muted); - margin-bottom: 0.5rem; +.instance-meta, +.meta { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; + font-size: 0.85rem; + color: var(--fg-muted); + margin-bottom: 0.5rem; } .actions { - display: flex; - gap: 0.5rem; - flex-wrap: wrap; - margin-top: 0.75rem; + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + margin-top: 0.75rem; } .players { - font-size: 0.9rem; - color: var(--accent); + font-size: 0.9rem; + color: var(--accent); } /* Player list */ .player-list { - list-style: none; - margin-top: 0.5rem; + list-style: none; + margin-top: 0.5rem; } .player-list li { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.4rem 0; - border-bottom: 1px solid var(--border); - font-size: 0.9rem; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.4rem 0; + border-bottom: 1px solid var(--border); + font-size: 0.9rem; } /* Table */ .table { - width: 100%; - border-collapse: collapse; - font-size: 0.9rem; + width: 100%; + border-collapse: collapse; + font-size: 0.9rem; } -.table th, .table td { - text-align: left; - padding: 0.5rem; - border-bottom: 1px solid var(--border); +.table th, +.table td { + text-align: left; + padding: 0.5rem; + border-bottom: 1px solid var(--border); } .table th { - color: var(--fg-muted); - font-weight: 600; + color: var(--fg-muted); + font-weight: 600; } /* Flash messages */ .flash { - padding: 0.75rem 1rem; - border-radius: var(--radius); - margin-bottom: 1rem; - font-size: 0.9rem; + padding: 0.75rem 1rem; + border-radius: var(--radius); + margin-bottom: 1rem; + font-size: 0.9rem; } .flash-err { - background: color-mix(in srgb, var(--err) 15%, transparent); - border: 1px solid var(--err); - color: var(--err); + background: color-mix(in srgb, var(--err) 15%, transparent); + border: 1px solid var(--err); + color: var(--err); } .flash-ok { - background: color-mix(in srgb, var(--ok) 15%, transparent); - border: 1px solid var(--ok); - color: var(--ok); + background: color-mix(in srgb, var(--ok) 15%, transparent); + border: 1px solid var(--ok); + color: var(--ok); } .flash-warn { - background: color-mix(in srgb, var(--warn) 15%, transparent); - border: 1px solid var(--warn); - color: var(--warn); + background: color-mix(in srgb, var(--warn) 15%, transparent); + border: 1px solid var(--warn); + color: var(--warn); } /* Login page */ .login-page { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 100vh; - max-width: 380px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + max-width: 380px; } .login-form { - width: 100%; - display: flex; - flex-direction: column; - gap: 1rem; + width: 100%; + display: flex; + flex-direction: column; + gap: 1rem; } .login-form button { - margin-top: 0.5rem; + margin-top: 0.5rem; } /* Responsive */ @media (max-width: 600px) { - nav { - flex-direction: column; - align-items: flex-start; - } - - .instance-grid { - grid-template-columns: 1fr; - } + nav { + flex-direction: column; + align-items: flex-start; + } + + .instance-grid { + grid-template-columns: 1fr; + } } -- cgit v1.2.3