/* =============================================================================
   CORPORATE PAGES
   assets/css/page-corporate.css

   The section types that About, Work and Contact use, and nothing else.
   theme.css stays the source of truth for the shared system: colour, type,
   spacing, motion, .card, .grid-2, .grid-3, .btn and .cta-panel all come from
   there and are not repeated here.

   Loaded after theme.css, and only by the three corporate pages. The WordPress
   theme enqueues it the same way, on the same condition.

     01. Section variants
     02. Team cards (About)
     03. Prose panels (About)
     04. Work filters and cards (Work)
     04c. Image lightbox (Work)
     05. Contact layout, form and map (Contact)
     06. Blog index and article (Blog)

   The body carries page-corporate plus the page's own name, so the template
   knows which of the sections below are in play. The names are declared here
   rather than only in markup, so a page can never reference a class the
   stylesheet has not heard of.

   Section order, a subset of the canonical order in service-page.css:
     hero -> team -> panels -> work grid -> enquiry -> cta -> posts -> article
   ========================================================================== */

.page-about,
.page-work,
.page-contact,
.page-blog {
  /* Named for the template to branch on. Each page sets the same rhythm, so
     the rule is the shared statement of that. */
  --section-pad: 96px;
}

/* =============================================================================
   01. SECTION VARIANTS
   The three page-level section treatments the corporate pages need. Each is a
   modifier on .section, so a page can take any combination.
   ========================================================================== */

.section-hero {
  padding-block: calc(var(--section-pad) * 0.72) calc(var(--section-pad) * 0.86);
  text-align: center;
}

/* A hero is the page's opening statement and the section that follows it is
   where the content starts, so the gap between them is set here rather than
   left to two full section paddings meeting. */
.page-blog #posts {
  padding-top: calc(var(--section-pad) * 0.34);
}

/* About and Contact carry stacked bands under their hero, so the two gaps
   that meet there add up. Each is pulled in on its own: the hero's bottom
   padding and the next section's top padding are both set, because between
   them they were the whole of a 173px hole above content that then had a
   second one below it before the closing band. */
.page-about .section-hero,
.page-contact .section-hero {
  padding-block: calc(var(--section-pad) * 0.72) calc(var(--section-pad) * 0.34);
}

.page-about #leadership {
  padding-top: 0;
}

.page-contact #enquiry {
  padding-top: 0;
  padding-bottom: calc(var(--section-pad) * 0.6);
}

/* The Work page's hero already carries the page's opening rhythm, and the
   grid that follows it takes no top padding at all. Two full section
   paddings in a row put roughly 180px between the H1 and the first card,
   which reads as two separate pages rather than one. Its hero takes the
   same tightened bottom padding as the other corporate pages, so the six
   project cards start at the same distance below their heading. */
.page-work .section-hero {
  padding-block: calc(var(--section-pad) * 0.72) calc(var(--section-pad) * 0.34);
}

.page-work #work {
  padding-top: 0;
}

@media (max-width: 760px) {
  .page-work #work {
    padding-top: 0;
  }
}

/* A quieter band between two loud ones. Used for the audience panels so the
   cards lift off something rather than sitting flat on the page background. */
.section-tint {
  background: var(--bg-2);
  border-block: 1px solid var(--line);
}

/* The closing band carries the cta-panel, so it needs no background of its
   own and sits closer to the panel than a full section would. */
.section-cta {
  padding-block: var(--section-pad) calc(var(--section-pad) * 0.9);
}

/* The closing band has no background of its own, so it reads as a continuation
   of the section above rather than as a new one. Two full section paddings
   meeting put 192px of unbroken page background between the last card and the
   panel, which is the largest single gap on these pages and carries nothing.
   The band above it already supplies the break. */
.page-about #cta,
.page-work #cta,
.page-blog #cta {
  padding-top: calc(var(--section-pad) * 0.42);
}

/* About and Contact get the tighter closing band too. About's closing band
   follows a full-width audience panel and Contact's follows the form, and on
   both pages the gap above the panel is the largest one left on the page. */
.page-about #cta,
.page-contact #cta {
  padding-top: calc(var(--section-pad) * 0.28);
}

@media (max-width: 760px) {
  .section-hero {
    padding-block: 56px 64px;
  }
}

/* =============================================================================
   02. TEAM CARDS (About)
   A photo above a text block. The photo keeps a 1:1 ratio so three portraits
   line up across the grid, and the text block below is a fixed minimum height
   so the three cards end level.

   A biography arrives in two parts: the short one always shows, and the rest
   sits behind a View more that expands in place. The card therefore has two
   heights, and the section reads as a row of introductions until a reader asks
   for more. Nothing is removed from the DOM either way, so the full text is
   still indexed and Find in page still reaches it.
   ========================================================================== */

.team-grid {
  align-items: stretch;
}

.team-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.team-photo {
  aspect-ratio: 1;
  overflow: hidden;
  background: var(--icon-bg);
  flex: none;
}

.team-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.team-body {
  padding: 24px 22px 26px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;
}

.team-body h3 {
  font-size: var(--fs-h3-card);
  line-height: var(--lh-h3);
}

