fix(brand): the logo, the typeface and the palette are HDFC's actual ones
The console was wearing an approximation of the bank. Everything here was read off HDFC Bank's own production assets instead. The mark was inside out. It had been drawn as a blue square with a red square inset; HDFC's is the reverse — a red ground, a white channel cross splitting it into four corner blocks, and a small blue square at the centre. Every coordinate now comes verbatim from the SVG the bank serves in its own site header, including the "HDFC BANK" wordmark outlines, so the lockup is the bank's rather than a redrawing of it. The favicon in index.html carried the same inversion and is fixed with the same numbers. Open Sans replaces the Segoe UI stack. hdfcbank.com preloads OpenSans Regular / SemiBold / Bold / ExtraBold and sets its whole site in it. It is self-hosted through @fontsource-variable/open-sans rather than pulled from a CDN, for the reason the API URL is read at runtime: this build gets promoted between environments and dropped behind bank proxies, and a font that sometimes fails to arrive is a product that sometimes looks like someone else's. The header stops being a navy band. HDFC puts the full lockup on a light ground on every one of its own digital surfaces, and a navy header made the real logo unusable — the lockup's own blue bar would have dissolved into it. The blue now does its work as the rule under the header, the active queue and the primary button, which is how the bank uses it. The lockup's 3px white keyline is the bank's own, so it sits correctly on the panel in dark mode too and no reversed variant is needed. Palette, grounded rather than invented: the page canvas is #F0F6FB, the tint hdfcbank.com uses behind content; the good/ tokens sit on the hue of the bank's #00B947 instead of a generic emerald, darkened to #00713D because #00B947 is 2.6:1 on white and unreadable as a label. The two brand hexes were already right and are unchanged. Also closes three AA failures the palette claimed it did not have — --color-faint in light, and --color-faint / --color-brand in dark, each below 4.5:1 on --color-panel3. White on --color-signal (4.31:1) is left alone knowingly: darkening HDFC red for the Decline button would have made it a different red from the logo beside it.
This commit is contained in:
parent
8949a1b0d7
commit
602b570064
46
README.md
46
README.md
@ -90,6 +90,34 @@ whole app. It is built around three claims:
|
||||
Rules are never violet; the agent's narrative always is. Nobody should have
|
||||
to be told which is which.
|
||||
|
||||
## Brand
|
||||
|
||||
Everything below was read off HDFC Bank's own production assets rather than
|
||||
recalled or approximated. Check the source before changing any of it.
|
||||
|
||||
| What | Value | Where it came from |
|
||||
|---|---|---|
|
||||
| Logo | `src/components/HdfcLogo.tsx` | Coordinates lifted verbatim from `s7ap1.scene7.com/is/content/hdfcbankPWS/hdfc-bank-logo` — the SVG the bank serves in its own site header |
|
||||
| Blue | `#004C8F` | The lockup's bar fill |
|
||||
| Red | `#ED232A` | The mark's ground |
|
||||
| Typeface | Open Sans | hdfcbank.com preloads OpenSans Regular / SemiBold / Bold / ExtraBold and sets its whole site in it. Self-hosted through `@fontsource-variable/open-sans`, never a CDN |
|
||||
| Page tint | `#F0F6FB` | The tint behind content on hdfcbank.com |
|
||||
| Success green | hue of `#00B947` | The bank's green, darkened to `#00713D` — `#00B947` itself is 2.6:1 on white and unreadable as a label |
|
||||
| Tagline | "We understand your world" | Shown once, on the sign-in card |
|
||||
|
||||
**The mark is a RED square with a BLUE centre**, split by a white channel
|
||||
cross — not a blue square with a red inset. It was drawn inside out here
|
||||
originally, in the component *and* in the favicon in `index.html`. Those two
|
||||
carry the same coordinates; change one and you must change the other.
|
||||
|
||||
The lockup carries the bank's own 3px white keyline, which is what lets it sit
|
||||
on a coloured ground — so there is no reversed variant, and none is needed. It
|
||||
is also why the header is a light surface: HDFC puts the full lockup on a light
|
||||
ground on every one of its own digital surfaces, and the navy header this app
|
||||
used to have would have dissolved the lockup's own blue bar into itself. The
|
||||
bank's blue does its work as the rule under the header, the active queue and
|
||||
the primary button.
|
||||
|
||||
## Theming
|
||||
|
||||
One attribute — `data-theme` on `<html>` — swaps the whole console. Every colour
|
||||
@ -97,13 +125,19 @@ is a CSS custom property redefined under `:root[data-theme='dark']` in
|
||||
`src/styles.css`. There is no second stylesheet and no `dark:` prefix anywhere.
|
||||
|
||||
**Never put a literal colour in a component.** A hex or a raw Tailwind shade is
|
||||
invisible to the toggle and will be wrong in one of the two themes.
|
||||
invisible to the toggle and will be wrong in one of the two themes. `HdfcLogo.tsx`
|
||||
is the single exception, and it earns it: a logo's colours are not design
|
||||
decisions, so the mark stays `#ED232A` and `#004C8F` in every theme.
|
||||
|
||||
The palette is the bank's two colours, and **red is never decoration** — it
|
||||
marks the logo block and a decline, nothing else. So when red appears in a
|
||||
queue it means something. The referred queue's amber is the only amber in the
|
||||
pipeline, for the same reason: it should read as "needs you" without reading as
|
||||
an error. A referred file is not a bad file.
|
||||
**Red is never decoration** — it marks the logo and a decline, nothing else. So
|
||||
when red appears in a queue it means something. The referred queue's amber is
|
||||
the only amber in the pipeline, for the same reason: it should read as "needs
|
||||
you" without reading as an error. A referred file is not a bad file.
|
||||
|
||||
Every text colour clears WCAG AA on each surface it actually lands on, in both
|
||||
themes. There is one knowing exception: white on `--color-signal` is 4.31:1 —
|
||||
the Decline button. Darkening HDFC red to clear AA would have made the one red
|
||||
button on the screen a different red from the logo beside it.
|
||||
|
||||
## Known gaps
|
||||
|
||||
|
||||
21
index.html
21
index.html
@ -5,17 +5,24 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!--BASE_HREF-->
|
||||
<title>HDFC Bank | Loan Desk</title>
|
||||
<!-- The logo block as a data-URI favicon: an inline SVG needs no asset,
|
||||
<!-- The bank's mark as a data-URI favicon: an inline SVG needs no asset,
|
||||
which matters because the build is promoted between environments
|
||||
unchanged and a /favicon.ico would resolve against whatever mount path
|
||||
the server chose. -->
|
||||
the server chose.
|
||||
|
||||
These coordinates are the same ones in src/components/HdfcLogo.tsx,
|
||||
taken off HDFC's own vector: a RED ground, a white channel cross, a
|
||||
BLUE centre. It was drawn inside out here too — blue outside, red in —
|
||||
so if you change one of the two, change the other. -->
|
||||
<link
|
||||
rel="icon"
|
||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23004C8F'/%3E%3Crect x='6' y='6' width='20' height='20' fill='%23fff'/%3E%3Crect x='9' y='9' width='14' height='14' fill='%23ED232A'/%3E%3C/svg%3E"
|
||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Crect width='40' height='40' fill='%23ED232A'/%3E%3Crect x='7' y='7' width='26' height='26' fill='%23fff'/%3E%3Crect x='18' width='4' height='40' fill='%23fff'/%3E%3Crect y='18' width='40' height='4' fill='%23fff'/%3E%3Crect x='14' y='14' width='12' height='12' fill='%23004C8F'/%3E%3C/svg%3E"
|
||||
/>
|
||||
<!-- Navy chrome on mobile browsers, matching the app header. Kept in step
|
||||
with the active theme by applyTheme() in src/theme.ts. -->
|
||||
<meta name="theme-color" content="#004c8f" />
|
||||
<!-- Mobile browser chrome, matching the app header — which is the panel
|
||||
colour, not the bank's blue: HDFC sets its logo on a light ground
|
||||
everywhere, so the console does too. Kept in step with the active
|
||||
theme by applyTheme() in src/theme.ts. -->
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<!-- Theme, resolved BEFORE first paint.
|
||||
This duplicates a few lines of src/theme.ts on purpose. The bundle is a
|
||||
module and therefore deferred, so resolving the theme in React means
|
||||
@ -35,7 +42,7 @@
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
if (t === 'dark') {
|
||||
var m = document.querySelector('meta[name="theme-color"]');
|
||||
if (m) m.setAttribute('content', '#00152b');
|
||||
if (m) m.setAttribute('content', '#052b52');
|
||||
}
|
||||
} catch (e) {
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@ -8,6 +8,7 @@
|
||||
"name": "hdfc-loan-desk",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/open-sans": "^5.3.0",
|
||||
"lucide-react": "^0.454.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -696,6 +697,15 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource-variable/open-sans": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/open-sans/-/open-sans-5.3.0.tgz",
|
||||
"integrity": "sha512-VOfhZfLIOBVa6K/dTEu7scqQXYdGa4jBX8quFpQ0S6574B+W2EOBJf9PkqyVtMEI7WWIWNEbpvPc3vY1mN2mmQ==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
"typecheck": "tsc -b --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/open-sans": "^5.3.0",
|
||||
"lucide-react": "^0.454.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
143
src/components/HdfcLogo.tsx
Normal file
143
src/components/HdfcLogo.tsx
Normal file
@ -0,0 +1,143 @@
|
||||
/**
|
||||
* HDFC Bank's logo, reproduced exactly.
|
||||
*
|
||||
* WHERE THESE NUMBERS COME FROM
|
||||
*
|
||||
* Every coordinate below is lifted from the bank's own vector asset — the file
|
||||
* served in the header of hdfcbank.com:
|
||||
*
|
||||
* https://s7ap1.scene7.com/is/content/hdfcbankPWS/hdfc-bank-logo
|
||||
*
|
||||
* Nothing here is drawn by eye. That matters because the mark had been drawn by
|
||||
* eye before, and it was inside out: a blue square with a red one inset. The
|
||||
* real mark is the other way round — a RED ground, a white channel cross that
|
||||
* splits it into four corner blocks, and a small BLUE square at the centre.
|
||||
* Getting that backwards is the kind of thing a client notices in the first
|
||||
* five seconds of a demo.
|
||||
*
|
||||
* The mark, normalised to a 40x40 grid, is:
|
||||
*
|
||||
* red 0,0 40x40 the ground
|
||||
* white 7,7 26x26 the inner square
|
||||
* white 18,0 4x40 the vertical channel
|
||||
* white 0,18 40x4 the horizontal channel
|
||||
* blue 14,14 12x12 the centre
|
||||
*
|
||||
* THE TWO COLOURS ARE FIXED AND ARE NOT THEME TOKENS. A logo does not restyle
|
||||
* itself for dark mode: HDFC red is #ED232A and HDFC blue is #004C8F on every
|
||||
* surface, in every theme, forever. The white is real white for the same
|
||||
* reason. This is the one file in the app allowed to hold literal colours, and
|
||||
* the reason it is allowed is that these are not design decisions.
|
||||
*
|
||||
* The full lockup carries a 3px white keyline, exactly as the bank's asset
|
||||
* does. That keyline is what lets the logo sit on a coloured ground without
|
||||
* the blue bar bleeding into it — so the lockup is safe on the panel in either
|
||||
* theme, and no reversed variant is needed.
|
||||
*/
|
||||
|
||||
/** HDFC red. Fixed. */
|
||||
const RED = '#ED232A';
|
||||
/** HDFC blue. Fixed. */
|
||||
const BLUE = '#004C8F';
|
||||
|
||||
/**
|
||||
* "HDFC BANK", as outlines rather than as text.
|
||||
*
|
||||
* The wordmark is Helvetica Black, which is licensed and which we therefore
|
||||
* cannot ship. Setting it in a substitute would produce a logo that is *nearly*
|
||||
* right, which is worse than one that is obviously a placeholder. The bank's
|
||||
* own outlines are exact and cost 3.6 KB, so the path is inlined verbatim from
|
||||
* the asset above. Do not reformat it.
|
||||
*/
|
||||
const WORDMARK =
|
||||
'M40.7812 24.1165V10.5516H45.2432V15.13H49.4243V10.5516H53.8929V24.1165H49.4243V18.8534H45.2432V24.1165H40.7812ZM60.9539 13.9222H62.1106C62.9432 13.9222 63.5524 14.0085 63.9418 14.1816C64.2299 14.3074 64.4641 14.4955 64.6696 14.7765C64.8571 15.0539 65.0086 15.4038 65.12 15.8435C65.2213 16.2797 65.2752 16.741 65.2752 17.2274C65.2752 18.0207 65.1673 18.6839 64.9325 19.2213C64.7055 19.7581 64.3779 20.1439 63.9562 20.3817C63.5345 20.6238 62.9073 20.7388 62.0783 20.7388L60.9539 20.746V13.9222ZM62.6767 24.1165C63.7291 24.1165 64.6013 24.0303 65.3147 23.8391C66.0323 23.6552 66.6157 23.4102 67.0626 23.1178C67.5172 22.8183 67.9276 22.4151 68.3061 21.9136C68.6811 21.3948 69.0094 20.7424 69.2645 19.9426C69.5281 19.1458 69.6539 18.2477 69.6539 17.2316C69.6539 15.7393 69.3694 14.4776 68.7931 13.4249C68.2091 12.3939 67.416 11.6474 66.4031 11.2083C65.3944 10.7679 64.1904 10.5516 62.7917 10.5516H56.589V24.1165H62.6767ZM71.6983 24.1165V10.5516H82.2797V13.897H76.1022V16.0706H81.044V19.3255H76.1022V24.1165H71.6983ZM97.2225 15.7393H93.0097C92.93 15.0647 92.685 14.5315 92.2777 14.1493C91.8668 13.7742 91.3517 13.5903 90.7173 13.5903C89.9278 13.5903 89.2863 13.897 88.7927 14.5027C88.2955 15.1156 88.0463 16.1065 88.0463 17.4695C88.0463 18.4209 88.1584 19.135 88.3638 19.6215C88.5765 20.1223 88.8754 20.483 89.2467 20.7244C89.6289 20.9659 90.0937 21.0779 90.6664 21.0779C91.3516 21.0779 91.8991 20.9041 92.2993 20.5081C92.7102 20.1367 92.948 19.5819 93.0277 18.839H97.3165C97.2225 19.6826 97.053 20.3889 96.808 20.9659C96.5744 21.5392 96.1557 22.1089 95.5902 22.6817C95.0096 23.2669 94.3119 23.7229 93.5428 24.0195C92.7533 24.3328 91.8416 24.4808 90.7964 24.4808C89.7661 24.4808 88.8035 24.3328 87.9349 24.0303C87.0556 23.7199 86.3128 23.2802 85.7216 22.7284C85.1196 22.1557 84.6512 21.514 84.3163 20.7891C83.8329 19.7694 83.6059 18.6479 83.6059 17.397C83.6059 16.3516 83.7754 15.36 84.1181 14.4524C84.4601 13.5508 84.9327 12.7833 85.5341 12.1632C86.1397 11.5462 86.7992 11.0848 87.5348 10.7751C88.4644 10.3785 89.4881 10.188 90.6233 10.188C91.611 10.188 92.5263 10.3318 93.3913 10.6235C94.2526 10.9159 94.9595 11.3413 95.5105 11.9074C96.0653 12.4662 96.5028 13.1302 96.7972 13.8605C97.0134 14.3763 97.1506 15 97.2225 15.7393M111.514 13.6053C112.144 13.6053 112.552 13.634 112.753 13.6915C113.002 13.7623 113.222 13.9084 113.384 14.1097C113.542 14.3116 113.626 14.5459 113.626 14.8167C113.626 15.1623 113.488 15.4469 113.226 15.6632C112.944 15.8908 112.465 15.9951 111.776 15.9951H110.097V13.6047L111.514 13.6053ZM112.634 24.1165C113.69 24.1165 114.433 24.0662 114.869 23.9728C115.324 23.8615 115.768 23.7043 116.192 23.5037C116.631 23.273 116.97 23.0567 117.193 22.8189C117.54 22.4978 117.795 22.1089 117.983 21.6398C118.17 21.1678 118.264 20.6345 118.264 20.0612C118.264 19.2428 118.062 18.5688 117.652 18.0207C117.241 17.4695 116.689 17.1052 115.994 16.9069C117.172 16.164 117.759 15.2055 117.759 14.0372C117.759 12.83 117.241 11.9074 116.213 11.2802C115.42 10.7967 114.184 10.5516 112.508 10.5516H105.779V24.1165H112.634ZM111.917 18.4352C112.8 18.4352 113.373 18.5431 113.662 18.7707C113.928 18.9906 114.073 19.2932 114.073 19.7005C114.073 20.1115 113.928 20.4362 113.636 20.6633C113.358 20.9047 112.779 21.0054 111.917 21.0054H110.097V18.4352H111.917ZM128.064 19.5568H125.227L126.626 14.6214L128.064 19.5568ZM123.894 24.1165L124.409 22.336H128.849L129.369 24.1165H133.806L128.94 10.5516H124.409L119.54 24.1165H123.894ZM135.679 24.1165V10.5516H139.835L144.524 17.8152V10.5516H148.799V24.1165H144.553L139.964 17.0333V24.1165H135.679ZM151.772 24.1165V10.5516H156.094V15.0251L160.127 10.5516H165.367L160.397 15.6596L165.883 24.1165H160.542L157.279 18.7126L156.093 19.8845V24.1165H151.772Z';
|
||||
|
||||
/**
|
||||
* The mark on its own — the red square. Use where the full lockup will not fit:
|
||||
* a favicon-sized slot, a dense table, a collapsed mobile header.
|
||||
*/
|
||||
export function HdfcMark({ size = 32, className = '' }: { size?: number; className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 40 40"
|
||||
className={`shrink-0 ${className}`}
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="40" height="40" fill={RED} />
|
||||
<rect x="7" y="7" width="26" height="26" fill="#ffffff" />
|
||||
<rect x="18" width="4" height="40" fill="#ffffff" />
|
||||
<rect y="18" width="40" height="4" fill="#ffffff" />
|
||||
<rect x="14" y="14" width="12" height="12" fill={BLUE} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The full lockup: mark, blue bar, white wordmark, white keyline.
|
||||
*
|
||||
* Sized by HEIGHT only — the width follows from the asset's 178:35 ratio, so
|
||||
* the proportions can never drift. A logo stretched to fit a container is a
|
||||
* logo used wrongly.
|
||||
*/
|
||||
export function HdfcLogo({ height = 30, className = '' }: { height?: number; className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
height={height}
|
||||
width={(height * 178) / 35}
|
||||
viewBox="0 0 178 35"
|
||||
className={`shrink-0 ${className}`}
|
||||
role="img"
|
||||
aria-label="HDFC Bank"
|
||||
>
|
||||
<title>HDFC Bank</title>
|
||||
{/* The bar, with the keyline that lets it sit on any ground. */}
|
||||
<path
|
||||
d="M3 1.5H1.5V3V31.6689V33.1689H3H175H176.5V31.6689V3V1.5H175H3Z"
|
||||
fill={BLUE}
|
||||
stroke="#ffffff"
|
||||
strokeWidth="3"
|
||||
/>
|
||||
<path d="M31.668 3H175V31.6689H31.668V3Z" fill={BLUE} />
|
||||
<path d={WORDMARK} fill="#ffffff" />
|
||||
{/* The mark. */}
|
||||
<path d="M3 3H31.6642V31.6689H3V3Z" fill={RED} />
|
||||
<path d="M8.01562 8.01758H26.6495V26.6545H8.01562V8.01758Z" fill="#ffffff" />
|
||||
<path d="M15.9023 3H18.768V31.6689H15.9023V3Z" fill="#ffffff" />
|
||||
<path d="M3 15.9048H31.6642V18.7709H3V15.9048Z" fill="#ffffff" />
|
||||
<path d="M13.0312 13.0352H21.6306V21.6365H13.0312V13.0352Z" fill={BLUE} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The header identity: the lockup, then the desk's name beside it.
|
||||
*
|
||||
* The sub-name is deliberately OUTSIDE the logo. "Loan Desk" is not part of
|
||||
* HDFC's mark and must never be set as though it were — a bank's identity is
|
||||
* not a slot you type your product into. A vertical rule and a distinctly
|
||||
* lighter weight say "this application, at this bank", which is the true
|
||||
* relationship.
|
||||
*
|
||||
* Narrow screens drop to the mark alone rather than shrinking the lockup: at
|
||||
* phone widths the wordmark's counters close up and it stops reading as HDFC's
|
||||
* logo at all, where the mark stays recognisable down to favicon size.
|
||||
*/
|
||||
export function HdfcAppLogo({ subtitle = 'Loan Desk' }: { subtitle?: string }) {
|
||||
return (
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<span className="sm:hidden">
|
||||
<HdfcMark size={28} />
|
||||
</span>
|
||||
<span className="hidden sm:inline-flex">
|
||||
<HdfcLogo height={28} />
|
||||
</span>
|
||||
<span aria-hidden="true" className="hidden h-7 w-px bg-line lg:block" />
|
||||
<span className="hidden text-[13px] font-semibold tracking-[0.14em] text-muted uppercase lg:inline">
|
||||
{subtitle}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* The bank's mark: a blue block with a red square inset, set beside the
|
||||
* wordmark.
|
||||
*
|
||||
* This is a demo asset, drawn from tokens rather than shipped as an image
|
||||
* file — the build is promoted between environments unchanged, so an
|
||||
* `/logo.png` would resolve against whatever mount path the server chose.
|
||||
* Drawing it also means it obeys the theme: on the navy header the white
|
||||
* separator band has to be white, and on a light panel it has to be the panel
|
||||
* colour, which an image cannot do.
|
||||
*/
|
||||
export function HdfcMark({ size = 26, onNavy = false }: { size?: number; onNavy?: boolean }) {
|
||||
// The inner band reads as the ground showing through. On navy chrome that is
|
||||
// white; on a light surface it is the panel.
|
||||
const band = onNavy ? '#ffffff' : 'var(--color-panel)';
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 32 32"
|
||||
aria-hidden="true"
|
||||
className="shrink-0"
|
||||
role="presentation"
|
||||
>
|
||||
<rect width="32" height="32" rx="2" fill="var(--color-navy)" />
|
||||
<rect x="5" y="5" width="22" height="22" fill={band} />
|
||||
<rect x="8.5" y="8.5" width="15" height="15" fill="var(--color-signal)" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function HdfcWordmark({ onNavy = false }: { onNavy?: boolean }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2.5">
|
||||
<HdfcMark onNavy={onNavy} />
|
||||
<div className="leading-none">
|
||||
<div
|
||||
className={`text-[15px] font-bold tracking-tight ${onNavy ? 'text-white' : 'text-ink'}`}
|
||||
>
|
||||
HDFC BANK
|
||||
</div>
|
||||
<div
|
||||
className={`mt-1 text-[10px] font-semibold tracking-[0.14em] uppercase ${
|
||||
onNavy ? 'text-white/70' : 'text-faint'
|
||||
}`}
|
||||
>
|
||||
Loan Desk
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,12 @@
|
||||
import { Moon, Sun } from 'lucide-react';
|
||||
import { useTheme } from '../theme';
|
||||
|
||||
export function ThemeToggle({ onNavy = false }: { onNavy?: boolean }) {
|
||||
/**
|
||||
* The `onNavy` variant went with the navy header. The chrome is `bg-panel` in
|
||||
* both themes now — white in light, navy in dark — so one set of token classes
|
||||
* covers every place this button appears.
|
||||
*/
|
||||
export function ThemeToggle() {
|
||||
const { theme, toggle } = useTheme();
|
||||
const next = theme === 'dark' ? 'light' : 'dark';
|
||||
return (
|
||||
@ -9,11 +14,7 @@ export function ThemeToggle({ onNavy = false }: { onNavy?: boolean }) {
|
||||
onClick={toggle}
|
||||
title={`Switch to ${next} mode`}
|
||||
aria-label={`Switch to ${next} mode`}
|
||||
className={`rounded-md p-2 transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 ${
|
||||
onNavy
|
||||
? 'text-white/75 hover:bg-white/10 hover:text-white focus-visible:outline-white'
|
||||
: 'text-muted hover:bg-panel2 hover:text-ink focus-visible:outline-brand'
|
||||
}`}
|
||||
className="rounded-md p-2 text-muted transition-colors hover:bg-panel2 hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
|
||||
>
|
||||
{theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
||||
</button>
|
||||
|
||||
@ -2,13 +2,24 @@ import { NavLink, Outlet, useNavigate } from 'react-router-dom';
|
||||
import { LogOut, Plus } from 'lucide-react';
|
||||
import { useZino } from '../api/provider';
|
||||
import { QUEUES, prettyRole } from '../api/config';
|
||||
import { HdfcWordmark } from '../components/HdfcMark';
|
||||
import { HdfcAppLogo } from '../components/HdfcLogo';
|
||||
import { ThemeToggle } from '../components/ThemeToggle';
|
||||
import { Button } from '../components/core';
|
||||
|
||||
/**
|
||||
* The chrome.
|
||||
*
|
||||
* WHY THE HEADER IS A LIGHT SURFACE AND NOT A NAVY BAR
|
||||
*
|
||||
* It used to be a navy band with the wordmark set in white on top of it. That
|
||||
* is not how HDFC Bank presents itself anywhere: every one of the bank's own
|
||||
* digital surfaces puts the FULL LOCKUP — red mark, blue bar, white "HDFC
|
||||
* BANK" — on a light ground. The navy bar also made the real logo impossible
|
||||
* to use, because the lockup's own blue bar would have dissolved into it. So
|
||||
* the chrome gives the logo the ground it was drawn for, and the bank's blue
|
||||
* does its work where it belongs: the rule under the header, the active queue,
|
||||
* and the primary button.
|
||||
*
|
||||
* The sidebar IS the pipeline: every entry is the record view filtered on
|
||||
* current_state_name, in the order a file actually moves. That is the whole
|
||||
* navigation model, and it means a person learns the process by using the app.
|
||||
@ -26,44 +37,42 @@ export function Shell() {
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col">
|
||||
{/* Header — navy in both themes. The bank's colour is not a surface. */}
|
||||
<header className="sticky top-0 z-30 border-b border-deepnavy bg-navy">
|
||||
<header className="sticky top-0 z-30 bg-panel">
|
||||
<div className="flex items-center gap-4 px-4 py-2.5">
|
||||
<HdfcWordmark onNavy />
|
||||
<HdfcAppLogo />
|
||||
|
||||
<div className="ml-auto flex items-center gap-3">
|
||||
<Button
|
||||
kind="secondary"
|
||||
onClick={() => navigate('/new')}
|
||||
className="!bg-white/10 !text-white !ring-white/20 hover:!bg-white/20"
|
||||
>
|
||||
<Button kind="primary" onClick={() => navigate('/new')}>
|
||||
<Plus className="h-4 w-4" />
|
||||
New Application
|
||||
</Button>
|
||||
|
||||
{user && (
|
||||
<div className="hidden text-right sm:block">
|
||||
<div className="text-[13px] leading-tight font-semibold text-white">
|
||||
<div className="text-[13px] leading-tight font-semibold text-ink">
|
||||
{user.name || user.email}
|
||||
</div>
|
||||
<div className="text-[11px] leading-tight text-white/65">
|
||||
<div className="text-[11px] leading-tight text-faint">
|
||||
{(user.roles ?? []).map(prettyRole).join(' · ') || 'No role'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ThemeToggle onNavy />
|
||||
<ThemeToggle />
|
||||
|
||||
<button
|
||||
onClick={logout}
|
||||
title="Sign out"
|
||||
aria-label="Sign out"
|
||||
className="rounded-md p-2 text-white/75 transition-colors hover:bg-white/10 hover:text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white"
|
||||
className="rounded-md p-2 text-muted transition-colors hover:bg-panel2 hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* The bank's blue, drawn as a rule rather than a field. A 3px band is
|
||||
enough to brand the chrome; a 56px one competes with the logo. */}
|
||||
<div aria-hidden="true" className="h-[3px] bg-navy" />
|
||||
</header>
|
||||
|
||||
<div className="flex flex-1 flex-col lg:flex-row">
|
||||
|
||||
@ -4,6 +4,13 @@ import { BrowserRouter } from 'react-router-dom';
|
||||
import { App } from './App';
|
||||
import { ZinoProvider } from './api/provider';
|
||||
import { basePath, requireConfigValue } from './runtimeConfig';
|
||||
// Open Sans is HDFC Bank's typeface — their site self-hosts it and sets both
|
||||
// body and headings in it. Self-hosted here for the same reason the API URL is
|
||||
// read at runtime: this build is promoted between environments and dropped
|
||||
// behind bank proxies, and a font fetched from a CDN is a font that sometimes
|
||||
// does not arrive. `wght` is the weight-axis-only variable file, which covers
|
||||
// the 400 / 600 / 700 / 800 this design uses in a single download.
|
||||
import '@fontsource-variable/open-sans/wght.css';
|
||||
import './styles.css';
|
||||
|
||||
/**
|
||||
|
||||
@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||
import { useZino } from '../api/provider';
|
||||
import { ORG_ID } from '../api/config';
|
||||
import { Button, ErrorNote, Input, Label } from '../components/core';
|
||||
import { HdfcWordmark } from '../components/HdfcMark';
|
||||
import { HdfcAppLogo, HdfcLogo } from '../components/HdfcLogo';
|
||||
import { ThemeToggle } from '../components/ThemeToggle';
|
||||
|
||||
/**
|
||||
@ -38,9 +38,12 @@ export function Login() {
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col bg-bg">
|
||||
<header className="flex items-center justify-between border-b border-deepnavy bg-navy px-4 py-2.5">
|
||||
<HdfcWordmark onNavy />
|
||||
<ThemeToggle onNavy />
|
||||
<header className="bg-panel">
|
||||
<div className="flex items-center justify-between px-4 py-2.5">
|
||||
<HdfcAppLogo />
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<div aria-hidden="true" className="h-[3px] bg-navy" />
|
||||
</header>
|
||||
|
||||
<div className="flex flex-1 items-center justify-center p-4">
|
||||
@ -51,16 +54,20 @@ export function Login() {
|
||||
}}
|
||||
className="lift w-full max-w-sm rounded-lg border border-line bg-panel p-6"
|
||||
>
|
||||
<h1 className="text-lg font-semibold text-ink">Sign in</h1>
|
||||
{/* The lockup again, above the fold of the only screen a signed-out
|
||||
person sees. A sign-in page is where someone checks they are at
|
||||
the right bank before typing a password into it. */}
|
||||
<HdfcLogo height={30} />
|
||||
<h1 className="mt-5 text-lg font-semibold text-ink">Sign in</h1>
|
||||
<p className="mt-1 text-sm text-muted">
|
||||
MSME and personal loan origination.
|
||||
Loan Desk — MSME and personal loan origination.
|
||||
</p>
|
||||
|
||||
<div className="mt-5 space-y-3">
|
||||
{err && <ErrorNote>{err}</ErrorNote>}
|
||||
<div>
|
||||
<Label required>Email</Label>
|
||||
<Input value={email} onChange={setEmail} type="email" placeholder="name@hdfc.example" />
|
||||
<Input value={email} onChange={setEmail} type="email" placeholder="name@hdfcbank.com" />
|
||||
</div>
|
||||
<div>
|
||||
<Label required>Password</Label>
|
||||
@ -70,6 +77,13 @@ export function Login() {
|
||||
Sign in
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* The bank's line, set the way the bank sets it — under the mark,
|
||||
light, small, and not shouted. It belongs on the one screen that
|
||||
is purely the bank talking to its own staff, and nowhere else. */}
|
||||
<p className="mt-6 border-t border-line pt-4 text-center text-[11px] tracking-wide text-faint">
|
||||
We understand your world
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -17,11 +17,25 @@
|
||||
className is invisible to the toggle and will be wrong in one of the two
|
||||
themes. That is what the semantic tone tokens below are for.
|
||||
|
||||
THE PALETTE IS HDFC'S TWO COLOURS. Their identity is a navy blue and a
|
||||
red, and the discipline here is that RED IS NEVER DECORATION. It marks
|
||||
the logo block, a decline, and nothing else — so when red appears in the
|
||||
queue it means something. The pipeline's own accent work is done by the
|
||||
blue and by the amber that marks the referred queue.
|
||||
WHERE THE BRAND VALUES COME FROM
|
||||
|
||||
Not from memory and not from a palette site. The two brand colours, the
|
||||
page tint and the typeface were all read off HDFC Bank's own production
|
||||
assets:
|
||||
|
||||
#004C8F, #ED232A the logo's two fills, taken from the vector the bank
|
||||
serves in its own site header (see HdfcLogo.tsx)
|
||||
#F0F6FB the page tint used behind content on hdfcbank.com
|
||||
Open Sans the bank self-hosts OpenSans Regular / SemiBold /
|
||||
Bold / ExtraBold and sets its whole site in it
|
||||
#00B947 the bank's success green — used here only as the HUE
|
||||
the good/ tokens sit on, because #00B947 itself is
|
||||
2.6:1 on white and unreadable as text
|
||||
|
||||
THE DISCIPLINE: RED IS NEVER DECORATION. It marks the logo and a decline,
|
||||
and nothing else — so when red appears in the queue it means something.
|
||||
The pipeline's accent work is done by the blue and by the amber that marks
|
||||
the referred queue.
|
||||
|
||||
Every colour used for text is checked at WCAG AA against the surfaces it
|
||||
actually appears on, in both themes. */
|
||||
@ -29,12 +43,12 @@
|
||||
/* --- Structural ---------------------------------------------------- */
|
||||
--color-ink: #10243d; /* primary text */
|
||||
--color-muted: #4a5f7a; /* secondary text */
|
||||
--color-faint: #6b7f96; /* labels, placeholders, dashes */
|
||||
--color-bg: #f6f8fb; /* page canvas */
|
||||
--color-faint: #586c83; /* labels, placeholders, dashes */
|
||||
--color-bg: #f0f6fb; /* page canvas — hdfcbank.com's */
|
||||
--color-panel: #ffffff; /* card face */
|
||||
--color-panel2: #f1f5fa; /* input fill, row hover */
|
||||
--color-panel3: #e4ecf5; /* pressed / hover on panel2 */
|
||||
--color-line: #d8e2ee; /* borders */
|
||||
--color-panel2: #f1f6fb; /* input fill, row hover */
|
||||
--color-panel3: #e2ecf7; /* pressed / hover on panel2 */
|
||||
--color-line: #d6e3f0; /* borders */
|
||||
|
||||
/* Brand accent. Used BOTH as a fill and as text, so it carries a
|
||||
companion for whatever sits on top of it. */
|
||||
@ -43,10 +57,9 @@
|
||||
--color-brand-hover: #003a6f;
|
||||
|
||||
/* --- HDFC palette, fixed in both themes ---------------------------- */
|
||||
/* Surfaces invert between themes; the bank's own colours do not. The
|
||||
header is navy in dark mode too. */
|
||||
/* Surfaces invert between themes; the bank's own colours do not. These two
|
||||
are the exact fills of the bank's logo and are never redefined below. */
|
||||
--color-navy: #004c8f;
|
||||
--color-deepnavy: #002e5f;
|
||||
--color-signal: #ed232a; /* HDFC red — declines and the logo block */
|
||||
--color-signal-hover: #c91d23;
|
||||
|
||||
@ -75,9 +88,12 @@
|
||||
--color-info-soft: #e8f1f9;
|
||||
--color-info-edge: #a9c9e6;
|
||||
|
||||
--color-good: #0a6b4a;
|
||||
--color-good-soft: #eafaf3;
|
||||
--color-good-edge: #94ceb4;
|
||||
/* Sat on HDFC's own success green (#00B947) rather than a generic emerald,
|
||||
but darkened until it clears AA: the bank's green is 2.6:1 on white,
|
||||
which is a fine fill and an unreadable label. Same hue, usable weight. */
|
||||
--color-good: #00713d;
|
||||
--color-good-soft: #e4f6ec;
|
||||
--color-good-edge: #93d3b0;
|
||||
|
||||
/* Drawn from HDFC red rather than Tailwind's rose, so a Declined badge
|
||||
and a Decline button are visibly the same red rather than two
|
||||
@ -99,12 +115,15 @@
|
||||
dialogue. */
|
||||
--color-scrim: rgb(0 24 48 / 0.45);
|
||||
|
||||
/* HDFC's own typeface is not licensable for redistribution, so the stack
|
||||
leads with what a bank desktop is likely to have and degrades through
|
||||
humanist relatives. */
|
||||
/* Open Sans — what HDFC Bank actually sets its digital estate in. Their
|
||||
site preloads OpenSans Regular / SemiBold / Bold / ExtraBold as woff2 and
|
||||
uses nothing else for body or headings.
|
||||
Self-hosted through @fontsource-variable/open-sans (imported in
|
||||
main.tsx), never a CDN: this build is promoted between environments and
|
||||
a bank desktop behind a proxy that cannot reach fonts.googleapis.com
|
||||
would silently fall back and render as a different product. */
|
||||
--font-sans:
|
||||
'Segoe UI', Inter, 'Source Sans 3', system-ui, -apple-system, 'Helvetica Neue', Arial,
|
||||
sans-serif;
|
||||
'Open Sans Variable', 'Open Sans', system-ui, -apple-system, 'Segoe UI', Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Non-Tailwind vars: consumed by the hand-rolled rules at the bottom rather
|
||||
@ -130,7 +149,7 @@
|
||||
:root[data-theme='dark'] {
|
||||
--color-ink: #e8f0f9;
|
||||
--color-muted: #a6c0dc;
|
||||
--color-faint: #8aa6c6;
|
||||
--color-faint: #99b3cf;
|
||||
--color-bg: #00152b;
|
||||
--color-panel: #052b52;
|
||||
--color-panel2: #0b3762;
|
||||
@ -140,9 +159,9 @@
|
||||
/* The accent lightens and the text on it darkens — the inverse of light.
|
||||
A #004C8F fill would vanish into the canvas, and #004C8F text on navy
|
||||
is unreadable. */
|
||||
--color-brand: #6fa8e8;
|
||||
--color-brand: #7cb2ec;
|
||||
--color-brand-fg: #00152b;
|
||||
--color-brand-hover: #8fbdf0;
|
||||
--color-brand-hover: #93c2f2;
|
||||
|
||||
/* HDFC red is unchanged: white on it clears AA either way, and it still
|
||||
clears 3:1 against the navy canvas as a large component. */
|
||||
@ -160,9 +179,9 @@
|
||||
--color-info-soft: #0f3763;
|
||||
--color-info-edge: #467cb8;
|
||||
|
||||
--color-good: #7fd6a8;
|
||||
--color-good-soft: #08301f;
|
||||
--color-good-edge: #2c6a4a;
|
||||
--color-good: #5fd694;
|
||||
--color-good-soft: #052a18;
|
||||
--color-good-edge: #10683b;
|
||||
|
||||
--color-bad: #f4a3a6;
|
||||
--color-bad-soft: #3c1416;
|
||||
|
||||
@ -56,9 +56,13 @@ export function resolveTheme(): Theme {
|
||||
export function applyTheme(theme: Theme): void {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
// Keep the mobile browser chrome in step with the header it sits above.
|
||||
// That is --color-panel in both themes, not the bank's blue: the header is a
|
||||
// light surface so the real logo lockup has the ground it was drawn for.
|
||||
// Keep these two literals in step with `--color-panel` in styles.css and
|
||||
// with the inline script in index.html.
|
||||
document
|
||||
.querySelector('meta[name="theme-color"]')
|
||||
?.setAttribute('content', theme === 'dark' ? '#00152b' : '#004c8f');
|
||||
?.setAttribute('content', theme === 'dark' ? '#052b52' : '#ffffff');
|
||||
}
|
||||
|
||||
export function useTheme(): { theme: Theme; setTheme: (t: Theme) => void; toggle: () => void } {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user