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..7ce8927d3 --- /dev/null +++ b/openespi-common/src/main/resources/META-INF/resources/css/gba-theme.css @@ -0,0 +1,361 @@ +/* + * 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 ---------------------------------------------------------------------------- */ + +/* As on greenbuttonalliance.org: a dark-green bar on top, a white band carrying the official + logo and green navigation links, and an orange rule underneath. The logo (green and grey on + white, with its TM mark) needs the white band to stay legible. */ +.gba-navbar { + background-color: #FFFFFF; + border-top: 10px solid var(--gba-green-900); + border-bottom: 3px solid var(--gba-orange); + box-shadow: none; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.gba-navbar.gba-navbar-admin { + border-top-color: var(--gba-slate); +} + +.gba-navbar .navbar-brand { + display: flex; + align-items: center; + gap: 0.85rem; + color: var(--gba-green-900); +} + +.gba-logo { + width: 60px; + height: 60px; + flex: 0 0 auto; +} + +.gba-brand-text { + display: flex; + flex-direction: column; + line-height: 1.15; +} + +.gba-brand-org { + font-size: 0.72rem; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--gba-green-700); +} + +.gba-brand-app { + font-size: 1.2rem; + font-weight: 700; + color: var(--gba-green-900); +} + +.gba-navbar .nav-link { + color: var(--gba-green-700); + font-weight: 500; + border-bottom: 2px solid transparent; +} + +.gba-navbar .nav-link:hover, +.gba-navbar .nav-link:focus, +.gba-navbar .nav-link.active, +.gba-navbar .nav-link.show { + color: var(--gba-green-900); + border-bottom-color: var(--gba-orange); +} + +@media (max-width: 575.98px) { + .gba-logo { + width: 48px; + height: 48px; + } +} + +/* ---- 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-common/src/main/resources/META-INF/resources/images/gba-favicon.png b/openespi-common/src/main/resources/META-INF/resources/images/gba-favicon.png new file mode 100644 index 000000000..8c1c4de1f Binary files /dev/null and b/openespi-common/src/main/resources/META-INF/resources/images/gba-favicon.png differ diff --git a/openespi-common/src/main/resources/META-INF/resources/images/gba-logo.png b/openespi-common/src/main/resources/META-INF/resources/images/gba-logo.png new file mode 100644 index 000000000..8c9ee6bc2 Binary files /dev/null and b/openespi-common/src/main/resources/META-INF/resources/images/gba-logo.png differ 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..cc333ac1c 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,16 +172,20 @@ 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/**") .addResourceLocations("classpath:/static/js/") .setCachePeriod(3600); + // classpath:/META-INF/resources/images/ serves the shared GBA logo and favicon from the + // OpenESPI-Common jar. registry.addResourceHandler("/images/**") - .addResourceLocations("classpath:/static/images/") + .addResourceLocations("classpath:/static/images/", "classpath:/META-INF/resources/images/") .setCachePeriod(86400); } 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..1bd5e2bd7 100644 --- a/openespi-datacustodian/src/main/resources/templates/fragments/layout.html +++ b/openespi-datacustodian/src/main/resources/templates/fragments/layout.html @@ -7,20 +7,29 @@ - - - - + + + + + + + + + - - + + - + @@ -52,9 +61,16 @@ -