/* The role sits between the name and the biography, in the same lime the
   section kicker uses, so a role reads at a glance when scanning three cards. */
.team-role {
  font-size: var(--fs-tag);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--lime);
}

.team-body p {
  font-size: var(--fs-card-p);
  line-height: 1.62;
  color: var(--text-2);
}

.team-body strong {
  color: var(--text);
}

/* The biography wrapper. It is a block so the expanded part can push the rest
   of the card down in document flow rather than overlaying it, which is what
   "expands the container at the same place" means: the card grows and the
   grid reflows around it. */
.team-bio {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* The expanded part is hidden by the collapsed state, not by display:none on
   the element itself, so a stylesheet that never loads still leaves the whole
   biography readable. */
.team-bio-full {
  display: none;
}

.team-bio.is-open .team-bio-full {
  display: block;
}

.team-bio-full p + p,
.team-bio-short + .team-bio-full p {
  margin-top: 12px;
}

.team-more {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  align-self: flex-start;
  margin-top: 2px;
  padding: 0;
  font: inherit;
  font-size: var(--fs-link);
  font-weight: 600;
  color: var(--cyan);
  background: none;
  border: 0;
  cursor: pointer;
}

.team-more:hover {
  color: var(--text);
}

/* The label is its own element because it is the control's accessible name,
   and site.js swaps its text when the block opens. It is a span rather than a
   bare text node so the swap has something to write into. */
.team-more-label {
  display: inline;
}

.team-more:focus-visible {
  outline: var(--focus);
  outline-offset: var(--focus-offset);
  border-radius: var(--radius-xs);
}

.team-more svg {
  width: 16px;
  height: 16px;
  transition: transform var(--t-fast);
}

/* Open, the chevron points up and the label offers the way back. */
.team-more[aria-expanded="true"] svg {
  transform: rotate(180deg);
}

@media (max-width: 900px) {
  .team-body {
    padding: 20px 18px 22px;
  }
}

/* =============================================================================
   03. PROSE PANELS (About)
   The who-we-serve and how-we-engage cards. A heading, then one or two short
   paragraphs that each open with a bolded lead-in.
   ========================================================================== */

.panel {
  padding: 28px 26px 30px;
  height: 100%;
}

.panel h3 {
  font-size: var(--fs-h3-why);
  margin-bottom: 14px;
}

.panel p {
  font-size: var(--fs-card-p);
  line-height: 1.62;
  color: var(--text-2);
}

.panel p + p {
  margin-top: 12px;
}

.panel strong {
  color: var(--text);
  font-weight: 600;
}

/* =============================================================================
   04. WORK FILTERS AND CARDS (Work)
   The filter row and the project grid it drives. The filter is a real
   disclosure of what it controls: it carries aria-pressed, and the empty
   message below the grid is the live region for "no match".

   A work card is a link to the project's own case study, so the whole card is
   one target rather than a title link and a decorative image link. The thumb is
   still an <a>, held out of the tab order and hidden from assistive tech, so
   the two do not announce the same destination twice.
   ========================================================================== */

.work-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  margin-bottom: var(--section-head-gap);
}

.filter-btn {
  font: inherit;
  font-size: var(--fs-link);
  font-weight: 600;
  color: var(--text-2);
  background: var(--card);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-pill);
  padding: 9px 20px;
  cursor: pointer;
  transition:
    color var(--t-fast),
    background var(--t-fast),
    border-color var(--t-fast);
}

.filter-btn:hover {
  color: var(--text);
  border-color: var(--teal);
}

.filter-btn.is-active {
  color: var(--on-dark);
  background: var(--teal);
  border-color: var(--teal);
}

.filter-btn:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

/* A discipline with no projects behind it. It stays readable so the row still
   describes the whole practice, and it is dimmed rather than removed so its
   presence is not mistaken for a broken page. */
.filter-btn:disabled {
  cursor: not-allowed;
  opacity: 0.42;
}

.filter-btn:disabled:hover {
  color: var(--text-2);
  border-color: var(--line-2);
}

/* The count beside each filter, so a visitor can see what a click will leave
   behind before clicking it. It is written by initWorkFilter() from the cards
   themselves, which means it cannot disagree with what the filter shows. */
.filter-count {
  display: inline-block;
  margin-left: 7px;
  font-size: var(--fs-tag);
  font-variant-numeric: tabular-nums;
  opacity: 0.72;
}

.filter-btn.is-active .filter-count {
  opacity: 0.9;
}

.work-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
  align-items: stretch;
}

.work-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* The containing block for the title link's stretched ::after. Without this
     the pseudo-element is bounded by .work-body, so the thumbnail stays dead
     and only the text half of the card is clickable. */
  position: relative;
}

/* The title link is the card's real control, so it carries the card's hit
   area. That keeps one tab stop and one accessible name for the destination,
   while the visual affordance is the whole card. The thumb link is held out of
   the tab order and hidden from assistive tech, so the destination is not
   announced twice. */
.work-card h3 a::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}

.work-card:focus-within {
  border-color: var(--teal);
}

.work-thumb {
  display: block;
  background: var(--card-2);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}

.work-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  aspect-ratio: 3 / 2;
  transition: transform var(--t-mid);
}

