diff --git a/docs/Logo/Minimalist logo.png b/docs/Logo/Minimalist logo.png new file mode 100644 index 0000000..aafb6a6 Binary files /dev/null and b/docs/Logo/Minimalist logo.png differ diff --git a/docs/Logo/unused/ferrite-fm-Logo.png b/docs/Logo/unused/ferrite-fm-Logo.png new file mode 100644 index 0000000..6729e9a Binary files /dev/null and b/docs/Logo/unused/ferrite-fm-Logo.png differ diff --git a/docs/Logo/unused/ferrite-fm-Logo.psd b/docs/Logo/unused/ferrite-fm-Logo.psd new file mode 100644 index 0000000..2cd46aa Binary files /dev/null and b/docs/Logo/unused/ferrite-fm-Logo.psd differ diff --git a/docs/Logo/unused/ferrite-fm-Logo_ultrahighres.png b/docs/Logo/unused/ferrite-fm-Logo_ultrahighres.png new file mode 100644 index 0000000..47b0b13 Binary files /dev/null and b/docs/Logo/unused/ferrite-fm-Logo_ultrahighres.png differ diff --git a/internal/control/control.go b/internal/control/control.go index d2271d4..2c765e1 100644 --- a/internal/control/control.go +++ b/internal/control/control.go @@ -22,6 +22,9 @@ import ( //go:embed ui.html var uiHTML []byte +//go:embed logo.png +var logoPNG []byte + // TXController is an optional interface the Server uses to start/stop TX // and apply live config changes. type TXController interface { @@ -279,6 +282,7 @@ func (s *Server) SetHardReload(fn func()) { func (s *Server) Handler() http.Handler { mux := http.NewServeMux() mux.HandleFunc("/", s.handleUI) + mux.HandleFunc("/logo", s.handleLogo) mux.HandleFunc("/healthz", s.handleHealth) mux.HandleFunc("/status", s.handleStatus) mux.HandleFunc("/dry-run", s.handleDryRun) @@ -307,6 +311,12 @@ func (s *Server) handleUI(w http.ResponseWriter, r *http.Request) { w.Write(uiHTML) } +func (s *Server) handleLogo(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "image/png") + w.Header().Set("Cache-Control", "public, max-age=3600") + w.Write(logoPNG) +} + func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) { s.mu.RLock() cfg := s.cfg diff --git a/internal/control/logo.png b/internal/control/logo.png new file mode 100644 index 0000000..aafb6a6 Binary files /dev/null and b/internal/control/logo.png differ diff --git a/internal/control/ui.html b/internal/control/ui.html index a7fa3c3..a0c3402 100644 --- a/internal/control/ui.html +++ b/internal/control/ui.html @@ -22,16 +22,21 @@ body{background:linear-gradient(180deg,#fbfcfe 0%,var(--bg) 100%);color:var(--te button,input,select{font:inherit}button{user-select:none} .app{max-width:1560px;margin:0 auto;padding:24px} /* Header */ -.header{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:4px 0 20px;border-bottom:1px solid var(--border);margin-bottom:20px} -.header-main{display:flex;flex-direction:column;gap:8px} -.header h1{font-size:28px;font-weight:800;letter-spacing:-.03em} +.header{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:2px 0 18px;border-bottom:1px solid var(--border);margin-bottom:20px} +.header-main{display:flex;align-items:center;min-height:64px} +.brand-lockup{display:flex;align-items:center;gap:0} +.brand-logo{height:62px;width:auto;display:block;object-fit:contain} +.brand-title{font-size:28px;font-weight:800;letter-spacing:-.03em} .header-note{font-size:13px;color:var(--text-muted)} .header-sub{display:flex;flex-wrap:wrap;gap:8px} .badge{display:inline-flex;align-items:center;gap:8px;min-height:28px;padding:0 10px;border:1px solid var(--border);border-radius:999px;background:var(--surface2);color:var(--text-dim);font-size:11px;text-transform:uppercase;letter-spacing:.08em} .badge strong{white-space:nowrap} .badge .tag{margin-left:2px} .badge strong{color:var(--text);font-weight:700} -.header-status{display:flex;align-items:center;gap:10px;padding-top:6px} +.header-status{display:flex;align-items:center;gap:8px;padding-top:0;padding-right:2px} +.header-status .danger-btn{min-height:36px;padding:0 14px;border-radius:10px;background:rgba(176,48,48,.06);border-color:rgba(176,48,48,.22);font-size:11px;letter-spacing:.08em} +.header-status .danger-btn:hover:not(:disabled){background:rgba(176,48,48,.1)} +.header-status .led{margin-left:4px} /* LED */ .led{width:10px;height:10px;border-radius:50%;background:#333;transition:all .25s;flex-shrink:0} .led.on-green{background:var(--green);box-shadow:0 0 0 3px rgba(13,148,74,.16)} @@ -285,19 +290,15 @@ input.input-error{border-color:var(--red);box-shadow:0 0 0 3px rgba(176,48,48,.1 .toast.warn{background:var(--amber);color:#141414}.toast.info{background:var(--text-dim);color:#fff} /* Responsive */ @media(max-width:980px){.app{max-width:100%;padding:14px}.tab-columns.two{grid-template-columns:1fr}.tx-bar{grid-template-columns:1fr}.tx-state-wrap{align-items:flex-start}.status-hint{text-align:left}.quick-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.signal-grid{grid-template-columns:1fr}.flow-master{grid-template-columns:1fr}.flow-topbar,.flow-bottom{grid-template-columns:repeat(2,minmax(0,1fr))}.flow-chain{grid-template-columns:repeat(4,minmax(140px,1fr))}} -@media(max-width:640px){.app{padding:12px}.header{flex-direction:column;gap:10px}.header h1{font-size:22px}.badge{width:100%;justify-content:space-between}.badge strong{max-width:52%;overflow:hidden;text-overflow:ellipsis}.quick-grid{grid-template-columns:1fr 1fr;gap:8px}.ctrl-row{flex-direction:column;align-items:stretch}.ctrl-label-wrap{min-width:auto}.ctrl-input{flex-wrap:wrap}.ingest-grid{grid-template-columns:1fr}input[type="number"]{width:100%;text-align:left}.actions-row,.tx-actions{flex-direction:column}.tx-btn,.ghost-btn,.apply-btn,.preset-btn,.danger-btn{width:100%}.freq-display{font-size:31px}.flow-master,.flow-topbar,.flow-bottom,.flow-chain{grid-template-columns:1fr}} +@media(max-width:640px){.app{padding:12px}.header{flex-direction:column;align-items:flex-start;gap:10px}.brand-logo{height:54px}.badge{width:100%;justify-content:space-between}.badge strong{max-width:52%;overflow:hidden;text-overflow:ellipsis}.quick-grid{grid-template-columns:1fr 1fr;gap:8px}.ctrl-row{flex-direction:column;align-items:stretch}.ctrl-label-wrap{min-width:auto}.ctrl-input{flex-wrap:wrap}.ingest-grid{grid-template-columns:1fr}input[type="number"]{width:100%;text-align:left}.actions-row,.tx-actions{flex-direction:column}.tx-btn,.ghost-btn,.apply-btn,.preset-btn,.danger-btn{width:100%}.freq-display{font-size:31px}.flow-master,.flow-topbar,.flow-bottom,.flow-chain{grid-template-columns:1fr}}