Add services page and make the portfolio carousel draggable
Port the /services page from the legacy site: hero, "Field of activity" with the five expertise areas, the video feature image that straddles the section boundary, the services slider, the stats row, and the capability lists with the closing "architecture" watermark. Images come from the legacy theme; they are also wired to Meshkee static-image slots (services-hero/feature/cards/gallery) so they can be swapped from the panel. The navbar Services link now points at /services instead of the homepage anchor, and the sitemap picks the route up automatically. PortfolioCarousel takes an optional `items` prop so the new page can reuse it, and now supports pointer dragging. Two fixes were needed for that: - Easing is toggled on the track imperatively with a forced style flush instead of via a class. Chrome decides whether to animate from the before-change style, so a class-based `transition: none` also suppressed the transition on the commit that removed it — the drag snap jumped, and the loop reset (which was driven by onTransitionEnd) never ran at all, letting the track drift off the end of the list. The loop now re-anchors on a timer and the reset hops instead of gliding. - The gesture is tracked on the window rather than through pointer capture, so releasing outside the carousel or losing focus mid-drag still settles it instead of leaving the track parked at its dragged offset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a8454f37f0
commit
6bcbe73d81
+310
-3
@@ -605,20 +605,30 @@ textarea {
|
||||
--gap: 35px;
|
||||
--shown: 4;
|
||||
--slide: 0;
|
||||
/* live pointer-drag offset, written straight to the node while dragging */
|
||||
--drag: 0px;
|
||||
container-type: inline-size;
|
||||
width: calc(100% + 30vw);
|
||||
overflow: hidden;
|
||||
cursor: grab;
|
||||
touch-action: pan-y;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.portfolio-bleed.is-instant .portfolio-track {
|
||||
transition: none;
|
||||
.portfolio-bleed.is-dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* the transition below is switched off imperatively while the track follows the cursor and
|
||||
while the loop re-anchors — see freeze()/thaw() in PortfolioCarousel */
|
||||
.portfolio-track {
|
||||
display: flex;
|
||||
gap: var(--gap);
|
||||
transition: transform 0.7s cubic-bezier(0.12, 0, 0.1, 0);
|
||||
transform: translateX(calc(-1 * var(--slide) * (100cqi + var(--gap)) / var(--shown)));
|
||||
transform: translateX(
|
||||
calc(-1 * var(--slide) * (100cqi + var(--gap)) / var(--shown) + var(--drag))
|
||||
);
|
||||
}
|
||||
|
||||
.portfolio-card {
|
||||
@@ -634,6 +644,7 @@ textarea {
|
||||
height: 430px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
-webkit-user-drag: none;
|
||||
transition: transform 1s cubic-bezier(0.12, 0, 0.39, 0);
|
||||
}
|
||||
|
||||
@@ -1148,6 +1159,217 @@ textarea {
|
||||
box-shadow: 0 25px 70px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
/* services page */
|
||||
|
||||
.service-intro {
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 8fr;
|
||||
gap: 30px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
/* mirrors the blank line the legacy page had between the lead and the body copy */
|
||||
.service-intro p + p {
|
||||
margin-top: 1.875rem;
|
||||
}
|
||||
|
||||
.service-areas {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.service-area {
|
||||
position: relative;
|
||||
padding-top: 56px;
|
||||
}
|
||||
|
||||
.service-area-number {
|
||||
position: absolute;
|
||||
top: -14px;
|
||||
left: -4px;
|
||||
font-family: var(--alt-font);
|
||||
font-size: clamp(66px, 6vw, 96px);
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
-webkit-text-stroke-color: rgba(255, 255, 255, 0.22);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.service-area h3 {
|
||||
position: relative;
|
||||
margin: 0 0 6px;
|
||||
color: var(--white);
|
||||
font-size: 19px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.service-area p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.service-common {
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.service-common p {
|
||||
margin: 6px 0 0;
|
||||
}
|
||||
|
||||
/* the feature image straddles the boundary between the two sections */
|
||||
.service-lead {
|
||||
padding-bottom: 415px;
|
||||
}
|
||||
|
||||
.service-feature-section {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.service-feature {
|
||||
position: relative;
|
||||
margin-top: -325px;
|
||||
}
|
||||
|
||||
.service-feature-image {
|
||||
height: 650px;
|
||||
border-radius: 6px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
box-shadow: inset 0 0 0 100vmax rgba(25, 25, 25, 0.2);
|
||||
}
|
||||
|
||||
/* centred with margin auto rather than a transform, which `.reveal-self > *` resets */
|
||||
.service-play {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
width: 170px;
|
||||
height: 170px;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: var(--base-color);
|
||||
color: var(--dark-gray);
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.service-play:hover {
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.service-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.service-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
padding: 0 20px;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.service-stat:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.service-stat strong {
|
||||
flex-shrink: 0;
|
||||
font-family: var(--alt-font);
|
||||
font-size: clamp(40px, 5vw, 58px);
|
||||
font-weight: 600;
|
||||
line-height: 1.1;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.service-stat strong sup {
|
||||
font-size: 0.52em;
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
.service-stat span {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
line-height: 26px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.service-stars {
|
||||
color: var(--base-color);
|
||||
letter-spacing: 2px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.service-outro-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 8fr 4fr;
|
||||
gap: 30px;
|
||||
align-items: start;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
.service-capabilities {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.service-capabilities ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.service-capabilities li {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.service-capabilities li::before {
|
||||
content: "+";
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--base-color);
|
||||
}
|
||||
|
||||
/* nothing clips this one, so keep it clear of the footer */
|
||||
.service-outro .watermark {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.service-visual {
|
||||
position: relative;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.service-pic-a {
|
||||
display: block;
|
||||
width: 80%;
|
||||
margin-left: auto;
|
||||
margin-top: -20px;
|
||||
box-shadow: 0 25px 70px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.service-pic-b {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
bottom: 10px;
|
||||
width: 50%;
|
||||
z-index: 2;
|
||||
box-shadow: 0 25px 70px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.service-visual.is-in .service-pic-a { transition-delay: 0.2s; }
|
||||
.service-visual.is-in .service-pic-b { transition-delay: 0.45s; }
|
||||
|
||||
.reveal > * {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
@@ -1536,6 +1758,28 @@ textarea {
|
||||
--shown: 3;
|
||||
}
|
||||
|
||||
.service-intro,
|
||||
.service-outro-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.service-feature-image {
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.service-feature {
|
||||
margin-top: -225px;
|
||||
}
|
||||
|
||||
.service-lead {
|
||||
padding-bottom: 315px;
|
||||
}
|
||||
|
||||
.service-play {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
.marquee {
|
||||
letter-spacing: -5px;
|
||||
}
|
||||
@@ -1672,6 +1916,69 @@ textarea {
|
||||
min-height: 320px;
|
||||
padding-top: 130px;
|
||||
}
|
||||
|
||||
.service-areas,
|
||||
.service-stats,
|
||||
.service-capabilities {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.service-areas {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.service-stat {
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
text-align: center;
|
||||
padding: 0 0 28px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.service-stat:last-child {
|
||||
border-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.service-feature-image {
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.service-feature {
|
||||
margin-top: -175px;
|
||||
}
|
||||
|
||||
.service-lead {
|
||||
padding-bottom: 265px;
|
||||
}
|
||||
|
||||
.service-play {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.service-capabilities {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.service-visual {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.service-pic-a,
|
||||
.service-pic-b {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
left: auto;
|
||||
bottom: auto;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 575px) {
|
||||
|
||||
Reference in New Issue
Block a user