.work-card:hover .work-thumb img {
  transform: scale(1.03);
}

.work-body {
  padding: 20px 22px 22px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
}

/* The disciplines a project sits under. A project can carry two, so this is
   the one place the meta line carries more than one value. It is set quieter
   than the lime the About page uses for a role, because here it is a filter
   label rather than a role, and the project name below is what a visitor is
   scanning for. */
.work-meta {
  font-size: var(--fs-tag);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
}

.work-body h3 {
  font-size: var(--fs-h3-case);
  line-height: var(--lh-h3);
}

.work-body h3 a {
  color: var(--text);
}

.work-card:hover h3 a {
  color: var(--cyan);
}

/* The one-line descriptor from the project record. It is the sentence that
   tells a visitor whether the project is the one they are looking for, and it
   clamps at two lines so every card in a row ends level. */
.work-sum {
  font-size: var(--fs-card-p);
  line-height: 1.6;
  color: var(--text-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-clamp: 2;
  overflow: hidden;
}

.work-tech {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  list-style: none;
  margin: auto 0 0;
  padding: 0;
}

.work-tech li {
  font-size: var(--fs-tag);
  color: var(--text-3);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-pill);
  padding: 3px 10px;
  white-space: nowrap;
}

.work-more {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 4px;
  color: var(--cyan);
  font-size: var(--fs-link);
  font-weight: 600;
}

.work-more svg {
  width: 16px;
  height: 16px;
  transition: transform var(--t-fast);
}

.work-card:hover .work-more svg {
  transform: translateX(4px);
}

/* The page intro under the H1, on the Work index only. */
.work-intro {
  max-width: 62ch;
  margin-inline: auto;
}

/* Set when a filter matches nothing, so the grid does not collapse to a
   blank band with no explanation. */
.work-empty {
  text-align: center;
  color: var(--text-3);
  font-size: var(--fs-body);
  padding: 40px 0;
}

.work-empty[hidden] {
  display: none;
}

@media (max-width: 980px) {
  .work-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 620px) {
  .work-grid {
    grid-template-columns: 1fr;
  }

  .work-sum {
    -webkit-line-clamp: 3;
    line-clamp: 3;
  }
}

/* =============================================================================
   04c. SCREENSHOT GALLERY LIGHTBOX (Work)
   A case study shows one or more screenshots of the product it describes.
   Clicking one opens it full size over a dimmed backdrop, so a detail the page
   scales down is readable, and where a project has more than one the dialog
   steps through them with the arrows.

   The markup is written by initLightbox() in assets/js/site.js, which creates
   the dialog once and fills it from whatever image was clicked. Nothing here is
   server rendered, because a dialog is only ever opened from a click and
   pre-rendering a copy per screenshot would put six hidden dialogs in the page.
   ========================================================================== */

/* The affordance, pinned to the screenshot's top-right corner. On a case study
   the screenshot is the thing being offered, so the control is a real button
   with a name rather than a decoration over a link: it sits above the image
   and takes the click, and the image underneath stays a plain picture. */
.zoom-hint {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--overlay-chip);
  border: 1px solid var(--line-2);
  /* Paired with the fill above. --overlay-chip is a near-white chip in a light
     theme, so --on-dark put a white glyph on a white button here. */
  color: var(--overlay-chip-ink);
  cursor: zoom-in;
  transition:
    background var(--t-fast),
    color var(--t-fast),
    border-color var(--t-fast);
}

.zoom-hint svg {
  width: 20px;
  height: 20px;
}

.zoom-hint:hover {
  background: var(--overlay-chip-hover-bg);
  /* The fill changed, so the ink has to change with it. It stays white in both
     modes, which is the point: the fill is opaque, so the glyph reads the same
     over every screenshot in the gallery instead of depending on the one the
     pointer happens to be over. */
  color: var(--overlay-chip-ink-hover);
  border-color: var(--teal);
}

.zoom-hint:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

/* The affordance stays on the picture rather than fading in on hover. It is
   the only way to open the preview on a case study, and a control that needs a
   hover to appear is a control a phone visitor cannot find: the media queries
   that separate the two cases report differently across browsers and across
   the same browser at different widths. It is a 40px control in the corner of
   a 560px-wide picture, so it covers nothing a visitor is reading. */

/* The dialog itself. It is a fixed overlay with no page chrome, so it sits
   outside .section rhythm entirely.

   The full width and height are stated rather than left to inset:0. A modal
   dialog is centred on its content and sized to it, so inset alone leaves the
   element at the size of the picture inside it. The scrim then covers only as
   much as the screenshot does and the page stays visible around the edges. */
.lightbox {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  max-width: none;
  max-height: none;
  margin: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 28px;
  background: var(--overlay-scrim);
  border: 0;
  opacity: 0;
  transition: opacity var(--t-mid);
}

/* A dialog is display:none until showModal() opens it, which is the only state
   the lightbox should ever be in while closed. */
.lightbox:not([open]) {
  display: none;
}

.lightbox.is-open {
  opacity: 1;
}

.lightbox-figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  max-width: 100%;
  max-height: 100%;
  min-height: 0;
  transform: scale(0.97);
  transition: transform var(--t-mid);
}

