
/* GLOBAL */
/* ==== */
html {
scroll-behavior: smooth;
}
#savecontent {
margin-top: -2px;
}
.anchor {
scroll-margin-top: 150px;
}
h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6 {
color: inherit;
}
h1, .h1 {
font-size: clamp(35px, 4.4vw, 46px);
}
h1.mega, .h1.mega {
font-size: clamp(36px, 4.7vw, 70px);
}
h1.ultra, .h1.ultra {
font-size: clamp(37px, 5.3vw, 95px);
}
h2, .h2 {
font-size: clamp(32px, 4vw, 42px);
}
h3, .h3 {
font-size: clamp(29px, 3.6vw, 37px);
}
h4, .h4 {
font-size: clamp(26px, 3.2vw, 32px);
}
h5, .h5 {
font-size: clamp(23px, 2.8vw, 27px);
}
h6, .h6 {
font-size: clamp(20px, 2.4vw, 22px);
}
@media(min-width: 1200px){
.es-subhero h1.es-display {
font-size: 60px;
}}
body {
font-size: clamp(15px, 2vw, 18px);
}
.btn {
white-space: normal;
}
a:hover img,
a.bg-img:hover a.bg-image:hover {
opacity: .85;
}
#savecontent img {
max-width: 100% !important;
height: auto !important;
}
.rightfloat {
float: right;
margin: 0 0 .5rem 1rem;
width: 40%;
}
@media (max-width: 767px) {
.rightfloat {
float: none;
margin: 0 0 1rem 0;
width: 100%;
}
}
.btn-primary {
color: white !important;
}
/* Kept as a zeroed rule rather than deleted. The class is used 15 times on the
homepage alone and is sprinkled through CMS page content across the site, so
removing it would mean hunting it out of every page in the editor for no
visual gain. Zeroed here, it is simply inert wherever it appears. */
.round {
border-radius: 0;
}
.carousel-control-next-icon,
.carousel-control-prev-icon {
background-color: rgba(0, 0, 0, 0.9);
padding: 1rem;
background-size: 20px;
}
@media(min-width: 1600px) {
.container {
max-width: 1300px;
}
}
@media(min-width: 1600px) {
.container.container-wide {
width: 1500px !important;
max-width: 1500px !important;
}
}
.product-image {
background-size: cover !important;
min-height: auto !important;
padding-bottom: 75%;
}
#savecontent form label {
font-weight: bold;
}
#savecontent form .checkbox-group label,
#savecontent form .radio-group label {
font-weight: normal;
}
.formbuilder-required {
color: red;
}
.text-right.addToCart {
text-align: left !important;
}
.blog-detail-page {
background: var(--light);
}
.blog-detail-page .blog {
border-radius: 0;
padding: 1.5rem;
background: white;
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.2);
}
.oms-blog {
background: var(--light);
}
.blog-item .blog-image {
padding: .5rem;
}
.blog-item .blog-image a.d-flex {
padding-bottom: 70% !important;
background-size: contain !important;
background-repeat: no-repeat;
}
.blog-data .blog-byline, .blog-data .blog-description {
display: none;
}
.blog-wrapper .blog-item {
background: white;
}
.blog-wrapper .blog-item.border {
border: 0 solid transparent !important;
}
.blog-data .blog-title.h5.font-weight-bold {
font-weight: normal !important;
font-size: 1.5rem !important;
color: var(--dark) !important;
}
.blog-detail-page .blog h4 {
margin-top: 1rem;
}
form li p {
margin-bottom: 0;
}
form label {
font-weight: bold;
}
form .checkbox-group label,
form .radio-group label {
font-weight: normal;
}
.formbuilder-checkbox,
.formbuilder-radio {
margin-bottom: 4px;
}
/* ==========================================================================
ESSENZA ARCHITECTURE
Brand colour comes from var(--primary) and var(--primary-alt) only, so a
palette change is made in the CMS and everything below follows it.
The two tokens here are neutrals, not accents.
========================================================================== */
:root {
color-scheme: light;
--es-ink: #1e2229;
--es-line: rgba(30, 34, 41, .14);
--es-navh: 76px;
}
/* ---------- Typography ---------- */
body {
/*font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;*/
color: var(--es-ink);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
/*font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;*/
letter-spacing: -.015em;
line-height: 1.14;
}
.es-display {
letter-spacing: -.025em;
line-height: 1.06;
}
.es-lead {
font-size: clamp(17px, 2.1vw, 21px);
line-height: 1.75;
}
.es-eyebrow {
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .22em;
font-size: clamp(11px, 1.4vw, 13px);
margin-bottom: 1.35rem;
}
/* ---------- Buttons ---------- */
.btn {
/*font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;*/
text-transform: uppercase;
letter-spacing: .14em;
font-size: clamp(12px, 1.5vw, 13px);
padding: .95rem 2.25rem;
border-radius: 0;
border-width: 1px;
-webkit-tap-highlight-color: transparent;
transition: background-color .3s linear, border-color .3s linear, color .3s linear;
}
.btn.btn-sm {
padding: .6rem 1.4rem;
letter-spacing: .12em;
}
.btn:active {
transform: none;
opacity: .9;
}
/* ---------- Section rhythm ---------- */
.es-sec {
padding-top: clamp(3.5rem, 6vw, 5rem);
padding-bottom: clamp(3.5rem, 6vw, 5rem);
}
/* ---------- Hero: viewport-fitted Ken Burns carousel ----------
Aspect ratio drives the shape (taller than wide on phones, wide and short
on desktop) and max-height guarantees it never exceeds the screen once the
fixed navbar is accounted for. --es-navh is measured in js.js.
------------------------------------------------------------------------ */
.es-hero {
position: relative;
isolation: isolate;
width: 100%;
overflow: hidden;
background-color: var(--es-ink);
aspect-ratio: 1 / 1;
max-height: calc(100vh - var(--es-navh));
max-height: calc(100svh - var(--es-navh));
}
@media (min-width: 576px) {
.es-hero {
aspect-ratio: 10 / 7;
}
}
@media (min-width: 768px) {
.es-hero {
aspect-ratio: 16 / 10;
}
}
@media (min-width: 992px) {
.es-hero {
aspect-ratio: 16 / 9;
}
}
@media (min-width: 1200px) {
.es-hero {
aspect-ratio: 2 / 1;
}
}
@media (min-width: 1600px) {
.es-hero {
aspect-ratio: 21 / 9;
}
}
.es-hero .carousel-inner {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: 100%;
}
.es-hero .carousel-item {
height: 100%;
transition-duration: 1.2s;
}
.es-hero .carousel-item.active.carousel-item-left,
.es-hero .carousel-item.active.carousel-item-right {
transition: opacity 0s 1.2s;
}
/* The platform ships Bootstrap 4.0-era carousel CSS with no stacking rules,
so overlapping slides paint in DOM order and reverse transitions hard-cut.
These are the 4.1+ z-index rules, scoped to the hero. */
.es-hero .carousel-item.active,
.es-hero .carousel-item-next.carousel-item-left,
.es-hero .carousel-item-prev.carousel-item-right {
z-index: 1;
}
.es-hero .carousel-item.active.carousel-item-left,
.es-hero .carousel-item.active.carousel-item-right {
z-index: 0;
}
.es-hero-img {
display: block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-size: cover;
background-position: center;
}
/* Ken Burns rides the inner element while Bootstrap fade owns opacity on the
item, keeping the two animated properties on separate compositor layers.
Duration must cover the 4s interval plus both 1.2s fades, plus margin.
Runs at every width. Bootstrap keeps inactive items at display none, so only
the outgoing and incoming slides ever composite, which phones handle fine. */
@media (prefers-reduced-motion: no-preference) {
.es-hero .carousel-item.active .es-hero-img,
.es-hero .carousel-item-next .es-hero-img,
.es-hero .carousel-item-prev .es-hero-img {
animation: esKbIn 8.5s linear both;
}
.es-hero .carousel-item:nth-child(even).active .es-hero-img,
.es-hero .carousel-item:nth-child(even).carousel-item-next .es-hero-img,
.es-hero .carousel-item:nth-child(even).carousel-item-prev .es-hero-img {
animation-name: esKbOut;
}
/* Origins pushed out toward the edges. With the larger scale this is what
turns the zoom into a lateral pan: the further the origin sits from centre
on the X axis, the more the frame travels left or right as it grows. */
.es-hero .carousel-item:nth-child(4n+1) .es-hero-img {
transform-origin: 15% 45%;
}
.es-hero .carousel-item:nth-child(4n+2) .es-hero-img {
transform-origin: 85% 55%;
}
.es-hero .carousel-item:nth-child(4n+3) .es-hero-img {
transform-origin: 22% 68%;
}
.es-hero .carousel-item:nth-child(4n+4) .es-hero-img {
transform-origin: 80% 32%;
}
}
@keyframes esKbIn {
from { transform: scale(1); }
to { transform: scale(1.19); }
}
@keyframes esKbOut {
from { transform: scale(1.19); }
to { transform: scale(1); }
}
/* ---------- Disciplines marquee ----------
Drifts instead of wrapping, which also solves the ugly multi-line wrap at
340px. Edges dissolve with a mask rather than being cut off by a rule. */
.es-marquee {
overflow: hidden;
margin-top: clamp(2.75rem, 5vw, 4.25rem);
-webkit-mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
}
/* Four identical copies on one track, translated by exactly half its width.
At the end of a cycle copies 3 and 4 sit precisely where 1 and 2 started,
so the reset is invisible and the loop never shows a gap. Translating the
track rather than the lists is what makes this hold at any screen width. */
.es-marquee-track {
display: flex;
flex-wrap: nowrap;
width: max-content;
animation: esMarquee 60s linear infinite;
}
.es-disciplines {
list-style: none;
margin: 0;
padding: 0;
flex: 0 0 auto;
display: flex;
flex-wrap: nowrap;
align-items: center;
}
@keyframes esMarquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
.es-marquee-track {
animation: none;
}
}
/* Each item is a small vertical stack: icon above, label below. Column flex
rather than a block with a centred child, so the two stay optically centred
on each other no matter how long the label is.
Spacing now comes from the item's own horizontal padding instead of the
hairline separator that used to sit between items. Padding is applied on
BOTH sides so the gap is identical everywhere including the seam where one
copy of the list hands off to the next, which is what the old trailing-only
separator was solving. */
.es-disciplines li {
display: inline-flex;
flex-direction: column;
align-items: center;
text-align: center;
white-space: nowrap;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
text-transform: uppercase;
letter-spacing: .16em;
font-size: clamp(10px, 1.3vw, 12px);
padding: .3rem .9rem;
opacity: .72;
}
@media (min-width: 768px) {
.es-disciplines li {
padding-left: 1.6rem;
padding-right: 1.6rem;
}
}
/* Accent colour on the glyph only. The label stays ink, so the row still reads
as type with a mark above it rather than as a row of coloured badges. */
.es-disc-icon {
font-size: clamp(15px, 1.9vw, 18px);
line-height: 1;
margin-bottom: .6rem;
color: var(--primary);
}
/* The hairline separator is retired now that the icons carry the rhythm. Kept
as an explicit override rather than deleted, because .es-disciplines markup
also lives in CMS page content and an older copy of it may still be pasted
somewhere; this guarantees it cannot reappear as a stray tick. */
.es-disciplines li::after {
content: none;
}
/* ---------- Three pillars ---------- */
.es-tile {
display: block;
color: inherit;
text-decoration: none;
-webkit-tap-highlight-color: transparent;
}
.es-tile:hover,
.es-tile:focus {
color: inherit;
text-decoration: none;
}
.es-tile-media {
display: block;
position: relative;
overflow: hidden;
width: 100%;
aspect-ratio: 4 / 3;
background-size: cover;
background-position: center;
background-color: var(--es-line);
}
/* Photo layer for the hover grow.
Everything that blocks GPU compositing has been stripped off this subtree,
because each one forces a re-rasterise on every frame of the scale:
1. No filter on this element. Even a static one forces a render surface.
2. No mix-blend-mode on the sibling bloom. A blend mode pulls the whole
group off the compositor fast path, and the bloom is on screen at
exactly the moment the scale is running, so the two fought directly.
3. Transform is the only property that ever animates here. Animating
filter and transform together on one element was the first cause of
chop, the same failure the Ken Burns recipe documents for opacity
plus transform.
Short and decisive beats long and slow: a big move over a short duration
gives stepping far less room to show than a small move drawn out.
Source images are 600x450 against roughly 410px of display width, so
scaling to 1.1 still lands inside the source and will not soften. */
.es-tile-media::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-image: inherit;
background-size: cover;
background-position: center;
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
.es-tile-media::before {
will-change: transform;
transition: transform .14s linear;
}
.es-tile:hover .es-tile-media::before,
.es-tile:focus .es-tile-media::before {
transform: scale(1.1);
}
}
.es-tile-body {
display: block;
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--es-line);
transition: border-color .35s linear;
}
.es-tile-title {
display: block;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-size: clamp(21px, 2.6vw, 27px);
letter-spacing: -.015em;
line-height: 1.2;
margin-bottom: .75rem;
transition: color .35s linear;
}
.es-tile-text {
display: block;
font-size: clamp(14px, 1.8vw, 16px);
line-height: 1.75;
opacity: .8;
margin-bottom: 1.25rem;
}
.es-tile-cta {
display: inline-block;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .16em;
font-size: 12px;
color: var(--primary);
padding-bottom: 3px;
border-bottom: 1px solid transparent;
transition: border-color .35s linear;
}
.es-tile:hover .es-tile-body,
.es-tile:focus .es-tile-body {
border-top-color: var(--primary);
}
.es-tile:hover .es-tile-title,
.es-tile:focus .es-tile-title {
color: var(--primary);
}
.es-tile:hover .es-tile-cta,
.es-tile:focus .es-tile-cta {
border-bottom-color: currentColor;
}
.es-tile:active {
opacity: .88;
}
/* ---------- Contact band ----------
Full-bleed background photo with a scrim over it. The scrim is a neutral,
not a brand colour, so it survives a palette change untouched. */
.es-cta {
position: relative;
background-color: var(--es-ink);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
padding-top: clamp(4.5rem, 8vw, 6.5rem);
padding-bottom: clamp(4.5rem, 8vw, 6.5rem);
}
.es-cta::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: linear-gradient(to bottom, rgba(18, 21, 26, .78), rgba(18, 21, 26, .58));
}
.es-cta .snip {
position: relative;
z-index: 1;
}
/* ---------- Featured projects slider ----------
Slick 1.8.1 is loaded in head.htm and owns touch swipe plus mouse drag. */
.es-projects {
padding-top: clamp(3.5rem, 6vw, 5rem);
padding-bottom: clamp(5.5rem, 9vw, 8rem);
}
.es-projects-slider {
margin-top: clamp(2.5rem, 5vw, 3.5rem);
}
.es-projects .slick-list {
padding: 8px 0;
margin: -8px 0;
}
.es-projects-slider .slick-track {
display: flex !important;
}
.es-projects-slider .slick-slide {
height: auto;
}
.es-projects-slider .slick-slide > div {
height: 100%;
}
.es-project-slide {
height: 100%;
padding: 0 12px;
outline: none !important;
}
/* Before slick initialises, and in the CMS page editor if it never does,
fall back to a plain row so the cards are still visible and editable. */
.es-projects-slider:not(.slick-initialized) {
display: flex;
overflow: hidden;
}
.es-projects-slider:not(.slick-initialized) .es-project-slide {
flex: 0 0 25%;
max-width: 25%;
}
@media (max-width: 991px) {
.es-projects-slider:not(.slick-initialized) .es-project-slide {
flex: 0 0 50%;
max-width: 50%;
}
}
@media (max-width: 575px) {
.es-projects-slider:not(.slick-initialized) .es-project-slide {
flex: 0 0 100%;
max-width: 100%;
}
}
.es-project-card {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
background: #fff;
border: 1px solid var(--es-line);
color: inherit;
text-decoration: none;
-webkit-tap-highlight-color: transparent;
transition: border-color .35s linear, box-shadow .35s linear;
}
.es-project-card:hover,
.es-project-card:focus {
color: inherit;
text-decoration: none;
border-color: var(--primary);
box-shadow: 0 10px 26px rgba(30, 34, 41, .1);
}
.es-project-card:active {
opacity: .95;
}
.es-project-media {
display: block;
position: relative;
}
.es-project-img {
display: block;
width: 100%;
aspect-ratio: 5 / 3;
background-size: cover;
background-position: center;
background-color: var(--es-line);
}
.es-project-body {
display: flex;
flex-direction: column;
flex: 1 1 auto;
padding: 1.5rem;
background: #fff;
}
.es-project-title {
display: block;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-size: clamp(19px, 2.2vw, 22px);
letter-spacing: -.015em;
line-height: 1.25;
margin-bottom: .6rem;
transition: color .35s linear;
}
.es-project-card:hover .es-project-title,
.es-project-card:focus .es-project-title {
color: var(--primary);
}
.es-project-text {
display: block;
flex: 1 1 auto;
font-size: 15px;
line-height: 1.7;
opacity: .8;
margin-bottom: 1.1rem;
}
.es-project-cta {
display: inline-block;
align-self: flex-start;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .16em;
font-size: 12px;
color: var(--primary);
padding-bottom: 3px;
border-bottom: 1px solid transparent;
transition: border-color .35s linear;
}
.es-project-card:hover .es-project-cta,
.es-project-card:focus .es-project-cta {
border-bottom-color: currentColor;
}
/* Slick chrome, restyled to match the rest of the page */
.es-projects .slick-prev,
.es-projects .slick-next {
width: 46px;
height: 46px;
background: #fff;
border: 1px solid var(--es-line);
border-radius: 0;
z-index: 2;
transition: background-color .3s linear, border-color .3s linear;
}
.es-projects .slick-prev:hover,
.es-projects .slick-next:hover,
.es-projects .slick-prev:focus,
.es-projects .slick-next:focus {
background: var(--primary);
border-color: var(--primary);
}
.es-projects .slick-prev:before,
.es-projects .slick-next:before {
color: var(--es-ink);
font-size: 18px;
opacity: 1;
}
.es-projects .slick-prev:hover:before,
.es-projects .slick-next:hover:before,
.es-projects .slick-prev:focus:before,
.es-projects .slick-next:focus:before {
color: #fff;
}
.es-projects .slick-prev {
left: -23px;
}
.es-projects .slick-next {
right: -23px;
}
@media (max-width: 1199px) {
.es-projects .slick-prev {
left: 4px;
}
.es-projects .slick-next {
right: 4px;
}
}
.es-projects .slick-dots {
bottom: -3rem;
}
.es-projects .slick-dots li,
.es-projects .slick-dots li button {
width: 14px;
height: 14px;
padding: 0;
}
.es-projects .slick-dots li {
margin: 0 3px;
}
.es-projects .slick-dots li button:before {
font-size: 8px;
line-height: 14px;
width: 14px;
height: 14px;
color: var(--es-ink);
opacity: .25;
}
.es-projects .slick-dots li.slick-active button:before {
color: var(--primary);
opacity: 1;
}
/* ==========================================================================
POLISH PASS
========================================================================== */
/* Hero gets a soft vignette so the crop never ends on a flat edge.
The z-index below only stays inside the hero because .es-hero declares
isolation: isolate. Without that, .es-hero is position:relative with
z-index:auto, which is NOT a stacking context, so this pseudo-element
escapes into the root context and paints over whatever section follows. */
.es-hero::after {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
pointer-events: none;
background:
radial-gradient(130% 100% at 50% 40%, transparent 58%, rgba(18, 21, 26, .26) 100%),
linear-gradient(to top, rgba(18, 21, 26, .34), rgba(18, 21, 26, 0) 26%);
}
/* ---------- Selection and focus ---------- */
::selection {
background: var(--primary);
color: #fff;
}
a:focus-visible,
.btn:focus-visible,
button:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 3px;
}
/* ---------- Optical typography ----------
Balanced ragging on display type, and no orphan on the long leads. */
h1, h2, h3,
.h1, .h2, .h3,
.es-display,
.es-tile-title,
.es-project-title {
text-wrap: balance;
}
.es-lead,
.es-tile-text,
.es-project-text {
text-wrap: pretty;
}
/* ---------- Imagery ----------
Slider photos sit slightly desaturated and resolve to full colour on the
card being pointed at. These never scale, so filter is free to animate
here. The pillar cards do the opposite: they scale, so their saturation is
held static and the transition lives on transform alone. */
.es-project-img {
filter: saturate(.84);
transition: filter .55s linear;
}
@media (hover: hover) {
.es-project-card:hover .es-project-img,
.es-project-card:focus .es-project-img {
filter: saturate(1.08);
}
}
/* ---------- Pointer-tracked light ----------
A soft bloom in the site's own accent follows the cursor across a photo.
Screen blend means it reads as light falling on the image rather than a
panel laid over it. Coordinates come from js.js; pointer devices only, so
touch is completely unaffected. */
/* Registering these means the browser treats them as real lengths instead of
opaque tokens, so updating them on mousemove no longer forces a broad style
invalidation. js.js writes them onto the image frame rather than the whole
card, which keeps the invalidated subtree down to the two pseudo-elements
and well away from the photo that is mid-transform. */
@property --es-mx {
syntax: '';
inherits: true;
initial-value: 50%;
}
@property --es-my {
syntax: '';
inherits: true;
initial-value: 50%;
}
/* Slider cards only. The pillar cards deliberately have NO bloom: their photo
scales on hover, and a blended overlay sitting on top of a transforming
layer drags the whole group off the compositor fast path. The two effects
cannot share a frame, and the grow won. */
.es-project-media::after {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
opacity: 0;
pointer-events: none;
mix-blend-mode: screen;
transition: opacity .45s linear;
background: radial-gradient(260px circle at var(--es-mx) var(--es-my), color-mix(in srgb, var(--primary-alt) 38%, transparent), transparent 66%);
}
@media (hover: hover) {
.es-project-card:hover .es-project-media::after {
opacity: 1;
}
}
/* ---------- Buttons ---------- */
.es-btn-arrow {
display: inline-block;
margin-left: .7rem;
font-size: .9em;
transition: transform .35s linear;
}
.btn:hover .es-btn-arrow,
.btn:focus .es-btn-arrow {
transform: translateX(5px);
}
/* ---------- Contact band ----------
The photo holds still while the band scrolls over it. Pointer devices at
desktop widths only, because fixed attachment is unreliable on iOS. */
@media (min-width: 992px) and (hover: hover) {
.es-cta {
background-attachment: fixed;
}
}
/* ==========================================================================
SUBPAGES
========================================================================== */
/* ---------- Subpage hero ----------
A short band rather than a viewport-filling hero, shared by every interior
page. Sized with min-height instead of aspect-ratio so it stays shallow on
wide screens and never eats the fold. Ink background underneath, so it
still reads as intentional while the photo is loading. */
.es-subhero {
position: relative;
isolation: isolate;
display: flex;
align-items: center;
min-height: clamp(180px, 21vw, 270px);
padding: clamp(2.5rem, 4.5vw, 3.25rem) 0;
background-color: var(--es-ink);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.es-subhero::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: linear-gradient(to bottom, rgba(18, 21, 26, .74), rgba(18, 21, 26, .54));
}
.es-subhero .snip {
position: relative;
z-index: 1;
}
/* ---------- Core competencies ---------- */
.es-comp-row {
margin-top: clamp(2.75rem, 6vw, 4.5rem);
}
.es-comp-num {
display: block;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 600;
font-size: clamp(30px, 4vw, 44px);
line-height: 1;
letter-spacing: -.02em;
color: var(--primary);
opacity: .55;
margin-bottom: .85rem;
}
.es-comp-title {
font-size: clamp(20px, 2.4vw, 25px);
letter-spacing: -.015em;
line-height: 1.2;
margin-bottom: .85rem;
}
.es-comp-text {
font-size: clamp(14px, 1.8vw, 16px);
line-height: 1.8;
opacity: .82;
}
/* ---------- Team grid ----------
No card chrome. Each member is a square portrait sitting on the section
background with a hairline rule under it, so the grid reads as a contact
sheet rather than a wall of boxes. Source headshots are all square and
centre-framed, so aspect-ratio 1/1 crops nothing. */
.es-team-row {
margin-top: clamp(1rem, 2vw, 1.5rem);
}
.es-member-col {
margin-bottom: clamp(2.5rem, 4.5vw, 3.5rem);
}
.es-member {
display: flex;
flex-direction: column;
height: 100%;
}
.es-member-media {
display: block;
position: relative;
overflow: hidden;
width: 100%;
aspect-ratio: 1 / 1;
background-size: cover;
background-position: center top;
background-color: var(--es-line);
-webkit-tap-highlight-color: transparent;
}
/* Accent rule wipes in across the foot of the portrait. Transform is the only
property animating, on its own layer, so it stays smooth. Deliberately no
scale on the photo itself: these are faces, and growing a headshot on hover
crops the chin. */
.es-member-media::after {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 3px;
background: var(--primary);
transform: scaleX(0);
transform-origin: left center;
transition: transform .18s linear;
}
.es-member:hover .es-member-media::after,
.es-member:focus-within .es-member-media::after {
transform: scaleX(1);
}
.es-member-media:active {
opacity: .9;
}
.es-member-body {
display: flex;
flex-direction: column;
flex: 1 1 auto;
margin-top: 1.35rem;
padding-top: 1.35rem;
border-top: 1px solid var(--es-line);
transition: border-color .35s linear;
}
.es-member:hover .es-member-body,
.es-member:focus-within .es-member-body {
border-top-color: var(--primary);
}
.es-member-name {
font-size: clamp(15px, 2.6vw, 23px);
letter-spacing: -.015em;
line-height: 1.2;
margin-bottom: .5rem;
}
.es-member-name a {
color: inherit;
text-decoration: none;
transition: color .3s linear;
}
.es-member:hover .es-member-name a,
.es-member-name a:focus {
color: var(--primary);
text-decoration: none;
}
.es-member-cred {
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .14em;
font-size: clamp(9px, 1.2vw, 11px);
line-height: 1.5;
color: var(--primary);
margin-bottom: .4rem;
}
.es-member-role {
font-size: clamp(12px, 1.5vw, 15px);
line-height: 1.55;
opacity: .78;
margin-bottom: 1rem;
}
/* margin-top auto pins the link row to the bottom so it lines up across a
row of cards whose roles wrap to different numbers of lines. */
.es-member-links {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: auto;
}
.es-member-cta {
display: inline-block;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .12em;
font-size: clamp(10px, 1.3vw, 12px);
white-space: nowrap;
color: var(--primary);
padding-bottom: 3px;
border-bottom: 1px solid transparent;
transition: border-color .3s linear;
}
.es-member:hover .es-member-cta,
.es-member-cta:focus {
color: var(--primary);
text-decoration: none;
border-bottom-color: currentColor;
}
.es-member-social {
font-size: 16px;
line-height: 1;
color: var(--es-ink);
opacity: .45;
padding: .35rem;
margin: -.35rem;
transition: color .3s linear, opacity .3s linear;
}
.es-member-social:hover,
.es-member-social:focus {
color: var(--primary);
opacity: 1;
text-decoration: none;
}
/* Two portraits per row all the way down to 340px, so everything below the
photo has to work in roughly 140px of width. The type is already fluid;
these just close up the vertical spacing to match. */
@media (max-width: 575px) {
.es-member-col {
margin-bottom: 2.25rem;
}
.es-member-body {
margin-top: 1rem;
padding-top: 1rem;
}
.es-member-name {
margin-bottom: .35rem;
}
.es-member-role {
margin-bottom: .75rem;
}
.es-member-social {
font-size: 14px;
}
}
/* ---------- Expertise and service ----------
Five alternating photo and text rows. The photo is a background-image on a
div that HOLDS the numeral, so it is never an empty element and survives
the CMS editor, and the crop stays fixed regardless of the source ratio.
That matters here because the source photos run anywhere from 720x370 to
2000x1330; an img tag would leave every row a different height. */
.es-svc-sub {
font-size: clamp(15px, 1.8vw, 17px);
line-height: 1.75;
opacity: .78;
margin-top: 1.5rem;
text-wrap: pretty;
}
.es-x-row {
margin-top: clamp(2.75rem, 5.5vw, 4.5rem);
}
@media (min-width: 992px) {
.es-x-flip {
flex-direction: row-reverse;
}
}
.es-x-media {
position: relative;
width: 100%;
aspect-ratio: 4 / 3;
background-size: cover;
background-position: center;
background-color: var(--es-line);
}
@media (min-width: 992px) {
.es-x-media {
aspect-ratio: 3 / 2;
}
}
/* Numeral sits on the photo rather than beside the copy, so the text column
stays clean and the 01 to 05 device from the About page carries over. */
.es-x-num {
position: absolute;
top: 0;
left: 0;
display: block;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 600;
font-size: clamp(15px, 1.7vw, 17px);
line-height: 1;
letter-spacing: .04em;
color: #fff;
background: var(--primary);
padding: .7rem .95rem;
}
.es-x-body {
margin-top: 1.75rem;
}
@media (min-width: 992px) {
.es-x-body {
margin-top: 0;
padding-left: clamp(1.5rem, 3vw, 3rem);
}
.es-x-flip .es-x-body {
padding-left: 15px;
padding-right: clamp(1.5rem, 3vw, 3rem);
}
}
.es-x-title {
font-size: clamp(21px, 2.6vw, 28px);
letter-spacing: -.015em;
line-height: 1.2;
margin-bottom: 1rem;
}
.es-x-text {
font-size: clamp(14px, 1.8vw, 16px);
line-height: 1.8;
opacity: .84;
margin-bottom: 1.1rem;
text-wrap: pretty;
}
/* The old page prefixed these with a literal arrow character. A hairline in
the accent does the same job without leaning on a glyph. */
.es-x-examples {
position: relative;
padding-left: 2.25rem;
font-size: clamp(13px, 1.6vw, 15px);
line-height: 1.7;
font-style: italic;
opacity: .7;
}
.es-x-examples::before {
content: '';
position: absolute;
top: .78em;
left: 0;
width: 1.5rem;
height: 1px;
background: var(--primary);
}
/* ---------- Supporting services ---------- */
.es-sup-row {
margin-top: clamp(2rem, 4vw, 3rem);
}
.es-sup-col {
margin-top: clamp(1.75rem, 3vw, 2.5rem);
}
.es-sup {
padding-top: 1.35rem;
border-top: 2px solid var(--primary);
}
.es-sup-title {
font-size: clamp(17px, 2vw, 21px);
letter-spacing: -.015em;
line-height: 1.25;
margin-bottom: .7rem;
}
.es-sup-text {
font-size: clamp(14px, 1.7vw, 16px);
line-height: 1.75;
opacity: .82;
text-wrap: pretty;
}
/* ---------- Contact ----------
Form in the wide column so it lands first on a phone, details behind a
hairline on the right from lg up. No map, and the address is deliberately
plain text rather than a link. */
.es-contact-lead {
margin-top: 1.35rem;
margin-bottom: clamp(2rem, 4vw, 2.75rem);
}
.es-contact-aside {
margin-top: clamp(3rem, 5vw, 3.5rem);
}
@media (min-width: 992px) {
.es-contact-aside {
margin-top: 0;
padding-left: clamp(1.75rem, 3vw, 3rem);
border-left: 1px solid var(--es-line);
}
/* Sticky rides an inner wrapper rather than the column itself. If the
column were the sticky element it would collapse to its own content
height and the hairline on its leading edge would stop short of the
form. Left as a stretched flex item, the rule runs the full height and
the details slide within it. */
.es-contact-sticky {
position: sticky;
top: calc(var(--es-navh) + 2rem);
}
}
.es-contact-line {
font-size: clamp(14px, 1.7vw, 16px);
line-height: 1.7;
opacity: .84;
}
/* Phone and email get real size because they are the two things somebody
lands on this page to find. */
.es-contact-strong {
display: inline-block;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-size: clamp(17px, 2.1vw, 21px);
letter-spacing: -.01em;
line-height: 1.35;
word-break: break-word;
color: var(--es-ink);
border-bottom: 1px solid var(--es-line);
padding-bottom: 2px;
transition: color .3s linear, border-color .3s linear;
}
.es-contact-strong:hover,
.es-contact-strong:focus {
color: var(--primary);
border-bottom-color: var(--primary);
text-decoration: none;
}
/* The full social set is emitted and the CMS hides whichever are not set on
the backend, so this has to survive anywhere from one icon to nine. Gap
plus wrap rather than margins, so a wrapped row keeps even spacing. */
.es-contact-socials {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .5rem;
}
.es-social {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-size: 16px;
color: var(--es-ink);
border: 1px solid var(--es-line);
-webkit-tap-highlight-color: transparent;
transition: background-color .3s linear, border-color .3s linear, color .3s linear;
}
.es-social:hover,
.es-social:focus {
background: var(--primary);
border-color: var(--primary);
color: #fff;
text-decoration: none;
}
/* ---------- CMS form ----------
The form itself is rendered by the CUSTFORM shortcode, so this only
restyles what the platform outputs: square fields, hairline borders, the
same Jost button treatment as the rest of the site. */
.es-form .form-group {
margin-bottom: 1rem;
}
.es-form .form-control {
height: auto;
padding: .9rem 1.1rem;
font-size: 15px;
line-height: 1.5;
color: var(--es-ink);
background: #fff;
border: 1px solid var(--es-line);
border-radius: 0;
box-shadow: none;
transition: border-color .3s linear;
}
.es-form textarea.form-control {
min-height: 140px;
resize: vertical;
}
.es-form .form-control:focus {
border-color: var(--primary);
box-shadow: none;
}
.es-form .form-control::placeholder {
color: var(--es-ink);
opacity: .5;
}
.es-form label {
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .14em;
font-size: 11px;
margin-bottom: .5rem;
}
.es-form .oms-captcha .form-control {
background: transparent;
}
.es-form .req,
.es-form .formbuilder-required {
color: var(--primary);
}
.es-form .btn {
margin-top: .5rem;
}
/* ---------- Full-bleed tagline band ----------
Same scrim recipe as the homepage contact band, but with no buttons. */
.es-band {
position: relative;
background-color: var(--es-ink);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
padding-top: clamp(4rem, 7vw, 6rem);
padding-bottom: clamp(4rem, 7vw, 6rem);
}
.es-band::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: linear-gradient(to bottom, rgba(18, 21, 26, .72), rgba(18, 21, 26, .55));
}
.es-band .snip {
position: relative;
z-index: 1;
}
.es-band-eyebrow {
color: #fff;
opacity: .75;
}
.es-band-line {
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 300;
font-size: clamp(24px, 3.6vw, 42px);
letter-spacing: -.02em;
line-height: 1.15;
color: #fff;
}
@media (min-width: 992px) and (hover: hover) {
.es-band {
background-attachment: fixed;
}
}
/* ---------- Testimonials ----------
Attribution in its own column, quote in the other, hairline between each.
Same editorial two-column rhythm as the bio pages, no cards. */
.es-quote {
padding-top: clamp(2.5rem, 5vw, 3.75rem);
padding-bottom: clamp(2.5rem, 5vw, 3.75rem);
border-top: 1px solid var(--es-line);
}
.es-quote:first-child {
padding-top: 0;
border-top: 0;
}
.es-quote:last-child {
padding-bottom: 0;
}
/* On wide screens the attribution rides along beside a long quote instead of
sitting marooned at the top of it. */
@media (min-width: 992px) {
.es-quote-meta {
position: sticky;
top: calc(var(--es-navh) + 2rem);
align-self: flex-start;
}
}
.es-quote-mark {
display: block;
font-size: clamp(20px, 2.6vw, 26px);
line-height: 1;
color: var(--primary);
opacity: .5;
margin-bottom: 1.1rem;
}
.es-quote-name {
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-size: clamp(17px, 2vw, 20px);
letter-spacing: -.01em;
line-height: 1.25;
margin-bottom: .4rem;
}
.es-quote-role {
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .14em;
font-size: clamp(10px, 1.2vw, 11px);
line-height: 1.6;
opacity: .62;
}
.es-quote-body {
margin-top: 1.5rem;
}
@media (min-width: 992px) {
.es-quote-body {
margin-top: 0;
}
}
.es-quote-text {
margin: 0;
padding: 0;
border: 0;
font-size: clamp(15px, 1.9vw, 18px);
font-weight: 300;
line-height: 1.85;
text-wrap: pretty;
}
.es-quote-text p {
margin-bottom: 1.25rem;
}
/* ---------- Team member bio ----------
Shared by all eleven bio pages. Only the portrait URL and the copy change,
so a new bio is a copy of this markup with the text swapped. */
/* The band grows to hold the portrait, so the shared min-height is released
and the padding does the sizing instead. */
.es-bio-hero {
min-height: 0;
padding-top: clamp(2.5rem, 5vw, 4rem);
padding-bottom: clamp(2.5rem, 5vw, 4rem);
}
.es-bio-head {
display: flex;
flex-direction: column;
align-items: flex-start;
}
@media (min-width: 576px) {
.es-bio-head {
flex-direction: row;
align-items: center;
}
}
/* A real img, not a background-image div. An empty div gets stripped by the
CMS editor, so the portrait vanished; an img element survives.
Held small on purpose. Source headshots are 373px square, so capping the
frame at 172px keeps them above 2x on a retina screen. Enlarging them
past roughly 190px is where they start to go soft. The base CSS forces
height:auto!important on content images, which is fine here because the
sources are square already. */
.es-bio-portrait {
display: block;
flex: 0 0 auto;
width: clamp(104px, 24vw, 172px);
max-width: 100%;
background-color: rgba(255, 255, 255, .12);
box-shadow: 0 0 0 1px rgba(255, 255, 255, .28);
margin-bottom: 1.5rem;
}
@media (min-width: 576px) {
.es-bio-portrait {
margin-bottom: 0;
margin-right: clamp(1.5rem, 3.5vw, 2.5rem);
}
}
.es-bio-cred {
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .18em;
font-size: clamp(10px, 1.3vw, 12px);
color: #fff;
opacity: .95;
margin-top: .9rem;
}
.es-bio-role {
font-size: clamp(15px, 1.9vw, 18px);
font-weight: 300;
line-height: 1.5;
color: #fff;
opacity: .8;
margin-top: .35rem;
}
/* ---------- Bio body ----------
Shared with the project detail pages, which want exactly the same
reading rhythm. Aliased here rather than duplicated so the two page
types can never drift apart. */
.es-bio-lead,
.es-proj-lead {
font-size: clamp(16px, 2vw, 19px);
line-height: 1.8;
margin-bottom: 1.5rem;
text-wrap: pretty;
}
.es-bio-text,
.es-proj-text {
font-size: clamp(15px, 1.8vw, 17px);
line-height: 1.85;
opacity: .86;
margin-bottom: 1.35rem;
text-wrap: pretty;
}
/* Facts stack under the bio on phones and sit alongside it from lg up, where
a hairline on the leading edge separates the two columns without a box. */
.es-bio-aside {
margin-top: clamp(2.75rem, 5vw, 3.5rem);
}
@media (min-width: 992px) {
.es-bio-aside {
margin-top: 0;
padding-left: clamp(1.75rem, 3vw, 2.75rem);
border-left: 1px solid var(--es-line);
}
}
.es-fact {
margin-bottom: 2.25rem;
}
.es-fact-last {
margin-bottom: 0;
}
.es-fact-title {
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .18em;
font-size: clamp(11px, 1.3vw, 12px);
line-height: 1.5;
color: var(--primary);
padding-bottom: .85rem;
margin-bottom: .95rem;
border-bottom: 1px solid var(--es-line);
}
.es-fact-list {
list-style: none;
margin: 0;
padding: 0;
}
.es-fact-list li {
position: relative;
font-size: clamp(13px, 1.6vw, 15px);
line-height: 1.6;
opacity: .84;
padding-left: 1rem;
margin-bottom: .5rem;
}
/* Square tick rather than a bullet glyph, so the list reads as a spec sheet
and lines up with the hairline vocabulary used everywhere else. */
.es-fact-list li::before {
content: '';
position: absolute;
top: .62em;
left: 0;
width: 5px;
height: 1px;
background: var(--primary);
opacity: .7;
}
.es-fact-list li:last-child {
margin-bottom: 0;
}
/* ---------- Published articles ----------
One flat list flowed into CSS columns, so the titles read top to bottom in
each column and the browser balances the split. break-inside on the items
is what stops a long title being torn across the column boundary. */
.es-pub-row {
margin-top: clamp(2.25rem, 4.5vw, 3.25rem);
}
.es-pub-list {
list-style: none;
margin: 0;
padding: 0;
column-count: 1;
column-gap: clamp(2rem, 4vw, 3.5rem);
}
@media (min-width: 768px) {
.es-pub-list {
column-count: 2;
}
}
@media (min-width: 992px) {
.es-pub-list {
column-count: 3;
}
}
.es-pub-list li {
position: relative;
break-inside: avoid;
-webkit-column-break-inside: avoid;
page-break-inside: avoid;
font-size: clamp(13px, 1.6vw, 15px);
line-height: 1.6;
opacity: .82;
padding-left: 1.1rem;
padding-bottom: .7rem;
margin-bottom: .7rem;
border-bottom: 1px solid var(--es-line);
}
.es-pub-list li::before {
content: '';
position: absolute;
top: .62em;
left: 0;
width: 5px;
height: 1px;
background: var(--primary);
opacity: .7;
}
/* Footnote under a column list, for the asterisks marking previous-firm work. */
.es-list-note {
margin-top: clamp(1.25rem, 2.5vw, 1.75rem);
font-size: clamp(12px, 1.4vw, 13px);
line-height: 1.6;
opacity: .6;
}
/* ---------- Back to the team ---------- */
/* Sits under the project list on most bios, and directly under the two
columns on the ones with no list, so it needs real separation either way. */
.es-bio-back-row {
padding-top: clamp(2rem, 4vw, 3rem);
}
.es-bio-back {
display: inline-block;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .14em;
font-size: clamp(11px, 1.3vw, 12px);
color: var(--primary);
padding-bottom: 3px;
border-bottom: 1px solid transparent;
transition: border-color .3s linear;
}
.es-bio-back:hover,
.es-bio-back:focus {
color: var(--primary);
text-decoration: none;
border-bottom-color: currentColor;
}
/* ---------- Portfolio grid ----------
Deliberately plain: a photo, a hairline, a label. No card chrome, because
thirty boxed cards in a row reads as a spreadsheet rather than a body of
work. The hover treatment is the same one the homepage pillars use, so
the two grids feel like the same site.
A real img here rather than a background-image div. aspect-ratio plus
object-fit gives every tile an identical crop even though the source
photos run from square to wide panorama, and the base rule that forces
height:auto on content images is satisfied rather than fought: with a
definite width and an explicit aspect-ratio, auto resolves to the ratio.
An img also carries alt text and lazy loading, which matters at thirty
images on one page. */
.es-work-row {
margin-top: clamp(2rem, 4vw, 3rem);
}
.es-work-col {
margin-bottom: clamp(2rem, 4vw, 3rem);
}
.es-work {
display: flex;
flex-direction: column;
height: 100%;
color: inherit;
text-decoration: none;
-webkit-tap-highlight-color: transparent;
}
.es-work:hover,
.es-work:focus {
color: inherit;
text-decoration: none;
}
.es-work:active {
opacity: .9;
}
.es-work-media {
display: block;
position: relative;
overflow: hidden;
width: 100%;
background-color: var(--es-line);
}
.es-work-img {
display: block;
width: 100%;
height: auto;
aspect-ratio: 4 / 3;
object-fit: cover;
object-position: center;
filter: saturate(.86);
transition: filter .45s linear;
}
/* Colour resolves on the tile being pointed at. Nothing here transforms, so
animating filter is free; the pillar cards do the opposite because they
scale and cannot afford it. */
@media (hover: hover) {
.es-work:hover .es-work-img,
.es-work:focus .es-work-img {
filter: saturate(1.06);
}
}
.es-work-body {
display: block;
flex: 1 1 auto;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--es-line);
transition: border-color .35s linear;
}
.es-work:hover .es-work-body,
.es-work:focus .es-work-body {
border-top-color: var(--primary);
}
.es-work-title {
display: block;
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-size: clamp(13px, 1.9vw, 19px);
letter-spacing: -.01em;
line-height: 1.3;
text-wrap: pretty;
transition: color .3s linear;
}
.es-work:hover .es-work-title,
.es-work:focus .es-work-title {
color: var(--primary);
}
/* Two tiles per row down to 340px, so the label works in roughly 145px of
width. Only the spacing needs closing up; the type is already fluid. */
@media (max-width: 575px) {
.es-work-col {
margin-bottom: 1.75rem;
}
.es-work-body {
margin-top: .8rem;
padding-top: .8rem;
}
}
/* ---------- Project detail ----------
Shared by all thirty project pages. Only the h1, the five spec values
and the narrative paragraphs change between them. */
/* Eyebrow doubles as the way back to the portfolio, which is where people
look for it on a detail page. Sits on the dark subhero, so it is white
with the accent reserved for the hover. */
.es-subhero-back {
color: #fff;
opacity: .8;
padding-bottom: 3px;
border-bottom: 1px solid transparent;
transition: opacity .3s linear, border-color .3s linear;
}
.es-subhero-back:hover,
.es-subhero-back:focus {
color: #fff;
opacity: 1;
text-decoration: none;
border-bottom-color: currentColor;
}
/* Taller hero on project pages only, because here the photo IS the
project rather than generic site furniture, so it earns real height.
A modifier on .es-subhero rather than a change to it, the same way
.es-bio-hero modifies the shared band for the team bios. Every other
subpage keeps the shallow version.
min-height and padding are both raised: min-height governs on wide
screens where the title is one line, padding governs on a phone where
a long project name wraps to three or four and the band has to grow
past the minimum anyway. */
.es-proj-hero {
min-height: clamp(260px, 30vw, 420px);
padding-top: clamp(4rem, 7vw, 6rem);
padding-bottom: clamp(4rem, 7vw, 6rem);
}
/* Lighter scrim than the shared band. On the other subpages the photo is
backdrop and the heading is the point, so it can take a heavy wash.
Here the photo is the finished building somebody came to look at, so
it gets to show through. Still graded darker at the top than the
bottom, which is where the title sits and where the contrast has to
hold. Later in the file than .es-subhero::before at equal specificity,
so this wins by source order. */
.es-proj-hero::before {
background: linear-gradient(to bottom, rgba(18, 21, 26, .58), rgba(18, 21, 26, .36));
}
/* Data sheet, not pills. Nothing on this site carries a border radius, so
a rounded pill would be the only one and would read as imported. A 2px
accent rule over each value is the same device the supporting services
block uses on the Expertise page.
CSS grid rather than Bootstrap columns, because the spec count varies
from roughly two to eight across the thirty projects. A fixed column
count per breakpoint means items keep the same width no matter how many
there are, a short final row simply sits left aligned under the others,
and adding or removing a spec needs no change to any class. Equal-width
flex columns would instead stretch a short final row across the whole
container, so a single leftover spec would span full width.
Steps 2 / 3 / 4 / 5. Five at xl because five specs is the common case
across the thirty projects, so the usual page lands on a single row.
Four is held at lg because five tracks in a 960px container leaves each
value too narrow to carry a sentence like the project size line. */
.es-spec {
padding-top: clamp(2.5rem, 5vw, 3.5rem);
padding-bottom: 0;
}
.es-spec-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: clamp(1.5rem, 3vw, 2.25rem) clamp(1.25rem, 2.5vw, 2rem);
}
@media (min-width: 768px) {
.es-spec-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (min-width: 992px) {
.es-spec-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
@media (min-width: 1200px) {
.es-spec-grid {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
/* minmax(0, 1fr) rather than 1fr so a long unbroken value cannot force a
track wider than its share and push the grid out of the container. */
.es-spec-item {
padding-top: 1.1rem;
border-top: 2px solid var(--primary);
}
.es-spec-label {
font-family: 'Jost', 'Century Gothic', -apple-system, sans-serif;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .18em;
font-size: clamp(10px, 1.2vw, 11px);
line-height: 1.5;
color: var(--primary);
margin-bottom: .55rem;
}
/* Values run from "2024" to a full sentence about square footage, so this
has to sit happily at either length. No balance or truncation: a spec
that gets clipped is worse than a spec that wraps. */
.es-spec-value {
font-size: clamp(13px, 1.6vw, 16px);
line-height: 1.6;
opacity: .86;
text-wrap: pretty;
}
/* Narrative reads as a continuation of the specs rather than a separate
band, so the two are pulled close: the spec section carries no bottom
padding at all and this supplies the whole gap. The CMS gallery lands
immediately after the closing section. */
.es-proj {
padding-top: clamp(1.75rem, 3vw, 2.25rem);
padding-bottom: 0;
}
/* Sized between the site h4 and h3 rather than on the scale exactly. A
full h3 competes with the project name in the hero directly above it,
and this only has two words to carry. Ink rather than the accent,
matching every other section heading on the site. */
.es-proj-heading {
font-size: clamp(24px, 3vw, 31px);
letter-spacing: -.015em;
line-height: 1.2;
margin-bottom: 1.35rem;
}
/* In-narrative subheading, for the longer project write-ups that come
broken into titled sections rather than running as plain paragraphs.
Deliberately well below .es-proj-heading so "The Project" still reads
as the owner of everything under it: this is a third level, not a
sibling. The generous top margin is what separates one section from
the next, so no rules or spacers are needed between them. */
.es-proj-sub {
font-family: 'Jost', sans-serif;
font-size: clamp(17px, 2.1vw, 20px);
letter-spacing: -.005em;
line-height: 1.3;
margin-top: clamp(2rem, 3.5vw, 2.75rem);
margin-bottom: .85rem;
text-wrap: pretty;
}
/* First subheading after the lead paragraph does not need the full gap;
the lead's own bottom margin has already opened the space. */
.es-proj-lead + .es-proj-sub,
.es-proj-heading + .es-proj-sub {
margin-top: clamp(1rem, 2vw, 1.5rem);
}
/* Bulleted spec lists inside the narrative. Markers are drawn rather
than inherited from list-style so they can take the accent colour and
sit on the cap height of the first line instead of floating. */
.es-proj-list {
list-style: none;
padding-left: 0;
margin-top: 0;
margin-bottom: 1.35rem;
}
.es-proj-list li {
position: relative;
padding-left: 1.35rem;
font-size: clamp(15px, 1.8vw, 17px);
line-height: 1.85;
opacity: .86;
margin-bottom: .35rem;
text-wrap: pretty;
}
.es-proj-list li:last-child {
margin-bottom: 0;
}
.es-proj-list li::before {
content: "";
position: absolute;
left: 0;
top: .78em;
width: 8px;
height: 1px;
background-color: var(--primary);
}
/* Optional project video. aspect-ratio rather than the old padding-top
56.25% hack: same 16:9 result, but the height is declared where it is
read rather than hidden in a percentage, and it survives having the
ratio changed for a vertical or square upload.
Ink underneath so the frame reads as intentional while Vimeo loads. */
.es-video-row {
margin-top: clamp(2rem, 4vw, 3rem);
}
.es-video {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
background-color: var(--es-ink);
}
.es-video iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
/* ==========================================================================
NO ROUNDED CORNERS, SITE WIDE
Deliberately the last rule in the file so nothing can override it.
Every corner on this site is square. The three that were not have been
zeroed at source above (.round, the blog detail card), but Bootstrap rounds
plenty of things on its own that no class of ours ever touches: form
controls, cards, badges, alerts, modals, dropdowns, list groups, pagination,
toasts, popovers, image thumbnails. The CMS can inject any of those into a
page at any time, so listing them one by one would leave a gap the moment a
new widget is used.
The universal selector is the only way to close that gap permanently.
!important is required, not lazy: a universal selector has zero specificity,
so without it every Bootstrap class rule would win and this would do nothing
at all.
Safe here because nothing on this site wants a radius. Checked: no
.rounded-circle, no .rounded-pill, no Bootstrap .rounded anywhere in the
markup or in any CMS page content. If a circular avatar is ever needed, give
it its own class and add an exception below rather than weakening this rule.
========================================================================== */
*,
*::before,
*::after {
border-radius: 0 !important;
}
/* ===== OMNIA STUDIO THEME =====
Generated by Studio > Site Theme. Edit in that panel, not by hand.
settings:{"primary":"#5e6e8c","secondary":"#bbbbbb","primaryalt":"#5e6e8c","link":"","bodytext":"","headings":"","pagebg":"","fontHead":"","fontBody":"","headWeight":"","radius":-1}
===== */
:root { --primary: #5e6e8c; --secondary: #bbbbbb; --primary-alt: #5e6e8c; }
a { color: #5e6e8c; }
a:hover { color: #4b5870; }
.btn-primary { background-color: #5e6e8c; border-color: #5e6e8c; color: #fff; }
.btn-primary:hover, .btn-primary:focus, .btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, .show > .btn-primary.dropdown-toggle { background-color: #53617b; border-color: #4f5c76; color: #fff; }
.btn-primary:focus, .btn-primary:not(:disabled):not(.disabled):active:focus { box-shadow: 0 0 0 0.2rem rgba(94, 110, 140, 0.4); }
.btn-outline-primary { color: #5e6e8c; border-color: #5e6e8c; }
.btn-outline-primary:hover, .btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active { background-color: #5e6e8c; border-color: #5e6e8c; color: #fff; }
.btn-outline-primary:focus { box-shadow: 0 0 0 0.2rem rgba(94, 110, 140, 0.4); }
.badge-primary { background-color: #5e6e8c; color: #fff; }
.bg-primary { background-color: #5e6e8c !important; }
.text-primary { color: #5e6e8c !important; }
a.text-primary:hover, a.text-primary:focus { color: #4b5870 !important; }
.border-primary { border-color: #5e6e8c !important; }
.progress-bar { background-color: #5e6e8c; }
.page-link { color: #5e6e8c; }
.page-item.active .page-link { background-color: #5e6e8c; border-color: #5e6e8c; }
.dropdown-item.active, .dropdown-item:active { background-color: #5e6e8c; }
.list-group-item.active { background-color: #5e6e8c; border-color: #5e6e8c; }
.nav-pills .nav-link.active, .nav-pills .show > .nav-link { background-color: #5e6e8c; }
.custom-control-input:checked ~ .custom-control-label::before { background-color: #5e6e8c; border-color: #5e6e8c; }
.form-control:focus { border-color: #96a1b4; box-shadow: 0 0 0 0.2rem rgba(94, 110, 140, 0.25); }
.btn-secondary { background-color: #bbbbbb; border-color: #bbbbbb; color: #212529; }
.btn-secondary:hover, .btn-secondary:focus, .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, .show > .btn-secondary.dropdown-toggle { background-color: #a5a5a5; border-color: #9d9d9d; color: #212529; }
.btn-secondary:focus, .btn-secondary:not(:disabled):not(.disabled):active:focus { box-shadow: 0 0 0 0.2rem rgba(187, 187, 187, 0.4); }
.btn-outline-secondary { color: #bbbbbb; border-color: #bbbbbb; }
.btn-outline-secondary:hover, .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active { background-color: #bbbbbb; border-color: #bbbbbb; color: #212529; }
.badge-secondary { background-color: #bbbbbb; color: #212529; }
.bg-secondary { background-color: #bbbbbb !important; }
.text-secondary { color: #bbbbbb !important; }
.border-secondary { border-color: #bbbbbb !important; }
.btn-primary-alt { background-color: #5e6e8c; border-color: #5e6e8c; color: #fff; }
.btn-primary-alt:hover, .btn-primary-alt:focus, .btn-primary-alt:not(:disabled):not(.disabled):active, .btn-primary-alt:not(:disabled):not(.disabled).active { background-color: #53617b; border-color: #4f5c76; color: #fff; }
.btn-outline-primary-alt { color: #5e6e8c; border-color: #5e6e8c; }
.btn-outline-primary-alt:hover, .btn-outline-primary-alt:not(:disabled):not(.disabled):active, .btn-outline-primary-alt:not(:disabled):not(.disabled).active { background-color: #5e6e8c; border-color: #5e6e8c; color: #fff; }
.badge-primary-alt { background-color: #5e6e8c; color: #fff; }
.bg-primary-alt { background-color: #5e6e8c !important; }
.text-primary-alt { color: #5e6e8c !important; }
.border-primary-alt { border-color: #5e6e8c !important; }
/* ===== END OMNIA STUDIO THEME ===== */
/* PAGE SPECIFIC */
/* FOOTER */
