diff --git a/addons/base/models/core_company.go b/addons/base/models/core_company.go index 8b382b0e..e81eaa40 100644 --- a/addons/base/models/core_company.go +++ b/addons/base/models/core_company.go @@ -24,4 +24,6 @@ type CoreCompany struct { MailChatterEnabled sdk.Boolean `sumeru:"string=Chatter,default=true"` MailActivityPanelEnabled sdk.Boolean `sumeru:"string=Activity panel,default=true"` Color sdk.Integer `sumeru:"string=Color Index"` + LoginLogo sdk.Text `sumeru:"string=Login logo"` + LoginTagline sdk.String `sumeru:"string=Login tagline"` } diff --git a/addons/base/views/core_company_form_views.xml b/addons/base/views/core_company_form_views.xml index b9842f11..756f36a5 100644 --- a/addons/base/views/core_company_form_views.xml +++ b/addons/base/views/core_company_form_views.xml @@ -28,6 +28,15 @@ + + + + + + + + + diff --git a/core/engine/assets/css/sumeru-login.css b/core/engine/assets/css/sumeru-login.css index f03fffc9..eb425c9c 100644 --- a/core/engine/assets/css/sumeru-login.css +++ b/core/engine/assets/css/sumeru-login.css @@ -15,8 +15,16 @@ } .sum-login-brand { + --sum-login-accent: var(--sum-header); + position: relative; flex: 1 1 42%; - background: linear-gradient(145deg, var(--sum-header) 0%, var(--sum-header-dark) 55%, #1a2332 100%); + overflow: hidden; + background: linear-gradient( + 155deg, + var(--sum-header-dark) 0%, + var(--sum-header) 42%, + color-mix(in srgb, var(--sum-login-accent) 35%, #1a2332) 100% + ); color: var(--sum-surface); display: flex; align-items: center; @@ -24,65 +32,126 @@ padding: 3rem 2.5rem; } +.sum-login-brand--accent-0 { --sum-login-accent: #875a7b; } +.sum-login-brand--accent-1 { --sum-login-accent: #9c9c9c; } +.sum-login-brand--accent-2 { --sum-login-accent: #67917a; } +.sum-login-brand--accent-3 { --sum-login-accent: #a06666; } +.sum-login-brand--accent-4 { --sum-login-accent: #8899aa; } +.sum-login-brand--accent-5 { --sum-login-accent: #d4a017; } +.sum-login-brand--accent-6 { --sum-login-accent: #6a5acd; } +.sum-login-brand--accent-7 { --sum-login-accent: #4682b4; } +.sum-login-brand--accent-8 { --sum-login-accent: #c0392b; } +.sum-login-brand--accent-9 { --sum-login-accent: #e67e22; } +.sum-login-brand--accent-10 { --sum-login-accent: #27ae60; } +.sum-login-brand--accent-11 { --sum-login-accent: #3498db; } + +.sum-login-brand-bg { + position: absolute; + inset: 0; + pointer-events: none; + background-image: + linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px); + background-size: 32px 32px; + mask-image: radial-gradient(ellipse 85% 70% at 30% 40%, #000 20%, transparent 75%); +} + +.sum-login-brand-bg::after { + content: ""; + position: absolute; + width: 420px; + height: 420px; + right: -120px; + top: -80px; + border-radius: 50%; + background: radial-gradient(circle, color-mix(in srgb, var(--sum-login-accent) 45%, transparent) 0%, transparent 70%); + opacity: 0.55; +} + .sum-login-brand-inner { - max-width: 420px; + position: relative; + z-index: 1; + max-width: 440px; } -.sum-login-brand-logo { +.sum-login-brand-lockup { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.65rem; margin: 0 0 1.75rem; - width: 72px; - height: 72px; + border-radius: 20px; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.14); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.22); +} + +.sum-login-brand-logo { + display: block; + width: 96px; + height: 96px; + object-fit: contain; + border-radius: 14px; } .sum-login-brand-kicker { margin: 0 0 0.5rem; font-size: var(--sum-font-size-sm); font-weight: var(--sum-font-weight-semibold); - letter-spacing: 0.08em; + letter-spacing: 0.12em; text-transform: uppercase; - opacity: 0.85; + opacity: 0.88; + color: color-mix(in srgb, var(--sum-login-accent) 55%, #fff); } .sum-login-brand-title { margin: 0 0 1rem; - font-size: 1.75rem; + font-size: clamp(1.5rem, 2.5vw, 2rem); font-weight: var(--sum-font-weight-bold); - line-height: 1.2; - letter-spacing: -0.02em; + line-height: 1.15; + letter-spacing: -0.025em; } .sum-login-brand-copy { margin: 0 0 1.5rem; font-size: var(--sum-font-size-md); line-height: 1.55; - opacity: 0.92; + opacity: 0.9; + max-width: 36ch; } -.sum-login-trust-list { +.sum-login-trust-chips { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; margin: 0; padding: 0; list-style: none; +} + +.sum-login-trust-chip { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.35rem 0.65rem; font-size: var(--sum-font-size-sm); - line-height: 1.6; - opacity: 0.9; + line-height: 1.35; + border-radius: 999px; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.12); } -.sum-login-trust-list li { - position: relative; - padding-left: 1.25rem; - margin-bottom: 0.35rem; +.sum-login-trust-chip svg { + flex-shrink: 0; + opacity: 0.9; } -.sum-login-trust-list li::before { - content: ""; - position: absolute; - left: 0; - top: 0.55em; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--sum-surface); - opacity: 0.75; +.sum-login-powered { + margin: 2rem 0 0; + font-size: var(--sum-font-size-sm); + letter-spacing: 0.04em; + text-transform: uppercase; + opacity: 0.65; } .sum-login-panel { @@ -229,7 +298,7 @@ font-size: 1.35rem; } - .sum-login-trust-list { + .sum-login-trust-chips { display: none; } } diff --git a/core/engine/assets/img/sumeru-logo.svg b/core/engine/assets/img/sumeru-logo.svg new file mode 100644 index 00000000..bcf2cbe2 --- /dev/null +++ b/core/engine/assets/img/sumeru-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/core/engine/render/page.go b/core/engine/render/page.go index 04713600..3becbe01 100644 --- a/core/engine/render/page.go +++ b/core/engine/render/page.go @@ -13,16 +13,14 @@ func RenderPage(ctx context.Context, templatesDir string, data PageData, bootstr if len(data.SWCBootstrapJSON) == 0 { data.SWCBootstrapJSON = BuildSWCBootstrapJSON(ctx, data, bootstrapWorkspace) } - tmpl, err := template.ParseFiles( - filepath.Join(templatesDir, "base.html"), - filepath.Join(templatesDir, "shell_partials.html"), - ) + paths := ShellLayoutTemplateFiles(templatesDir) + tmpl, err := template.ParseFiles(paths...) if err != nil { return "", err } var buf bytes.Buffer // Must execute the layout by name: the partials file only defines {{template "sumMenuIcon"}}. - if err := tmpl.ExecuteTemplate(&buf, "base.html", data); err != nil { + if err := tmpl.ExecuteTemplate(&buf, filepath.Base(TemplateShellBase), data); err != nil { return "", err } return buf.String(), nil diff --git a/core/engine/render/templates_paths.go b/core/engine/render/templates_paths.go new file mode 100644 index 00000000..f23ad96b --- /dev/null +++ b/core/engine/render/templates_paths.go @@ -0,0 +1,61 @@ +package render + +import "path/filepath" + +// Relative paths under TemplatesPath (core/engine/templates). +const ( + TemplateShellBase = "shell/base.html" + + TemplatePartialMenuIcons = "partials/menu_icons.html" + TemplatePartialHomeHub = "partials/home_hub.html" + TemplatePartialAppsModule = "partials/apps_module.html" + TemplatePartialLoginBrand = "partials/login_brand_aside.html" + TemplatePartialAuthHead = "partials/auth_head.html" + + TemplateAuthLogin = "auth/login.html" + TemplateAuthTOTP = "auth/totp_login.html" + TemplateAuthSetup = "auth/setup.html" + + TemplatePortalHome = "portal/portal_home.html" + TemplatePortalShare = "portal/portal_share.html" + + TemplatePagesHome = "pages/home_dashboard.html" + TemplatePagesApps = "pages/apps.html" + TemplatePagesAppLogs = "pages/app_logs.html" + TemplatePagesSettingsHub = "pages/settings_hub.html" + TemplatePagesSettingsAccount = "pages/settings_account.html" + TemplatePagesSettingsFieldACL = "pages/settings_field_acl.html" + TemplatePagesSettingsModelACL = "pages/settings_model_acl.html" +) + +// TemplatePath joins templatesDir with a relative template path. +func TemplatePath(templatesDir, rel string) string { + return filepath.Join(templatesDir, rel) +} + +// ShellLayoutTemplateFiles returns files parsed for base.html layout rendering. +func ShellLayoutTemplateFiles(templatesDir string) []string { + return []string{ + TemplatePath(templatesDir, TemplateShellBase), + TemplatePath(templatesDir, TemplatePartialMenuIcons), + } +} + +// ShellInnerTemplateFiles returns files parsed for a shell inner page (pages/*.html). +func ShellInnerTemplateFiles(templatesDir, pageRel string) []string { + return []string{ + TemplatePath(templatesDir, pageRel), + TemplatePath(templatesDir, TemplatePartialMenuIcons), + TemplatePath(templatesDir, TemplatePartialHomeHub), + TemplatePath(templatesDir, TemplatePartialAppsModule), + } +} + +// AuthTemplateFiles returns files parsed for standalone auth pages (login, TOTP, setup). +func AuthTemplateFiles(templatesDir, pageRel string) []string { + return []string{ + TemplatePath(templatesDir, pageRel), + TemplatePath(templatesDir, TemplatePartialLoginBrand), + TemplatePath(templatesDir, TemplatePartialAuthHead), + } +} diff --git a/core/engine/templates/README.md b/core/engine/templates/README.md new file mode 100644 index 00000000..c20ed134 --- /dev/null +++ b/core/engine/templates/README.md @@ -0,0 +1,17 @@ +# Engine HTML templates + +Root path: `core/engine/templates` (`templates_path` in config). Relative paths are defined in [`render/templates_paths.go`](../render/templates_paths.go). + +| Folder | Role | Loaded by | +|--------|------|-----------| +| `shell/` | Authenticated app chrome (`base.html`) | `render.RenderPage` | +| `partials/` | Shared `{{define}}` fragments (menu icons, home hub, apps forms, login brand, auth assets) | Parsed with shell layout, shell inner pages, or auth pages | +| `pages/` | Main column HTML injected into `base.html` | `web.renderShellPage` / `executeInnerTemplate` | +| `auth/` | Standalone login, TOTP, setup wizards | `web.getAuthTemplate`, setup handler | +| `portal/` | Portal home and share pages | `web.loadPortalTemplates` | + +## Parse sets + +- **Shell layout:** `shell/base.html` + `partials/menu_icons.html` +- **Shell inner page:** `pages/.html` + menu, home hub, and apps partials +- **Auth page:** `auth/.html` + `partials/login_brand_aside.html` + `partials/auth_head.html` diff --git a/core/engine/templates/login.html b/core/engine/templates/auth/login.html similarity index 67% rename from core/engine/templates/login.html rename to core/engine/templates/auth/login.html index fb49d7be..e6a36832 100644 --- a/core/engine/templates/login.html +++ b/core/engine/templates/auth/login.html @@ -4,32 +4,11 @@ Sign in — {{.AppName}} - {{range .Stylesheets}} - - {{end}} - - - + {{template "auth_head_assets" .}}