.lightbox.is-open .lightbox-figure {
  transform: scale(1);
}

/* The image fills the space the viewport leaves, so a wide screenshot is shown
   whole and a tall one scrolls inside its own column rather than being cut. */
.lightbox-img {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - 140px);
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius);
  background: var(--bg-2);
}

.lightbox-caption {
  font-size: var(--fs-link);
  font-weight: 600;
  color: var(--text);
  text-align: center;
  margin: 0;
}

/* The position readout, which only appears when there is more than one
   screenshot to move between. */
.lightbox-count {
  font-family: var(--font-display);
  font-size: var(--fs-tag);
  font-weight: 600;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  text-align: center;
  margin: 0;
}

.lightbox-count[hidden] {
  display: none;
}

/* The two arrows, one per edge of the picture. They are 48px so they clear the
   44px touch target a phone visitor needs, and they sit above the dismiss
   catcher so a click on an arrow never reaches the backdrop. */
.lightbox-arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  width: 48px;
  height: 48px;
  margin-top: -24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--card);
  border: 1px solid var(--line-2);
  color: var(--text);
  cursor: pointer;
  transition:
    color var(--t-fast),
    border-color var(--t-fast),
    opacity var(--t-fast);
}

.lightbox-arrow:hover {
  /* The button keeps its --card fill, so the glyph takes the page's strongest
     ink. --on-dark was white on a near-white card in a light theme. */
  color: var(--text-strong);
  border-color: var(--teal);
}

.lightbox-arrow svg {
  width: 20px;
  height: 20px;
}

/* At either end of a run there is nowhere to go, so the arrow is dimmed rather
   than removed: a control that vanishes mid-sequence moves the target a
   visitor was reaching for. */
.lightbox-arrow[disabled] {
  opacity: 0.35;
  cursor: default;
}

.lightbox-arrow[disabled]:hover {
  color: var(--text);
  border-color: var(--line-2);
}

.lightbox-prev {
  left: 18px;
}

.lightbox-next {
  right: 18px;
}

.lightbox-hint {
  font-size: var(--fs-tag);
  color: var(--text-3);
  text-align: center;
  margin: 0;
}

/* The close button and the click-outside catcher. One element does both jobs:
   it fills the dialog and sits behind the figure, so pressing Escape, the
   button, or the backdrop all reach the same close handler. */
.lightbox-dismiss {
  position: absolute;
  inset: 0;
  background: none;
  border: 0;
  padding: 0;
  cursor: zoom-out;
}

.lightbox-close {
  position: absolute;
  top: 18px;
  right: 18px;
  z-index: 2;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--card);
  border: 1px solid var(--line-2);
  color: var(--text);
  cursor: pointer;
  transition:
    color var(--t-fast),
    border-color var(--t-fast);
}

.lightbox-close:hover {
  color: var(--text-strong);
  border-color: var(--teal);
}

.lightbox-close svg {
  width: 18px;
  height: 18px;
}

@media (max-width: 620px) {
  .lightbox {
    padding: 16px;
  }

  .lightbox-img {
    max-height: calc(100vh - 120px);
  }

  .lightbox-close {
    top: 10px;
    right: 10px;
  }

  /* The arrows move to the bottom corners on a phone. A screenshot held to
     portrait height leaves no room at the sides, and a thumb reaches the
     bottom of the screen far more easily than the middle of it. */
  .lightbox-arrow {
    top: auto;
    bottom: 14px;
    margin-top: 0;
  }

  .lightbox-prev {
    left: 16px;
  }

  .lightbox-next {
    right: 16px;
  }

  /* The dialog has to keep the whole screenshot readable: the arrows sit over
     the image, so the image is inset by the height they take rather than
     shrinking to fit between them. */
  .lightbox-img {
    max-height: calc(100vh - 150px);
  }
}

/* =============================================================================
   06. BLOG INDEX AND ARTICLE (Blog)

   The index reuses the house insights card from theme.css (.card.post) so a
   post looks the same in the home grid, in this grid and in the related band
   at the foot of an article. What is added here is the tile artwork, the topic
   rail, and the reading layout an article needs.

   An article is the one page on this site that is read rather than scanned, so
   it is the one page with a measure: the prose column is capped, and the table
   of contents sits beside it on a screen wide enough to carry both. Below
   that the contents moves above the prose, because a sticky sidebar on a
   phone is a panel over the text.
   ========================================================================== */

.post-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  margin-bottom: var(--section-head-gap);
}

.post-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
  align-items: stretch;
}

.post-card {
  padding: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative;
}

/* The title link is the card's real control and carries the card's hit area,
   the same contract the work card uses. The thumb link is held out of the tab
   order and hidden from assistive tech so the destination is announced once. */
.post-card h3 a::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}

.post-card:focus-within {
  border-color: var(--teal);
}

.post-thumb {
  display: block;
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}

/* A real featured image sits in the same box as the tile, so a card's height
   does not depend on whether an editor has uploaded one. */
.post-thumb .post-img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 16 / 7;
  object-fit: cover;
  transition: transform var(--t-mid);
}

