From e85b326cc0fe8df8cd2b015d4ccad134441071f8 Mon Sep 17 00:00:00 2001 From: "Donald F. Coffin" Date: Sat, 14 Nov 2026 12:41:51 -0500 Subject: [PATCH 1/2] feat(ui): shared Green Button Alliance theme; Third Party shared layout (prototype) Prototype that makes the Data Custodian and Third Party look like part of greenbuttonalliance.org. - openespi-common ships META-INF/resources/css/gba-theme.css (served at /css/gba-theme.css in each app). Static resources cannot override application settings the way the jar's application.properties did. GBA palette (#2C4C3B, #3F6C54, #F99742, #005B96, #264653), Roboto, square outlined buttons, solid green card headers, dark-green header and footer with an orange rule. Bootstrap 5.3 variables plus the DC's --gb-* tokens are remapped, so existing pages adopt it without markup changes. - Third Party: new fragments/layout.html (head, header, footer). All five pages use it, all links are context-relative (th:href), and the footer year is computed. Terms of Service, Usage Policy and /error are now public, because the navbar links to them. - Data Custodian: theme and Roboto in the shared head; GBA header on the main, customer and custodian navbars (slate for the admin portal); GBA footer; home cards use the GBA card style; the custodian hero is now readable; duplicate Bootstrap JS removed from 4 pages; /css/** also serves the theme from the common jar (@EnableWebMvc disables Boot's default static mappings). - Bootstrap CSS/JS and Bootstrap Icons are pinned with SRI hashes, computed from the CDN files. Co-Authored-By: Claude Opus 5.5 --- .../META-INF/resources/css/gba-theme.css | 352 ++++++++++++++++++ .../config/WebConfiguration.java | 4 +- .../resources/templates/authorize-screen.html | 3 +- .../resources/templates/custodian/home.html | 2 +- .../resources/templates/customer/home.html | 3 +- .../templates/customer/usagepoints/index.html | 3 +- .../resources/templates/fragments/layout.html | 76 ++-- .../src/main/resources/templates/home.html | 42 +-- .../src/main/resources/templates/login.html | 3 +- .../config/SecurityConfiguration.java | 4 +- .../resources/templates/TermsOfService.html | 85 ++--- .../main/resources/templates/UsagePolicy.html | 85 ++--- .../src/main/resources/templates/error.html | 90 ++--- .../resources/templates/fragments/layout.html | 100 +++++ .../src/main/resources/templates/home.html | 265 +++++-------- .../src/main/resources/templates/login.html | 114 +----- 16 files changed, 721 insertions(+), 510 deletions(-) create mode 100644 openespi-common/src/main/resources/META-INF/resources/css/gba-theme.css create mode 100644 openespi-thirdparty/src/main/resources/templates/fragments/layout.html diff --git a/openespi-common/src/main/resources/META-INF/resources/css/gba-theme.css b/openespi-common/src/main/resources/META-INF/resources/css/gba-theme.css new file mode 100644 index 000000000..e13028f66 --- /dev/null +++ b/openespi-common/src/main/resources/META-INF/resources/css/gba-theme.css @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2026 Green Button Alliance, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Shared Green Button Alliance theme for the OpenESPI web applications (Data Custodian, Third Party). + * + * Served by Spring Boot from META-INF/resources in the OpenESPI-Common jar at /css/gba-theme.css, + * relative to each application's context path. Load it AFTER Bootstrap 5.3 (and after any + * application stylesheet) and load Roboto 400/500/700 from Google Fonts. + * + * Palette and shapes follow greenbuttonalliance.org: dark green chrome with an orange rule, + * Roboto, square corners, outlined buttons, solid green card headers. + * + * Contrast notes (WCAG AA): white on --gba-green-900 and --gba-slate, --gba-green-700 on white and + * --gba-blue on --gba-page all pass for body text. --gba-orange does NOT pass as text on white; use + * it only for borders, rules and large text on dark backgrounds. + */ + +:root { + --gba-green-900: #2C4C3B; /* chrome: utility bar, navbar, card headers, footer */ + --gba-green-700: #3F6C54; /* links, hover */ + --gba-green-050: #EAF1EC; /* tints */ + --gba-orange: #F99742; /* accent rules and borders */ + --gba-orange-700: #D56300; /* accent hover */ + --gba-blue: #005B96; /* section headings */ + --gba-slate: #264653; /* secondary dark (Third Party hero, admin chrome) */ + --gba-text: #333333; + --gba-page: #F8F8F8; + --gba-font: "Roboto", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif; + + /* Per-application hero colour; body[data-gba-app] selects it below. */ + --gba-hero-bg: var(--gba-green-900); + + /* Bootstrap 5.3 globals */ + --bs-primary: var(--gba-green-900); + --bs-primary-rgb: 44, 76, 59; + --bs-link-color: var(--gba-green-700); + --bs-link-color-rgb: 63, 108, 84; + --bs-link-hover-color: var(--gba-green-900); + --bs-link-hover-color-rgb: 44, 76, 59; + --bs-body-font-family: var(--gba-font); + --bs-body-color: var(--gba-text); + --bs-body-bg: var(--gba-page); + --bs-border-radius: 0; + --bs-border-radius-sm: 0; + --bs-border-radius-lg: 0; + --bs-focus-ring-color: rgba(249, 151, 66, 0.45); + + /* Data Custodian application.css tokens, so its existing rules adopt the GBA palette */ + --gb-primary: var(--gba-green-900); + --gb-secondary: var(--gba-green-700); + --gb-accent: var(--gba-green-700); + --gb-warning: var(--gba-orange); +} + +body[data-gba-app="thirdparty"] { + --gba-hero-bg: var(--gba-slate); +} + +body { + font-family: var(--gba-font); + color: var(--gba-text); + background-color: var(--gba-page); + display: flex; + flex-direction: column; + min-height: 100vh; +} + +main { + flex: 1 0 auto; +} + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + font-family: var(--gba-font); + font-weight: 700; +} + +.gba-section-title { + color: var(--gba-blue); +} + +/* ---- Header ---------------------------------------------------------------------------- */ + +.gba-navbar { + background-color: var(--gba-green-900); + border-bottom: 3px solid var(--gba-orange); + box-shadow: none; +} + +.gba-navbar.gba-navbar-admin { + background-color: var(--gba-slate); +} + +.gba-navbar .navbar-brand { + display: flex; + align-items: center; + gap: 0.75rem; + color: #fff; +} + +.gba-brand-mark { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.25rem; + height: 2.25rem; + border-radius: 50%; + border: 2px solid #fff; + color: #fff; + font-size: 1.1rem; +} + +.gba-brand-text { + display: flex; + flex-direction: column; + line-height: 1.1; +} + +.gba-brand-org { + font-size: 0.7rem; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + opacity: 0.85; +} + +.gba-brand-app { + font-size: 1.15rem; + font-weight: 700; +} + +.gba-navbar .nav-link { + color: rgba(255, 255, 255, 0.88); + font-weight: 500; + border-bottom: 2px solid transparent; +} + +.gba-navbar .nav-link:hover, +.gba-navbar .nav-link:focus, +.gba-navbar .nav-link.active { + color: #fff; + border-bottom-color: var(--gba-orange); +} + +/* ---- Buttons (square, outlined, as on greenbuttonalliance.org) --------------------------- */ + +.btn { + --bs-btn-border-radius: 0; + --bs-btn-border-width: 2px; + --bs-btn-font-weight: 500; + --bs-btn-font-family: var(--gba-font); + border-radius: 0; /* the Data Custodian application.css sets a literal 0.5rem */ +} + +.btn:hover { + transform: none; +} + +.btn-primary { + --bs-btn-bg: var(--gba-green-900); + --bs-btn-border-color: var(--gba-green-900); + --bs-btn-hover-bg: var(--gba-green-700); + --bs-btn-hover-border-color: var(--gba-green-700); + --bs-btn-active-bg: var(--gba-green-700); + --bs-btn-active-border-color: var(--gba-green-700); + --bs-btn-disabled-bg: var(--gba-green-900); + --bs-btn-disabled-border-color: var(--gba-green-900); + --bs-btn-focus-shadow-rgb: 249, 151, 66; +} + +.btn-outline-primary { + --bs-btn-color: var(--gba-green-900); + --bs-btn-border-color: var(--gba-green-900); + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: var(--gba-green-900); + --bs-btn-hover-border-color: var(--gba-green-900); + --bs-btn-active-color: #fff; + --bs-btn-active-bg: var(--gba-green-900); + --bs-btn-active-border-color: var(--gba-green-900); + --bs-btn-focus-shadow-rgb: 249, 151, 66; +} + +/* Main call to action on dark backgrounds (hero): orange outline, white text. */ +.btn-gba-accent { + --bs-btn-color: #fff; + --bs-btn-border-color: var(--gba-orange); + --bs-btn-border-width: 3px; + --bs-btn-hover-color: var(--gba-green-900); + --bs-btn-hover-bg: var(--gba-orange); + --bs-btn-hover-border-color: var(--gba-orange); + --bs-btn-active-color: var(--gba-green-900); + --bs-btn-active-bg: var(--gba-orange); + --bs-btn-active-border-color: var(--gba-orange); + --bs-btn-focus-shadow-rgb: 249, 151, 66; +} + +.btn-gba-light { + --bs-btn-color: #fff; + --bs-btn-border-color: rgba(255, 255, 255, 0.85); + --bs-btn-hover-color: var(--gba-green-900); + --bs-btn-hover-bg: #fff; + --bs-btn-hover-border-color: #fff; + --bs-btn-active-color: var(--gba-green-900); + --bs-btn-active-bg: #fff; + --bs-btn-focus-shadow-rgb: 255, 255, 255; +} + +/* ---- Hero ------------------------------------------------------------------------------ */ + +.gba-hero-admin { + --gba-hero-bg: var(--gba-slate); +} + +.gba-hero, +.hero-unit { + background: var(--gba-hero-bg) !important; + /* !important: hero backgrounds are always dark, so override text-dark and similar utilities */ + color: #fff !important; + border-left: 6px solid var(--gba-orange); + border-radius: 0 !important; + box-shadow: none !important; + padding: 3rem; +} + +@media (max-width: 575.98px) { + .gba-hero, + .hero-unit { + padding: 1.5rem !important; + } +} + +.gba-hero a:not(.btn), +.hero-unit a:not(.btn) { + color: #fff; +} + +.gba-hero .lead, +.hero-unit .lead { + font-weight: 400; +} + +/* ---- Cards (solid green header strip, square corners) -------------------------------------- */ + +.card { + --bs-card-bg: #FFFFFF; /* white cards on the grey page, as on greenbuttonalliance.org */ + border: 1px solid #DDDDDD; + border-radius: 0 !important; + box-shadow: none; +} + +.card:hover { + transform: none; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + +.card-header { + border-radius: 0 !important; +} + +.gba-card-header { + background-color: var(--gba-green-900); + color: #fff; + text-align: center; + padding: 0.85rem 1rem; +} + +.gba-card-header h2, +.gba-card-header h3, +.gba-card-header h4, +.gba-card-header h5 { + margin: 0; + font-size: 1.35rem; + color: #fff; +} + +.gba-icon { + color: var(--gba-green-700); +} + +/* ---- Forms ----------------------------------------------------------------------------- */ + +.form-control:focus, +.form-select:focus { + border-color: var(--gba-orange); + box-shadow: 0 0 0 0.2rem rgba(249, 151, 66, 0.35); +} + +/* ---- Footer ---------------------------------------------------------------------------- */ + +.gba-footer { + background-color: var(--gba-green-900); + color: rgba(255, 255, 255, 0.9); + border-top: 3px solid var(--gba-orange); + margin-top: 3rem; + padding: 2rem 0 1.5rem; +} + +/* Data Custodian templates place the footer inside the page .container; let it span the full + viewport anyway (body clips the scrollbar-width overshoot of 100vw). */ +.container > .gba-footer { + margin-left: calc(50% - 50vw); + margin-right: calc(50% - 50vw); +} + +html, +body { + overflow-x: clip; +} + +.gba-footer a { + color: #fff; + text-decoration: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.4); +} + +.gba-footer a:hover, +.gba-footer a:focus { + border-bottom-color: var(--gba-orange); +} + +.gba-footer .gba-footer-title { + font-weight: 700; + font-size: 1.05rem; + color: #fff; +} + +.gba-footer .gba-footer-links { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-wrap: wrap; + gap: 0.5rem 1.25rem; +} + +.gba-footer small { + color: rgba(255, 255, 255, 0.8); +} diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/config/WebConfiguration.java b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/config/WebConfiguration.java index f095f8a19..07a06a4e5 100644 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/config/WebConfiguration.java +++ b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/config/WebConfiguration.java @@ -172,8 +172,10 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { // CSS/JS/images must be registered explicitly; otherwise the templates' /css and /js links // 404 and the portal renders unstyled (#173). These paths are also security-ignored via the // WebSecurityCustomizer in SecurityConfiguration. + // classpath:/META-INF/resources/css/ serves the shared Green Button Alliance theme + // (gba-theme.css) from the OpenESPI-Common jar. registry.addResourceHandler("/css/**") - .addResourceLocations("classpath:/static/css/") + .addResourceLocations("classpath:/static/css/", "classpath:/META-INF/resources/css/") .setCachePeriod(3600); registry.addResourceHandler("/js/**") diff --git a/openespi-datacustodian/src/main/resources/templates/authorize-screen.html b/openespi-datacustodian/src/main/resources/templates/authorize-screen.html index 686ab0450..0aac41a2e 100644 --- a/openespi-datacustodian/src/main/resources/templates/authorize-screen.html +++ b/openespi-datacustodian/src/main/resources/templates/authorize-screen.html @@ -107,8 +107,7 @@
Personal information
- - + diff --git a/openespi-datacustodian/src/main/resources/templates/custodian/home.html b/openespi-datacustodian/src/main/resources/templates/custodian/home.html index f6a03668e..9a967da0b 100644 --- a/openespi-datacustodian/src/main/resources/templates/custodian/home.html +++ b/openespi-datacustodian/src/main/resources/templates/custodian/home.html @@ -9,7 +9,7 @@
-
+

Data Custodian Dashboard

Manage retail customers, monitor OAuth authorizations, and administer Green Button data access. diff --git a/openespi-datacustodian/src/main/resources/templates/customer/home.html b/openespi-datacustodian/src/main/resources/templates/customer/home.html index dbe7a15d9..29df81b8b 100644 --- a/openespi-datacustodian/src/main/resources/templates/customer/home.html +++ b/openespi-datacustodian/src/main/resources/templates/customer/home.html @@ -80,7 +80,6 @@

- - + \ No newline at end of file diff --git a/openespi-datacustodian/src/main/resources/templates/customer/usagepoints/index.html b/openespi-datacustodian/src/main/resources/templates/customer/usagepoints/index.html index 657939480..beffbc09f 100644 --- a/openespi-datacustodian/src/main/resources/templates/customer/usagepoints/index.html +++ b/openespi-datacustodian/src/main/resources/templates/customer/usagepoints/index.html @@ -106,8 +106,7 @@
- - + diff --git a/openespi-datacustodian/src/main/resources/templates/fragments/layout.html b/openespi-datacustodian/src/main/resources/templates/fragments/layout.html index d01cc5b00..8c57199ec 100644 --- a/openespi-datacustodian/src/main/resources/templates/fragments/layout.html +++ b/openespi-datacustodian/src/main/resources/templates/fragments/layout.html @@ -7,12 +7,20 @@ - - - - + + + + + + + + + @@ -20,7 +28,8 @@ - + @@ -52,9 +61,15 @@ -