.post-card:hover .post-thumb .post-img {
  transform: scale(1.03);
}

/* The tile that stands in for a photograph. A post with no image still has to
   look like a card rather than a gap, and a colour band with the topic's
   initial reads as deliberate rather than as a broken image. The colour comes
   from data-art, so a list of them is varied without hardcoding a hex per
   post. */
.post-art {
  aspect-ratio: 16 / 7;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 10px;
  padding: 20px 22px;
  background:
    radial-gradient(120% 140% at 12% 8%, var(--teal) 0%, transparent 62%),
    linear-gradient(160deg, var(--hero-frame-a), var(--hero-frame-b));
}

.post-art[data-art="ai"] {
  background:
    radial-gradient(120% 140% at 88% 12%, var(--teal) 0%, transparent 62%),
    linear-gradient(200deg, var(--hero-frame-a), var(--hero-frame-b));
}

.post-art[data-art="adoption"] {
  background:
    radial-gradient(130% 150% at 50% 110%, var(--teal) 0%, transparent 58%),
    linear-gradient(20deg, var(--hero-frame-a), var(--hero-frame-b));
}

.post-art[data-art="azure"] {
  background:
    radial-gradient(110% 130% at 82% 86%, var(--teal) 0%, transparent 55%),
    linear-gradient(160deg, var(--hero-frame-b), var(--hero-frame-a));
}

.post-art[data-art="cloud"] {
  background:
    radial-gradient(120% 140% at 50% 0%, var(--teal) 0%, transparent 58%),
    linear-gradient(180deg, var(--hero-frame-a), var(--hero-frame-b));
}

.post-art[data-art="data"] {
  background:
    radial-gradient(140% 140% at 18% 82%, var(--teal) 0%, transparent 60%),
    linear-gradient(240deg, var(--hero-frame-a), var(--hero-frame-b));
}

.post-art[data-art="security"] {
  background:
    radial-gradient(130% 130% at 8% 92%, var(--teal) 0%, transparent 60%),
    linear-gradient(140deg, var(--hero-frame-a), var(--hero-frame-b));
}

.post-art[data-art="build"] {
  background:
    radial-gradient(120% 140% at 78% 88%, var(--teal) 0%, transparent 60%),
    linear-gradient(220deg, var(--hero-frame-a), var(--hero-frame-b));
}

.post-art-initial {
  font-family: var(--font-display);
  font-size: 44px;
  font-weight: 800;
  line-height: 1;
  color: var(--lime);
}

.post-art-cat {
  font-size: var(--fs-tag);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--teal-ink);
}

.post-body {
  padding: 20px 22px 22px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
}

.post-cat {
  font-size: var(--fs-tag);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--lime);
}

.post-card h3 {
  font-size: var(--fs-h3-post);
  line-height: var(--lh-h3);
}

.post-card h3 a {
  color: var(--text);
}

.post-card:hover h3 a {
  color: var(--cyan);
}

.post-sum {
  font-size: var(--fs-card-p);
  line-height: 1.6;
  color: var(--text-2);
  flex: 1;
}

.post-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  font-size: var(--fs-label);
  color: var(--text-3);
  margin-top: 4px;
}

.post-foot .link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--cyan);
  font-weight: 600;
}

.post-foot .link svg {
  width: 16px;
  height: 16px;
  transition: transform var(--t-fast);
}

.post-card:hover .post-foot .link svg {
  transform: translateX(4px);
}

/* The date on a card and the date in an article's byline are the same value
   written at two sizes, so they share one treatment here. */
.post-date,
.article-date,
.article-read {
  font-variant-numeric: tabular-nums;
}

/* Set when a topic matches nothing, so the grid does not collapse to a blank
   band with no explanation. */
.post-empty {
  text-align: center;
  color: var(--text-3);
  font-size: var(--fs-body);
  padding: 40px 0;
}

.post-empty[hidden] {
  display: none;
}

/* ------------------------------------------------------------- article --- */

.article-page #article {
  padding-top: calc(var(--section-pad) * 0.42);
}

.article-head {
  max-width: 760px;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

.article-standfirst {
  font-size: var(--fs-body-lg);
  line-height: var(--lh-section-p);
  color: var(--text-2);
  max-width: 66ch;
}

.article-byline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font-size: var(--fs-link);
  color: var(--text-3);
  margin-top: 4px;
}

.article-author {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--text-2);
  font-weight: 600;
}

.article-author-avatar {
  width: 30px;
  height: 30px;
  border-radius: var(--radius-pill);
  background: var(--tag-bg);
  border: 1px solid var(--line-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  /* Same reasoning as .tag: the initials sit on the green tint, so they take
     the strongest brand ink rather than the general body-text green. */
  color: var(--brand-strong);
}

.article-sep {
  color: var(--text-3);
}

/* The reading layout. The prose column is capped at a comfortable measure and
   the contents list takes the space beside it; below 1080px the two stack and
   the contents sits above the text. */
.article-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  gap: 48px;
  align-items: start;
}

.article-layout > .prose {
  grid-column: 1;
}

.article-side {
  grid-column: 2;
  grid-row: 1;
  position: sticky;
  top: calc(var(--nav-h) + 24px);
}

.article-side:empty {
  display: none;
}

.article-toc {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  padding: 20px 20px 22px;
}

.article-toc-title {
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 12px;
}

.article-toc-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  counter-reset: toc;
}

.article-toc-list li {
  counter-increment: toc;
}

.article-toc-list a {
  display: flex;
  gap: 9px;
  font-size: var(--fs-card-p);
  line-height: 1.45;
  color: var(--text-2);
  transition: color var(--t-fast);
}

.article-toc-list a::before {
  content: counter(toc, decimal-leading-zero);
  font-size: var(--fs-tag);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--lime);
  flex: none;
  padding-top: 1px;
}

.article-toc-list a:hover {
  color: var(--text);
}

@media (max-width: 1080px) {
  .article-layout {
    grid-template-columns: 1fr;
    gap: 32px;
  }

  .article-side {
    position: static;
    grid-column: 1;
    grid-row: auto;
  }

  .post-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 700px) {
  .post-grid {
    grid-template-columns: 1fr;
  }

  .article-toc-list {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 8px 16px;
  }

  .article-toc-list a {
    font-size: var(--fs-label);
  }
}

/* The breadcrumb above an article's title, the same trail a case study opens
   with. */
.article-page .crumb {
  margin-bottom: var(--section-head-gap-sm);
}

@media (max-width: 760px) {
  .article-byline {
    gap: 8px;
  }
}

/* =============================================================================
   04b. CASE STUDY PAGE (work-<slug>.html)
   The detail page a work card links to. It is a corporate page, so it wears
   page-corporate.css and the shared .section rhythm, and it adds the case
   hero, the overview split and the delivered list.

   The bands themselves reuse classes the service pages already ship - .pills,
   .deliv-list, .grid-2, .grid-3, .work-grid - so a case study needs no new
   card styling.
   ========================================================================== */

.case-hero {
  padding-block: calc(var(--section-pad) * 0.55) calc(var(--section-pad) * 0.35);
  border-bottom: 0;
}

.case-hero .crumb {
  margin-bottom: var(--section-head-gap-sm);
}

.case-hero-grid {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: 48px;
  align-items: center;
}

.case-hero-text h1 {
  margin-bottom: 14px;
}

.case-hero-text .hero-lead {
  max-width: 46ch;
  margin-bottom: 12px;
  color: var(--text-2);
}

.case-hero-text .hero-lead + .hero-lead {
  margin-top: 0;
}

.case-hero-text .pills {
  margin-bottom: 24px;
}

/* The hero's buttons are a row of actions, not a stacked panel like the one the
   page ends on. `.cta-actions` is a column so the consultation panel stacks its
   button under its copy; inherited unchanged it stretched both buttons to the
   full 565px of the hero's text column, which read as two wide slabs with a
   36-character tagline stranded above them. The row also keeps the secondary
   action beside the primary, where a visitor's eye lands after the sentence
   they came to read. */
.case-hero-text .cta-actions {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
}

/* The row is a desktop shape. At phone width the two labels together exceed the
   column, so the wrap drops them to a stack - where each button takes the full
   width, the way every other stacked button on the site does, rather than
   shrinking to the length of its own label and leaving a ragged left edge.
   `align-self` rather than `align-items`, because stretching both axes would
   make the stack's free height split between the two and grow each button to
   200px tall. */
@media (max-width: 560px) {
  .case-hero-text .cta-actions .btn {
    align-self: stretch;
  }
}

/* The eyebrow repeats the first discipline pill directly beneath it, so the
   same word appears twice in the first two lines of the page. The pills carry
   the whole list and are the one place it is complete, so the eyebrow goes and
   the list moves up into its place. */
.case-hero-text .pills {
  margin-bottom: 16px;
  margin-top: 0;
}

.case-hero-visual {
  overflow: hidden;
}

/* A screenshot is wrapped in a figure so the control that opens it has
   something to sit on top of: an absolutely positioned button needs a
   positioned ancestor, and the image alone has no box of its own here. The
   first screenshot fills the frame; a project with more than one shows the
   rest stacked under it, so the page reads as a gallery rather than as a
   carousel the visitor has to discover. */
.case-hero-visual .shot {
  position: relative;
  margin: 0;
}

.case-hero-visual .shot + .shot {
  border-top: 1px solid var(--line);
}

.case-hero-visual img {
  display: block;
  width: 100%;
  height: auto;
}

/* The overview band: the project name beside its summary, so the summary has
   a generous measure rather than running the full width of the wrap. */
.case-overview {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: 48px;
  align-items: start;
}

.case-overview-body p {
  font-size: var(--fs-body-lg);
  line-height: var(--lh-section-p);
  color: var(--text-2);
}

.case-overview-body p + p {
  margin-top: 16px;
}

/* Ramp-style checks under the hero paragraph. */
.case-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  list-style: none;
  margin: 4px 0 22px;
  padding: 0;
}

.case-chips li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14.5px;
  line-height: 1.4;
  color: var(--text-2);
}

.case-chips svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--lime);
}

/* The product frame beside the hero: a padded stage, not a bare screenshot. */
.case-stage {
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: calc(var(--radius) + 8px);
  padding: 14px;
  box-shadow: var(--shadow-frame);
  overflow: hidden;
}

.case-stage .shot {
  border-radius: var(--radius);
  overflow: hidden;
}

/* Three proof cards. The figure is the large line; the sentence sits above it. */
.case-proofs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
  align-items: stretch;
}

.case-proof {
  display: flex;
  flex-direction: column;
  min-height: 280px;
  padding: 28px 24px 22px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.case-proof h3 {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--text-strong);
  margin-bottom: 10px;
}

.case-proof > p:not(.case-figure) {
  font-size: var(--fs-card-p);
  line-height: 1.6;
  color: var(--text-2);
}

.case-figure {
  margin-top: auto;
  padding-top: 28px;
  font-family: var(--font-display);
  font-size: 48px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--text-strong);
}

.case-figure span {
  display: block;
  margin-top: 10px;
  font-family: inherit;
  font-size: 14.5px;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.45;
  color: var(--text-2);
}

/* Alternating feature rows: copy one side, the product frame the other. */
.case-page #approach {
  padding-block: calc(var(--section-pad) * 0.35);
}

.case-feature {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 56px;
  align-items: center;
  padding-block: 56px;
}

.case-feature + .case-feature {
  border-top: 1px solid var(--line);
}

.case-feature.is-flip .case-feature-copy {
  order: 2;
}

.case-feature-copy .kicker {
  margin-bottom: 10px;
}

.case-feature-copy h2 {
  margin-bottom: 14px;
}

.case-feature-copy > p:not(.kicker) {
  font-size: var(--fs-body-lg);
  line-height: var(--lh-section-p);
  color: var(--text-2);
  max-width: 46ch;
}

.case-feature-stage {
  height: 340px;
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: calc(var(--radius) + 8px);
  padding: 14px;
  box-shadow: var(--shadow-frame);
}

.case-shot,
.case-feature-stage img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius);
}

.case-feature:nth-child(4n + 1) .case-feature-stage img { object-position: center top; }
.case-feature:nth-child(4n + 2) .case-feature-stage img { object-position: left center; }
.case-feature:nth-child(4n + 3) .case-feature-stage img { object-position: right center; }
.case-feature:nth-child(4n + 4) .case-feature-stage img { object-position: center bottom; }

/* Compact grid under the feature rows, the "more" band. */
.case-more {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
}

.case-more-item {
  padding: 22px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.case-more-item .ico {
  margin-bottom: 14px;
}

.case-more-item h3 {
  font-size: var(--fs-h3-post);
  line-height: 1.35;
  color: var(--text-strong);
  margin-bottom: 8px;
}

.case-more-item p {
  font-size: var(--fs-card-p);
  line-height: 1.6;
  color: var(--text-2);
}

.case-note h3 {
  font-size: var(--fs-h3-post);
  margin-bottom: 10px;
}

/* The watermark numeral on a "where we stood out" card. It reuses the numeral
   treatment the service cards use, at a size that stays a label, and it keeps a
   little air under it so it reads as the card's own marker rather than as a
   line of text in the body. */
.case-num {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--lime);
  margin-bottom: 4px;
}

.case-note p:not(.case-num) {
  font-size: var(--fs-card-p);
  line-height: 1.65;
  color: var(--text-2);
}

/* The case page's two plain card grids - the "how it came together" and the
   "where we stood out" bands.

   `.card` in theme.css sets the surface: background, border, radius. Every other
   card type supplies its own padding on top of it, which is the house pattern
   (.svc-card 30px, .rel 26px, .why via --card-pad-why). These two grids supplied
   none, so the heading and the paragraph sat hard against the border at 0px of
   inset. The text looked like it belonged to the page rather than to the card,
   and the three cards of a row were indistinguishable from the band behind them
   - which is what "everything looks the same" amounted to on this page.

   `height: 100%` is what stretches each card to the row's height, so a row of
   three cards with text running to eight, eleven and eight lines still reads as
   three equal cards rather than three ragged boxes. The flex column is what lets
   that stretch happen without the paragraph collapsing. */
.case-page .grid-3 > .card,
.case-page .grid-2 > .card {
  padding: var(--card-pad);
  display: flex;
  flex-direction: column;
  gap: 12px;
  height: 100%;
}

/* The heading is the card's title, so it is sized as one and given room below
   it. It sat flush against the paragraph with no gap at all, which is the other
   half of why the three cards read as one undifferentiated block of text. */
.case-page .grid-3 > .card h3,
.case-page .grid-2 > .card h3 {
  font-size: var(--fs-h3-post);
  line-height: 1.35;
  /* These cards carry the card surface in both modes, so the heading follows
     the page ink. --on-dark was white on a white card in a light theme. */
  color: var(--text-strong);
}

/* The body of these cards is the account of the work, read at length, so it runs
   at the page's prose size and tone rather than the smaller service-card blurb.
   --text on the card is the same ink the overview band's prose uses, which keeps
   the long read on this page at one voice from the summary to the argument. */
.case-page .grid-3 > .card > p,
.case-page .grid-2 > .card > p:not(.case-num) {
  font-size: var(--fs-body);
  line-height: var(--lh-section-p);
  color: var(--text);
}

/* The delivered list is a grid of check items rather than one long column, so
   a six-item list reads as a set of capabilities instead of a wall. */
.case-page .deliv-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px 32px;
  max-width: none;
}

.case-page .deliv {
  align-items: flex-start;
}

/* The technology band holds one short row of pills. At the standard section
   padding it spent 371px of band to show 36px of content, so a reader scrolling
   to the end of a case study met a full screen of near-empty space and a
   heading with a single line under it. The padding comes in to match the bands
   around it, and the pill row is centred with its heading so the two read as
   one block rather than a heading floating over a full-width list. */
.case-page #tech {
  padding-block: calc(var(--section-pad) * 0.62);
}

.case-page #tech .pills {
  justify-content: center;
  margin-bottom: 0;
}

/* The nav is sticky, so any `#anchor` written against a case study's bands -
   from the breadcrumb, from another page, or from a link in post copy - has to
   land below it rather than tuck the band's own heading underneath the header. */
.case-page section[id] {
  scroll-margin-top: calc(var(--nav-h) + 24px);
}

@media (max-width: 900px) {
  .case-hero-grid,
  .case-overview,
  .case-proofs,
  .case-more,
  .case-feature,
  .case-feature.is-flip {
    grid-template-columns: 1fr;
    gap: 22px;
  }

  .case-feature {
    padding-block: 36px;
  }

  .case-feature.is-flip .case-feature-copy {
    order: 0;
  }

  .case-feature-stage {
    height: 220px;
  }

  .case-proof {
    min-height: 0;
  }

  .case-page .deliv-list {
    grid-template-columns: 1fr;
  }
}

/* =============================================================================
   05. CONTACT LAYOUT, FORM AND MAP (Contact)
   The form takes the wider column and the office details the narrower one.
   Below 900px they stack, form first.
   ========================================================================== */

.contact-layout {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: 32px;
  align-items: start;
}

.contact-form-card {
  padding: 30px 30px 28px;
}

.contact-form-card > h2 {
  font-size: var(--fs-h3-post);
  margin-bottom: 6px;
}

.contact-form-card > p {
  font-size: var(--fs-card-p);
  color: var(--text-2);
  margin-bottom: 22px;
}

.contact-aside {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.contact-info {
  padding: 26px 24px 28px;
}

.contact-info h2 {
  font-size: var(--fs-h3-post);
  margin-bottom: 4px;
}

.contact-info h3 {
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-top: 20px;
  margin-bottom: 6px;
}

.contact-info p {
  font-size: var(--fs-card-p);
  color: var(--text-2);
  line-height: 1.6;
}

.contact-info a {
  color: var(--text);
}

.contact-info a:hover {
  color: var(--cyan);
}

.contact-company {
  color: var(--text) !important;
  font-weight: 600;
  margin-bottom: 14px;
}

.contact-info .social {
  margin-top: 22px;
}

.contact-map {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--card);
}

.contact-map img {
  display: block;
  width: 100%;
  height: auto;
}

/* The form. Fields sit in a two-column row, then go full width below 620px,
   which is the width a single name field stops being comfortable. */
.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-bottom: 16px;
}

.field label {
  font-size: var(--fs-label);
  font-weight: 600;
  color: var(--text);
}

.req {
  color: var(--lime);
}

.field input,
.field textarea {
  font: inherit;
  font-size: var(--fs-link);
  color: var(--text);
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-xs);
  padding: 12px 14px;
  transition:
    border-color var(--t-fast),
    box-shadow var(--t-fast);
}

.field input::placeholder,
.field textarea::placeholder {
  color: var(--text-3);
}

.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(46, 118, 131, 0.28);
}

.field textarea {
  resize: vertical;
  min-height: 120px;
  line-height: 1.6;
}

/* The two invalid states, drawn from the same red so a visitor who submits
   an empty form can see every field that needs attention at once. */
.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] {
  border-color: var(--amber);
}

.field .err {
  font-size: var(--fs-tag);
  color: var(--amber);
}

.form-foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  margin-top: 6px;
}

.form-note {
  font-size: var(--fs-tag);
  color: var(--text-3);
  line-height: 1.55;
}

.form-note a {
  /* --cyan rather than --teal-ink: this link sits on the page, not on the teal
     band, and --teal-ink is a near-white meant for copy on the band fill. */
  color: var(--cyan);
  text-decoration: underline;
}

/* The honeypot. It is taken out of the flow and out of sight rather than
   display:none, which some assistive technology and every autofill treats as
   "not a field" and skips - the point is for a bot to find it. It is clipped
   to a pixel rather than hidden, so it costs the form no space. */
.form-hp {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The live region site.js writes to. Empty it renders nothing, so it takes up
   no space until there is something to say. */
.form-status {
  font-size: var(--fs-card-p);
  margin-top: 14px;
  min-height: 0;
}

.form-status:empty {
  display: none;
}

.form-status.is-ok {
  color: var(--lime);
}

.form-status.is-error {
  color: var(--amber);
}

@media (max-width: 900px) {
  .contact-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .form-row {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .contact-form-card {
    padding: 24px 20px 24px;
  }
}

