/*
 * MinCode overrides, loaded after main.css.
 * Kept separate so the Rayo vendor stylesheets can be replaced wholesale
 * on a template update without losing our changes.
 */

/* ------------------------------------------------ */
/* Palette: one accent, lime                          */
/* ------------------------------------------------ */

/*
 * The Rayo palette pairs a lime (#DDF160) with a purple (#9F8BE7). MinCode uses
 * the lime alone: both the light-scheme accent and the dark-scheme additional
 * are set to it, so every accent slot in either theme is the same colour.
 *
 * Lime is a light colour, and that decides where it may go:
 *   - as a background, only under dark text — #DDF160 + #161616 is about 15:1.
 *     The "text on lime" rules further down use the fixed dark token for that.
 *   - as text or an outline on the light theme's pale ground, never — about
 *     1.2:1. Those cases fall back to the dark text colour (see below).
 *
 * Overridden here rather than edited in main.css so the vendor stylesheet can
 * still be replaced wholesale; the scheme blocks alias these source tokens.
 */
:root {
  --accent--light: #DDF160;
  --accent-rgb--light: 221, 241, 96;

  --additional--dark: #DDF160;
  --additional-rgb--dark: 221, 241, 96;

  /* Only used for a drop shadow behind an accent button. */
  --pt-purple-rgb: 221, 241, 96;
  --pt-purple-dark-rgb: 88, 97, 24;
}

/*
 * `.btn-additional` and `.tag-additional` take --t-bright, which is white in the
 * dark scheme — 2.7:1 on the template's purple already, and 2.9:1 on the orange.
 * Both surfaces this slot can hold are light colours, in either scheme, so the
 * text on them cannot follow the theme: it is the permanent dark. Measured
 * after: 6.94:1 on the orange, 14.5:1 on the lime.
 */
.btn-additional,
.tag-additional {
  color: var(--pt-t-dark-bright);
}

/*
 * The template styles headings by element only, so matching h6's look meant
 * writing <h6> directly after an <h2> — a heading-order break (WCAG 2.4.6,
 * axe "heading-order"). This mirrors the h6 rules as a class, so markup can
 * use the correct level and still look right.
 */
.h6 {
  font: normal var(--fw-medium) 2.2rem/1.2 var(--_font-accent);
  color: var(--t-bright);
}

.h6 a {
  color: var(--t-bright);
}

.h6.opposite,
.h6.opposite a {
  color: var(--t-opp-bright);
}

@media only screen and (min-width: 1200px) {
  .h6 {
    font-size: 3rem;
  }
}

/* Same reasoning as .h6 — used by the blog author card. */
.h5 {
  font: normal var(--fw-medium) 2.6rem/1.2 var(--_font-accent);
  color: var(--t-bright);
}

.h5 small {
  display: block;
  font: normal var(--fw-regular) 1.6rem/1.2 var(--_font-accent);
  color: var(--t-medium);
  margin-top: 0.2rem;
}

.h5 a {
  color: var(--t-bright);
}

.h5.opposite,
.h5.opposite a {
  color: var(--t-opp-bright);
}

@media only screen and (min-width: 768px) {
  .h5 {
    font-size: 3.4rem;
  }
}

/*
 * The template highlights inline runs with a bare <span> ("p span", "li span").
 * Markdown has no way to emit that — **bold** becomes <strong> — so mirror the
 * same treatment onto <strong>, keeping the semantics Markdown intends.
 */
p strong,
.mxd-article__content ol li strong,
.mxd-article__content ul li strong {
  font-weight: var(--fw-semibold);
  color: var(--t-bright);
}

/*
 * Article chapters are <h2> so the document outline runs h1 -> h2 without a skip
 * (the design used <h6>-style jumps). The template sizes h2 for section titles,
 * which is far too large inside prose, so match the h3 scale the design used.
 */
.mxd-article__content h2 {
  font-size: 3.6rem;
}

@media only screen and (min-width: 768px) {
  .mxd-article__content h2 {
    font-size: 5rem;
  }
}

/*
 * The author's role moved out of the <h2> — a heading containing both a link and
 * plain text makes the link indistinguishable (WCAG 1.4.1). It therefore no longer
 * inherits `h5 small`, so restate that look here.
 */
.mxd-article-author__position {
  display: block;
  font: normal var(--fw-regular) 1.6rem/1.4 var(--_font-accent);
  color: var(--t-medium);
  margin-top: 0.2rem;
}

/*
 * The 404's headline. The template scopes its text utilities to <p> (`p.t-large`,
 * `p.t-120`, …), so the design's plain paragraphs could not simply become an <h1>
 * without losing every style. The page needs a level-one heading, so mirror those
 * declarations for the heading inside the error caption.
 */
.mxd-error__caption h1 {
  font-family: var(--_font-accent);
  font-size: 2.5rem;
  font-weight: var(--fw-medium);
  line-height: 1.2;
  letter-spacing: normal;
  color: var(--t-bright);
}

@media only screen and (min-width: 1600px) {
  .mxd-error__caption h1 {
    font-size: 3rem;
  }
}

/* ------------------------------------------------ */
/* Inline icons (replacing the Phosphor webfonts)     */
/* ------------------------------------------------ */

/*
 * The template sized icons as text, through the font-size they inherited.
 * 1em keeps every existing rule (.btn i, .mxd-links-lines__icon i, …) working
 * unchanged, now that the glyphs are SVG.
 */
.mxd-icon-wrap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  font-style: normal;
}

.mxd-icon {
  width: 1em;
  height: 1em;
  flex: none;
  fill: currentColor;
}

/*
 * Nothing renders in the Phosphor families any more. Stated explicitly so the
 * browser can never be asked for those font files.
 */
.ph,
.ph-bold,
.ph-fill,
.ph-light,
.ph-thin,
.ph-duotone {
  font-family: inherit !important;
}

/* ------------------------------------------------ */
/* Header controls on small screens                   */
/* ------------------------------------------------ */

/*
 * Below 640px the header keeps only the theme toggle and the hamburger. The
 * "Say Hello" link goes: at that width the template has already collapsed it to
 * an icon-only circle, so it reads as a second unlabelled button next to the
 * toggle, and Contact is one tap away in the menu and again in the footer.
 */
@media only screen and (max-width: 640px) {
  .mxd-header__controls .btn-mobile-icon {
    display: none;
  }

  /* Now the only control beside the hamburger, so give it the room. */
  .mxd-color-switcher {
    width: 5.6rem;
    height: 5.6rem;
  }

  .mxd-color-switcher i {
    font-size: 2.6rem;
  }
}

/* ------------------------------------------------ */
/* Mobile: stop decorative layers scrolling the page  */
/* ------------------------------------------------ */

/*
 * At 390px the marquee rows and the CTA's background panel reach a few pixels past
 * the viewport, which is enough to make the whole page scroll sideways on a phone.
 * `clip` rather than `hidden`: it trims the overflow without creating a scroll
 * container, so the template's ScrollTrigger pinning keeps working.
 */
html,
body {
  overflow-x: clip;
}

@media only screen and (max-width: 767px) {
  .marquee,
  .mxd-promo,
  .mxd-promo__inner {
    overflow-x: clip;
  }
}

/*
 * Service names on /services/ are <h2>, not <p> (see ServicesList.astro). The
 * template scopes this type ramp to `.mxd-services-list__title p`, so restate it
 * for the heading — identical size, weight and tracking.
 */
.mxd-services-list__title h2 {
  font: normal var(--fw-medium) 4.4rem/0.7 var(--_font-accent);
  letter-spacing: -0.06rem;
  color: var(--t-bright);
}

@media only screen and (min-width: 1200px) {
  .mxd-services-list__title h2 {
    font-size: 9rem;
  }
}

@media only screen and (min-width: 1600px) {
  .mxd-services-list__title h2 {
    font-size: 12rem;
  }
}

/* ------------------------------------------------ */
/* Hero title: two lines, at every width              */
/* ------------------------------------------------ */

/*
 * The template's hero type is 60px at 390px, which fits roughly eleven characters
 * per line — "Custom software" alone wrapped onto two lines, making the title
 * three lines before the second row even started. Scaling it with the viewport
 * below the tablet breakpoint keeps the title to two lines on a phone without
 * touching the desktop sizes the design was drawn at.
 */
/*
 * Each <em> of the hero title stays on a single line, so the title is two lines
 * total instead of three.
 *
 * Measured: "automation, stores, apps, sites" needs ~14.5× the font size in width,
 * and the title column is ~90% of the viewport. That puts the ceiling at 22px on a
 * 390px phone and 90px at 1440 — a constant ratio, so one fluid value covers every
 * width. 5.5vw sits under the ceiling everywhere with room to spare.
 */
.hero-05-title {
  font-size: clamp(2rem, 5.5vw, 11rem);
  line-height: 1.05;
}

/* ------------------------------------------------ */
/* Header logo on small screens                       */
/* ------------------------------------------------ */

/*
 * The design widened .mxd-logo__image from the template's 4.6rem square mark to
 * 20rem, because MinCode's logo is a 129×24 wordmark rather than an icon. That
 * fits from the tablet breakpoint up, but at 390px a 200px logo plus the 102px
 * header controls overflowed the header and the "Say Hello" button collided with
 * the hamburger by 13px. Scaling the wordmark with the viewport below 768px keeps
 * the desktop size the design asked for and gives the controls room on a phone.
 */
@media only screen and (max-width: 767px) {
  .mxd-logo__image {
    width: clamp(11rem, 34vw, 20rem);
  }
}

/* ------------------------------------------------ */
/* Hero: stacked, centred variant                     */
/* ------------------------------------------------ */

/*
 * The design puts the trust badge in a narrow column beside the title. Stacked,
 * it sits on its own row underneath and both are centred — centring only the
 * badge under a left-aligned title reads as a mistake rather than a choice.
 */
.hero-05--stacked .hero-05-title {
  align-items: center;
  text-align: center;
}

.hero-05--stacked .hero-05-title__row {
  justify-content: center;
}

.hero-05--stacked .hero-05-headline__avatars {
  display: flex;
  justify-content: center;
  margin-top: 3.2rem;
}

.hero-05--stacked .mxd-avatars-group {
  flex-direction: row;
  align-items: center;
  justify-content: center;
}

.hero-05--stacked .mxd-avatars-group__text {
  text-align: left;
}

/*
 * On a phone the avatars leave the text only ~200px of the 330px row, so
 * "Trusted by 40 delivered projects" wrapped. Stacking gives the sentence the
 * full width and keeps it on one line.
 */
@media only screen and (max-width: 767px) {
  .hero-05--stacked .mxd-avatars-group {
    flex-direction: column;
    gap: 1.6rem;
  }

  .hero-05--stacked .mxd-avatars-group__text {
    text-align: center;
  }

  .hero-05--stacked .mxd-hero-rating {
    justify-content: center;
  }
}

/*
 * Star rating under the trust text. Colour matches the template's own testimonial
 * ratings (--t-bright) rather than the purple --additional, which reads as a
 * different kind of accent and sits close to the SaaS-purple the brief rules out.
 */
.mxd-hero-rating {
  display: flex;
  gap: 0.6rem;
  margin-top: 0.6rem;
}

.mxd-hero-rating .mxd-icon-wrap {
  font-size: 1.6rem;
  color: var(--t-bright);
}

@media only screen and (min-width: 768px) {
  .mxd-hero-rating .mxd-icon-wrap {
    font-size: 1.8rem;
  }
}

/* Requested breathing room above the hero. */
.mxd-hero-section {
  padding-top: 8rem;
}

/* ------------------------------------------------ */
/* hero-06: the headline is the marquee               */
/* ------------------------------------------------ */

/*
 * The template hides .hero-06-headline__title with `opacity: 0; visibility: hidden`
 * — in this hero the marquee is the visible headline and the <h1> is there for
 * structure only. But `visibility: hidden` also removes it from the accessibility
 * tree: checked against the AX tree, the page exposed six h2s and no h1 at all.
 * Automated audits miss this because the element is still in the DOM.
 *
 * Clip instead: invisible on screen, still announced.
 */
.hero-06-headline__title {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
  opacity: 1;
  visibility: visible;
}

/* ------------------------------------------------ */
/* hero-06 video: give it its own compositor layer    */
/* ------------------------------------------------ */

/*
 * The hero video carries border-radius over object-fit: cover. Without a layer of
 * its own the compositor re-clips those rounded corners on every decoded frame,
 * which is what makes the scroll animations stutter once the video is playing —
 * measured: transform none, will-change auto, so the element was never promoted.
 *
 * Promoting it caches the clip; the video then composites as a plain texture.
 */
.mxd-hero-06__video video,
.menu-promo__video video {
  transform: translateZ(0);
  will-change: transform;
  backface-visibility: hidden;
  /* Dark ground under the video: nothing white can flash through before the
     poster paints, on a page this dark. */
  background-color: var(--base-tint);
}

/* ------------------------------------------------ */
/* Parallax layers: promote what Ukiyo transforms     */
/* ------------------------------------------------ */

/*
 * Ukiyo animates `transform` on .parallax-img / .parallax-img-small every scroll
 * frame. Measured mid-scroll, those elements had `transform: matrix(...)` but
 * `will-change: auto` — never promoted, so each frame repainted a 627×720
 * background image instead of just moving a cached layer.
 *
 * Through the Case studies grid four or five of them are on screen at once, which
 * is exactly the stretch where the reveal animations stuttered. Promoting them
 * turns the repaint into a composite.
 *
 * Deliberately narrow: only the elements Ukiyo actually drives (nine on the
 * homepage), not every image — a layer per element is not free.
 */
.parallax-img,
.parallax-img-small {
  will-change: transform;
}

/* ------------------------------------------------ */
/* No preloader, no entrance delay                    */
/* ------------------------------------------------ */

/*
 * The template gates the whole page behind a percentage counter, then fades the
 * content in with delays of 0.8s and 3.2s. That is the "still loading" feeling —
 * the markup is ready long before anything is visible.
 *
 * The #loader element stays in the DOM only because app.js reads it at top level
 * (see Loader.astro); it is never shown.
 */
#loader {
  display: none !important;
}

/*
 * GSAP sets these to opacity 0 inline and animates them in. A stylesheet
 * !important outranks a normal inline style, so the content is simply visible
 * from the first paint — no fade, no slide, no delay.
 */
.loading__item,
.loading__fade {
  opacity: 1 !important;
  transform: none !important;
  visibility: visible !important;
}

/* ------------------------------------------------ */
/* Partner logo grid                                  */
/* ------------------------------------------------ */

/*
 * The row is a <ul> so the count is announced and the group can be skipped.
 * Bootstrap's .row does not reset list styling, so do it here.
 */
.mxd-partners-list {
  list-style: none;
  margin-bottom: 0;
  padding-left: 0;
}

/* ------------------------------------------------ */
/* Footer: two columns                                */
/* ------------------------------------------------ */

/*
 * The template's footer is three equal thirds. MinCode has no social accounts,
 * so the third column went; the navigation keeps the width it had and the
 * contact column takes the space the socials used to occupy.
 */
@media only screen and (min-width: 1200px) {
  .footer-blocks__column:first-child {
    -ms-flex: 1;
        flex: 1;
  }

  .footer-blocks__column:last-child {
    -ms-flex: 2;
        flex: 2;
  }

  /*
   * The template pins the columns to 650px and lays both cards out with
   * `space-between`, so the leftover height became one big gap — 171px between
   * the navigation and the legal links, 134px between the subscribe title and
   * its form. Let the columns follow their content instead, and set those two
   * gaps explicitly at roughly 2.5× less.
   */
  .footer-blocks__column {
    height: auto;
  }

  .footer-blocks__column:first-child .fullheight-card,
  .footer-blocks__card.notify {
    -ms-flex-pack: start;
        justify-content: flex-start;
  }

  .footer-blocks__column:first-child .fullheight-card {
    gap: 6.8rem;
  }

  .footer-blocks__card.notify {
    gap: 5.4rem;
  }
}

/* Four contact cards, two per row once there is room for two. */
.footer-contacts {
  display: flex;
  flex-direction: column;
  gap: 3rem;
}

@media only screen and (min-width: 768px) {
  .footer-contacts {
    flex-direction: row;
    flex-wrap: wrap;
  }

  .footer-contacts__item {
    flex: 1 1 calc(50% - 1.5rem);
    min-width: 0;
  }
}

/*
 * The template sizes these at 2.2rem, which fits "hello@min-code.com" on a 390px
 * phone with 6px to spare — and breaks "contact@min-code.com" across two lines at
 * the hyphen, so it reads "contact@min-" / "code.com". Measured: the value has
 * about 0.54 of the viewport to work with and needs roughly 11× its font size,
 * which caps it at 4.7vw. Above ~470px the template size takes over again.
 */
@media only screen and (max-width: 767px) {
  .footer-contacts__item .mxd-point-subtitle {
    font-size: clamp(1.6rem, 4.7vw, 2.2rem);
  }
}

/* Two links in one card, separated by a slash. */
.footer-messengers {
  flex-wrap: wrap;
}

.footer-messengers__sep {
  color: var(--t-muted);
}

/* Privacy/Terms, then the copyright under them — see the note in SiteFooter. */
.footer-blocks__foot {
  display: flex;
  flex-direction: column;
  gap: 4.6rem;
}

/*
 * Out of `.footer-blocks__links` now, so it no longer inherits that rule's flex
 * row — and Astro drops the whitespace between the name and the © glyph, which
 * left them touching. Restate the row here.
 */
.footer-copyright {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
}

/* Service description on the homepage. */
.mxd-pinned__text-item p {
  font-size: 2.4rem;
}

/* ------------------------------------------------ */
/* Rotating badge outside the hero                    */
/* ------------------------------------------------ */

/*
 * `ver-04` is the hero's badge, and the hero sits on a video, so the template
 * hardcodes `color: var(--nl-white)` and an off-white border. The same badge in
 * the Case studies grid sits on the page surface, where white disappears in the
 * light theme. Restate both from theme tokens; the geometry stays the hero's.
 */
.mxd-projects-masonry .btn-rotating-blur-outline.ver-04 {
  color: var(--t-bright);
  border-color: var(--st-bright);
}

/* Larger ring text than the hero's, on this badge only. */
.mxd-projects-masonry .btn-rotating-blur-outline.ver-04 .btn-rotating__text text {
  font-size: 2.05rem;
}

/*
 * The ring path is a circle of radius 69.7 inside a 160×160 viewBox, so its top
 * sits 10.3 units from the edge — less than a capital letter is tall. The glyphs
 * stand outward from the path, and an SVG clips at its viewBox by default, which
 * shaved the tops off the letters at the top of the circle ("PR" of PROJECT).
 * Measured: the text box overruns the viewBox by 10.9 units on every side at
 * 2.05rem, and by 7.2 at the hero's 1.74rem — so both were clipped, the hero
 * just less visibly. Nothing else is drawn there; let the letters paint.
 */
.btn-rotating__text {
  overflow: visible;
}

/*
 * The template pins the badge to the bottom edge of the grid. Lift it clear.
 * Only from 1200px up — below that the group is in normal flow, not absolute.
 */
@media only screen and (min-width: 1200px) {
  .mxd-projects-masonry__btngroup {
    bottom: 3rem;
  }
}

/*
 * The template fills .mxd-divider__image with a CSS background. Where the picture
 * is ours we pass a real <img> instead, so it carries alt text; these rules make
 * it fill the band the same way `background-size: cover` did.
 */
.mxd-divider__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ------------------------------------------------ */
/* CTA before the footer                              */
/* ------------------------------------------------ */

/*
 * main.css points .mxd-promo__bg at one of the pf-*.png placeholders, because
 * the reconstruction mapped every dummyimage URL onto those four files. This is
 * the real artwork. Overridden here rather than edited in main.css so a template
 * update can replace the vendor stylesheet without losing it.
 */
.mxd-promo__bg {
  background-image: url(../img/icons/cta-bg.webp);
}

/*
 * The caption runs across the artwork at every width. With the flat placeholder
 * that did not matter; with a photograph the last words landed on mid-grey chrome
 * — measured 1.99:1 against the caption colour at 1440px, and 3.77:1 at 1024px.
 * Fading the image out towards the left keeps the words on plain card colour.
 * The stops differ because the layout does: below 1200px the image is 70% of the
 * panel and starts under the text, above it drops to 50% and sits to the side.
 * Measured after the change: 6.9:1 at 1024, 10.6:1 at 768, 12.8:1 at 390,
 * 8.0:1 at 1440 — all clear of 4.5:1, so the strict threshold holds even though
 * this caption is large text.
 */
.mxd-promo__bg {
  -webkit-mask-image: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.35) 22%, #000 46%);
          mask-image: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.35) 22%, #000 46%);
}

@media only screen and (max-width: 1199px) {
  .mxd-promo__bg {
    -webkit-mask-image: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.3) 40%, #000 76%);
            mask-image: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.3) 40%, #000 76%);
  }
}

/*
 * `.btn-anim .btn-caption` is a 3.3rem window with `overflow: hidden`, so a label
 * that needs two lines loses its second one with no sign anything is missing —
 * "Start the Conversation" rendered as "Start the" on a 390px phone. The theme's
 * own labels are all short enough to never hit it. Ours are page-authored, so on
 * phones let the caption grow and drop the duplicate block the hover slide uses;
 * touch has no hover, so nothing is lost. Keep CTA labels short anyway.
 */
@media only screen and (max-width: 767px) {
  .mxd-promo__controls .btn-anim .btn-caption {
    height: auto;
    line-height: 1.2;
    overflow: visible;
  }

  .mxd-promo__controls .btn-anim .btn-anim__block:last-child {
    display: none;
  }
}

/*
 * Inner pages start closer to the header than the template's 13–17rem.
 * Overridden at every breakpoint the template sets, otherwise its later
 * media queries win over this one.
 */
.mxd-section-inner-headline,
.mxd-section-inner-headline.grid-headline {
  padding-top: 8rem;
}

/*
 * The pill before the blog headline. The template's `headline-img-06` points at
 * one of the pf-*.png placeholders, so it rendered as an empty dark lozenge.
 * Own class rather than repointing that one, so nothing else on the site moves.
 *
 * The source is 1000×771 (1.3:1) and the pill is 1.9:1, so `cover` crops top and
 * bottom. Pulled up from centre to keep the face rather than a band across it.
 */
/*
 * Both selectors carry `.inner-headline__title` on purpose. The theme sets
 * `background-position` on `.inner-headline__title.headline-img-before::before`
 * — two classes — so a single-class rule here loses the cascade on position
 * while still winning on image, and the crop silently stays at 50% 50%. That is
 * what the blog pill did from the day it was written: the offset below was in
 * the file and never in effect.
 */
.inner-headline__title.headline-img-blog::before {
  background-image: url(../images/blog.webp);
  background-position: center 32%;
}

/*
 * The FAQ source is 600×463 (1.3:1) against a 2:1 lozenge, so `cover` keeps a
 * horizontal band about 65% of the image tall. Pulled slightly above centre so
 * the band holds the laptop screen rather than the desk below it — at 140×70px
 * the lit screen is the only shape that still reads.
 */
.inner-headline__title.headline-img-faq::before {
  background-image: url(../images/faq.jpg);
  background-position: center 42%;
}

/*
 * About. At 400×225 (1.78:1) against a 2:1 lozenge this is the one that barely
 * crops — about 11% off the height — so plain centring keeps the prism edge and
 * the spectrum, which are the only two things that read at 140×70px.
 */
.inner-headline__title.headline-img-about::before {
  background-image: url(../images/about-fav.webp);
  background-position: center;
}

/*
 * Works. Same 400×225 as the about pill, so the crop is the same 11% and plain
 * centring keeps both halves of the frame — the dark lens on the left against
 * the white panels on the right, which is the only contrast that survives at
 * 140×70px.
 */
.inner-headline__title.headline-img-works::before {
  background-image: url(../images/works-fav.webp);
  background-position: center;
}

/* Services. Same 400×225 source size, so the same light crop and centring. */
.inner-headline__title.headline-img-services::before {
  background-image: url(../images/service-fav.webp);
  background-position: center;
}

/*
 * The featured post card lays its text directly over the thumbnail, and the
 * template ships no scrim — its own demo image was dark enough. Ours is a
 * laptop with a near-white screen exactly where the title sits: measured
 * 1.00:1, white on white.
 *
 * The text does not stay in a neat column — the tag row runs along the top and
 * the title wraps well past the middle — so a side gradient left gaps wherever
 * a line ran long (2.07:1 at 1600px on the first attempt). It is a wash across
 * the whole thumb, leaning darker on the left where most of the text sits.
 * 0.62 alpha over pure white composites to rgb(103,103,103) — 5.3:1 against the
 * white text, and the picture still reads underneath.
 */
.post-featured__thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, rgba(10, 10, 10, 0.8) 0%, rgba(10, 10, 10, 0.62) 100%);
}

/* ------------------------------------------------ */
/* Consent banner                                     */
/* ------------------------------------------------ */

/*
 * Bottom-anchored card, not a full-screen overlay. An overlay that blocks the
 * page pressures people into clicking the bigger button, and "consent" obtained
 * that way is not consent. Accept and Reject are the same size, the same shape
 * and the same weight; only the fill differs, so the eye is not steered.
 */
.consent {
  position: fixed;
  left: 1.6rem;
  right: 1.6rem;
  bottom: 1.6rem;
  z-index: 9999;
  max-width: 46rem;
  padding: 2.4rem;
  border: 1px solid var(--st-muted);
  border-radius: var(--_radius-m);
  background-color: var(--base-tint);
  box-shadow: 0 1.2rem 4rem rgba(0, 0, 0, 0.35);
  animation: consent-in 0.3s var(--_animbezier) both;
}

/*
 * Slide only — no opacity. A fixed panel that is even briefly semi-transparent
 * gets sampled by contrast checkers against whatever it sits over, and the hero
 * image behind it turns a 17:1 panel into a reported 2.8:1. Opaque at every
 * instant removes the ambiguity, for the checker and for the reader.
 */
@keyframes consent-in {
  from { transform: translateY(1.2rem); }
  to   { transform: translateY(0); }
}

.consent.is-leaving {
  animation: consent-out 0.25s var(--_animbezier) both;
}

@keyframes consent-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(1.2rem); }
}

@media (prefers-reduced-motion: reduce) {
  .consent,
  .consent.is-leaving {
    animation: none;
  }
}

.consent__title {
  font: normal var(--fw-medium) 2.2rem/1.2 var(--_font-accent);
  color: var(--t-bright);
}

.consent__text,
.consent__list,
.consent__note {
  margin-top: 1.2rem;
  font-size: 1.5rem;
  line-height: 1.5;
  color: var(--t-medium);
}

.consent__list {
  padding-left: 1.8rem;
}

.consent__list strong {
  color: var(--t-bright);
}

.consent__note {
  font-size: 1.3rem;
}

.consent__note a {
  color: var(--t-bright);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.consent__actions {
  display: flex;
  gap: 1rem;
  margin-top: 2rem;
}

/* Same box for both. The only difference is the fill. */
.consent__btn {
  flex: 1 1 0;
  min-height: 4.4rem;
  padding: 0 1.8rem;
  border-radius: 2.2rem;
  border: 1px solid var(--st-bright);
  font: normal var(--fw-medium) 1.6rem/1 var(--_font-accent);
  cursor: pointer;
  transition: background-color 0.2s var(--_animbezier), color 0.2s var(--_animbezier);
}

/*
 * Both buttons identical, including the fill. The site's orange would have made
 * Accept the obvious one — which is exactly the nudge that makes consent
 * obtained through a banner worthless. There is no primary action here.
 */
.consent__btn {
  background-color: transparent;
  color: var(--t-bright);
}

.no-touch .consent__btn:hover {
  background-color: var(--st-muted);
}

/* Accept all is the one filled button, in the site's lime. Reject all stays on
   the same first screen at the same size, so refusing is exactly as easy. */
.consent__btn--accept {
  background-color: var(--accent);
  border-color: var(--accent);
  color: var(--pt-t-dark-bright);
}
.no-touch .consent__btn--accept:hover {
  background-color: var(--accent);
  opacity: 0.88;
}

.consent__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media only screen and (min-width: 768px) {
  .consent {
    left: 3rem;
    bottom: 3rem;
    right: auto;
    padding: 3rem;
  }
}

/* ------------------------------------------------ */
/* Legal pages                                        */
/* ------------------------------------------------ */

/*
 * Privacy and Terms borrow the article typography, but not its headline size:
 * a policy is read, not scanned from across the room, and the template's inner
 * headline runs to 10rem.
 */
.legal-headline {
  font-size: min(6rem, 8vw);
}

@media only screen and (min-width: 768px) {
  .legal-headline {
    font-size: 6rem;
  }
}

.legal-meta {
  margin-top: 1.6rem;
  margin-bottom: 4rem;
}

/*
 * The article renderer wraps prose in .mxd-article__block, which carries the
 * vertical rhythm. This content is plain markup, so the spacing is stated here
 * — and stated tightly: the template's own article headings sit inside blocks
 * that already have 8rem of padding, which on a policy leaves the reader
 * scrolling through empty screens between sections.
 */
/*
 * .mxd-article__content is a flex column with `gap: 6rem` — sized for the
 * .mxd-article__block wrappers the Markdown renderer emits, each holding a whole
 * chunk of prose. This content is plain paragraphs, so that gap puts six rem
 * between every single one. Zero it and let the margins below do the spacing.
 */
.legal-content {
  gap: 0;
}

.legal-content > * + * {
  margin-top: 1.6rem;
}

.legal-content h2 {
  margin-top: 4.4rem;
  margin-bottom: 0;
  font-size: 3.2rem;
  line-height: 1.2;
}

.legal-content h3 {
  margin-top: 2.8rem;
  margin-bottom: 0;
  font-size: 2.2rem;
  line-height: 1.3;
  color: var(--t-bright);
}

.legal-content li + li {
  margin-top: 0.6rem;
}

/* Inline links in running prose need a non-colour cue — WCAG 1.4.1. */
.legal-content a {
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.legal-content code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9em;
  padding: 0.15em 0.4em;
  border-radius: 0.4rem;
  background-color: var(--base-tint);
  color: var(--t-bright);
}

/* ------------------------------------------------ */
/* Contact page                                       */
/* ------------------------------------------------ */

/*
 * Three sizes on this page are set against how much room the text actually has,
 * not at a fixed value: the template steps its type at breakpoints but the
 * column widths do not follow the same steps, so a single rem value that fits at
 * 1440 wrapped at 1200 and 768. Each ceiling below was derived from the measured
 * column width at every breakpoint, then verified on the built page.
 */

/*
 * "Let's talk / about your project!" carries its own <br>, but at the template's
 * 10rem the second half needed 912px in an 866px column and ran to a third line.
 * The column is ~84% of the viewport while stacked and ~59% from 1200px up,
 * hence the two ceilings.
 */
.contact-headline {
  font-size: min(8rem, 7.6vw);
}

@media only screen and (min-width: 1200px) {
  .contact-headline {
    font-size: min(10rem, 5.4vw);
  }
}

/*
 * The email under it is set at headline size and broke in two at the hyphen in
 * "min-code". The arrow icon takes ~65px of the row, so the label gets the
 * column width minus that; measured, it needs 11.4× its font size.
 */
.contact-email .btn-caption {
  font-size: min(6rem, 5.8vw);
}

@media only screen and (min-width: 1200px) {
  .contact-email .btn-caption {
    font-size: min(6rem, 4.6vw);
  }
}

/*
 * "Tell us what you want to build." runs to three lines at the template's size
 * on everything below 1920px. Held to two, sized against the room the caption
 * actually has: from 1200px up that is the gap before the floating artwork
 * (never nearer than ~49% of the viewport); below it, the content column minus
 * the heart icon. Measured at two lines: the sentence needs about 9.6× its own
 * font size.
 */
.mxd-promo__caption--two-lines {
  font-size: min(8rem, 7vw);
}

@media only screen and (min-width: 1200px) {
  .mxd-promo__caption--two-lines {
    font-size: min(10rem, 5vw);
  }
}

/* ------------------------------------------------ */
/* Case study pages                                   */
/* ------------------------------------------------ */

/*
 * A case-study headline names the project and what it does, so it runs longer
 * than the one-or-two-word headings the other inner pages carry. At the
 * template's 6rem it took six lines on a 390px screen. Scaling it with the
 * viewport below the tablet breakpoint brings that to four; from 768px up the
 * template size is untouched. Scoped to this page so /about/ and /services/
 * keep the sizes the design was drawn at.
 */
@media only screen and (max-width: 767px) {
  .case-headline {
    font-size: clamp(3.4rem, 10.5vw, 6rem);
  }
}

/*
 * The template fills .mxd-divider__image with a CSS background. Case studies use
 * real <img> instead, so the alt text is available and the browser can pick the
 * right source later — these rules make an image behave like that background.
 */
.mxd-case-image {
  position: relative;
  overflow: hidden;
}

.mxd-case-image img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.mxd-case-stats {
  margin-top: 3rem;
}

/*
 * Four stats on one row: "Interface languages" wraps to two lines while the
 * others take one, which dropped its value below the rest. Reserving two lines
 * for every label puts all four values on the same baseline.
 */
@media only screen and (min-width: 1200px) {
  .mxd-case-stats .mxd-data-list__name {
    min-height: 2.8em;
  }
}

.mxd-case-quote {
  margin-top: 3rem;
}

.mxd-case-quote blockquote {
  margin: 0;
}

.mxd-case-quote__name {
  margin-top: 1.2rem;
}

/*
 * WCAG 1.4.1: a link sitting inside a sentence must be told apart by something
 * other than colour. The theme strips underlines site-wide, so put one back
 * here — this is the only inline link in running text on a case study.
 */
.mxd-case-quote figcaption a {
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* Grid cards that carry a real screenshot rather than a template background. */
.mxd-project-item__preview.has-image,
.mxd-blog-preview__image.has-image {
  background-image: none;
}

.mxd-project-item__preview.has-image img,
.mxd-blog-preview__image.has-image img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media only screen and (max-width: 640px) {
  .mxd-section-inner-headline,
  .mxd-section-inner-headline.grid-headline {
    padding-top: 6rem;
  }
  .contact-headline {
    font-size: min(8rem, 12.6vw);
  }
  .btn-line-headline i {
    font-size: 2rem
  }
  .btn-line-headline {
    margin-top: 2rem
  }
  .mxd-section.padding-default,
  .mxd-section.padding-grid-pre-mtext {
    padding-bottom: 9rem;
  }
  .mxd-footer__fullwidth-text {
    padding-top: 6.5rem
  }
}

/* ------------------------------------------------ */
/* Slider arrows                                      */
/* ------------------------------------------------ */

/*
 * The theme sizes the icon inside a round button with `.btn-round-small i
 * { font-size: 1.8rem }` — an icon-font rule. We render SVGs instead, and no
 * rule was sizing those, so the glyph stretched to fill the circle: a 38px
 * arrow inside a 40px button, touching the border on every side. That is what
 * made the arrows look borrowed from another design.
 */
.btn-round svg {
  width: 2.2rem;
  height: 2.2rem;
}

.btn-round-small svg {
  width: 1.8rem;
  height: 1.8rem;
}

/* ------------------------------------------------ */
/* Services list headings                             */
/* ------------------------------------------------ */

/*
 * The theme steps this size at breakpoints (12rem / 9rem / 4.4rem). Between
 * those steps the column narrows but the type does not, so "Business process
 * automation" ran to three lines at 1600px, 1280px and on a phone. A fluid size
 * tracks the column instead of the breakpoint, which holds every title to two
 * lines at every width.
 */
.mxd-services-list__title h2 {
  font-size: clamp(2.6rem, 4vw, 9rem);
  /*
   * Unitless, so the leading follows the fluid size. The theme sets it in pixels
   * per breakpoint, which left the ratio drifting between 0.70 and 0.94 across
   * widths once the size stopped stepping with it. 0.8 keeps the tight display
   * leading the design is built on, at every width.
   */
  line-height: 0.8;
}

/* ------------------------------------------------ */
/* Blog sidebar                                       */
/* ------------------------------------------------ */

/*
 * Sticky, not fixed. `.mxd-posts-area` is a flex row from 1200px up, so the
 * sidebar is stretched to the full height of the article list by default and a
 * sticky offset would have nothing to travel through — `align-self: flex-start`
 * shrinks it back to its own content first. True `position: fixed` would take it
 * out of the flow entirely and collapse the column the articles sit beside.
 *
 * Below 1200px the sidebar stacks under the articles, where sticking it would
 * park a panel over the content on a phone. So the rule only exists at the width
 * where there are two columns to begin with.
 */
@media only screen and (min-width: 1200px) {
  .mxd-sidebar {
    position: sticky;
    top: 3rem;
    align-self: flex-start;
    /*
     * If the widgets ever outgrow the viewport, the tail would be unreachable —
     * sticky pins the top and the rest hangs below the fold with no way to
     * scroll it. Capping the height and letting the panel scroll itself keeps
     * every widget reachable no matter how many categories the articles create.
     */
    max-height: calc(100vh - 6rem);
    overflow-y: auto;
  }

  /*
   * Without this the cap never produces a scrollbar. `.mxd-sidebar` is a flex
   * column, so its children shrink to fit the capped height instead of
   * overflowing it: measured on a 900px-tall window the promo panel came out at
   * 163px instead of 343px, and on a 700px window at 0px — gone from the page
   * entirely, with no scrollbar to reveal it. Holding the widgets at their own
   * height is what makes `overflow-y` mean anything.
   */
  .mxd-sidebar > * {
    flex-shrink: 0;
  }
}

/* ------------------------------------------------ */
/* Blog index grid                                    */
/* ------------------------------------------------ */

/*
 * The sidebar is gone, so the article column runs full width and the remaining
 * posts sit two across. The sticky sidebar rules above now apply to nothing on
 * this page; they are left in place because the component still exists and the
 * article template may take it back.
 */
.posts-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
}

@media only screen and (min-width: 992px) {
  .posts-grid {
    grid-template-columns: repeat(2, 1fr);
    /* Column gap only — the cards carry their own vertical rhythm. */
    column-gap: 4rem;
  }
}

/*
 * `post-simple` is built for a full-width list: image left, text right, with a
 * rule above each row. At half the width that layout has nowhere to put the
 * text, and the rules read as a list rather than a grid.
 */
.post-simple--grid {
  flex-direction: column;
  gap: 2.4rem;
  padding: 4rem 0;
}

/* The separator belongs to the row above, so only the top edge carries one. */
.post-simple--grid:last-of-type {
  border-bottom: none;
}

.post-simple--grid .post-simple__thumb {
  /*
   * Overrides the 260px flex-basis the theme sets from 768px up, which would
   * otherwise squeeze the image into a narrow strip once the card is a column.
   */
  flex-basis: auto;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
}

.post-simple--grid .post-simple__content {
  max-width: none;
}

/* ------------------------------------------------ */
/* FAQ accordion                                      */
/* ------------------------------------------------ */

/*
 * The template styles `.mxd-accordion__title` as a <div>. We render a <button>
 * so the questions are reachable by keyboard and announced with their state,
 * which brings the UA's button styling with it: a light ButtonFace background,
 * a border, and shrink-to-fit width. Reset it back to the block the theme's
 * flex rules expect — the element looks exactly like the design, and behaves
 * like the control it actually is.
 */
button.mxd-accordion__title {
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  border-radius: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* The heading inside carries the type; it must not add margin of its own. */
button.mxd-accordion__title h2 {
  margin: 0;
}

/* Keeps the plus from crowding a question that wraps to the full column. */
button.mxd-accordion__title .mxd-accordion__arrow {
  flex-shrink: 0;
  margin-left: 2rem;
}

/*
 * The theme rotates the arrow by 45 degrees to turn the plus into a cross. That
 * rule targets the icon-font <i>; ours is an inline SVG, so it needs saying.
 */
.mxd-accordion__arrow svg {
  display: block;
  transition: transform var(--_animspeed-medium) var(--_animbezier);
}

.mxd-accordion__arrow.accordion-rotate svg {
  transform: rotate(45deg);
}

@media (prefers-reduced-motion: reduce) {
  .mxd-accordion__arrow svg {
    transition: none;
  }
}

/* ------------------------------------------------ */
/* About — values grid images                         */
/* ------------------------------------------------ */

/*
 * The tall slot is 420×1116 on desktop — a 0.38 ratio, far narrower than any
 * photograph. `about1.webp` is 667×1000 (0.67), so `cover` keeps a vertical
 * strip a little over half the frame's width. Centred, that strip holds the
 * chipset and the diagonal lettering, which is what makes it read as a board
 * rather than as texture.
 *
 * The second slot is still on the template's placeholder: about2.webp has not
 * been uploaded. It is 645×720 (0.90), so it wants a near-square image, not a
 * portrait one.
 */
.image-about-1 {
  background-image: url(../images/about1.webp);
  background-position: center;
}

/*
 * The second slot is 645×720 — 0.90, near square — and this source is 1000×527,
 * a 1.90 landscape. `cover` therefore keeps under half its width. That is a
 * heavy crop, and survivable only because the image is a repeating abstract
 * texture rather than a photograph of something: centred, the frame lands on
 * the dark void and the circuitry around it, which is the most structured part
 * of it. A picture with a subject would need re-cropping at source instead.
 */
.image-about-2 {
  background-image: url(../images/about2.webp);
  background-position: center;
}

/*
 * Full-width divider after the marquee. 1400×933 (1.50) into a 1320×760 slot
 * (1.74), so `cover` trims about 14% off the height — top and bottom, centred.
 * The phone runs the full height of the frame, so it is the one element the
 * crop touches; centring keeps its screen intact and takes the trim off the
 * desk below and the empty wall above.
 */
.divider-image-about {
  background-image: url(../images/about3.webp);
  background-position: center;
}

/* ------------------------------------------------ */
/* Services — stat card images clear of the text       */
/* ------------------------------------------------ */

/*
 * From 1200px the two stat cards sit side by side and narrow as the viewport
 * does. The theme anchors each image from the right, so a narrower card slides
 * the image left over the text — the white robot over white text, the dark
 * molecule over the dark arrow. The text itself is anchored left and does not
 * move within a breakpoint.
 *
 * So each image keeps the theme's right-anchored position (the calc term) until
 * that position would reach the text, and stops there (the px floor). The floor
 * is not eyeballed: it was computed from the images' alpha channels against the
 * measured glyph boxes, so that no opaque pixel comes within 16px of a letter or
 * of the button, per breakpoint. Widths stay the theme's.
 *
 * Re-derive the floors if the card copy or either image changes: for every text
 * line and the button, find the image rows beside it, take the leftmost opaque
 * column there, and require it to sit 16px past the line's right edge.
 */
@media only screen and (min-width: 1200px) {
  .mxd-stats-cards__image.stats-img-clear-1 { left: max(214px, calc(100% + 40px - 450px)); right: auto; }
  .mxd-stats-cards__image.stats-img-clear-2 { left: max(271px, calc(100% + 60px - 280px)); right: auto; }
}
@media only screen and (min-width: 1600px) {
  .mxd-stats-cards__image.stats-img-clear-1 { left: max(238px, calc(100% + 40px - 620px)); right: auto; }
  .mxd-stats-cards__image.stats-img-clear-2 { left: max(263px, calc(100% + 60px - 400px)); right: auto; }
}

/* ------------------------------------------------ */
/* One accent: #DDF160                                */
/* ------------------------------------------------ */

/*
 * The orange is gone; lime is the only accent, in both themes. Lime is light,
 * so anything written ON it must be dark in both themes — hence the fixed dark
 * token, not --t-bright / --t-opp-bright, which flip with the theme and would
 * put white text on lime in one of them (the project counter and .btn-accent
 * did exactly that in the light theme).
 */
.btn-accent,
.footer-nav__counter,
.footer-nav__counter span,
.mxd-tech-stack-cards__logo {
  color: var(--pt-t-dark-bright);
}

/*
 * And lime cannot be text or an outline on the light theme's pale ground —
 * about 1.2:1. There those fall back to the dark text colour: still no second
 * accent, just legible.
 */
[color-scheme=light] .btn-line-headline:hover { color: var(--t-bright); }
[color-scheme=light] .consent__btn:focus-visible,
[color-scheme=light] .contact-modal :focus-visible { outline-color: var(--t-bright); }

/* `hidden` loses to `.btn { display: … }`; say it outright for the controls
   consent.js reveals only when there is something to set. */
[data-consent-reopen][hidden] { display: none !important; }
.footer-cookie-settings { background: none; border: 0; font-family: inherit; cursor: pointer; }

.mc-sr-only {
  position: absolute !important;
  width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ------------------------------------------------ */
/* Consent — categories and settings                  */
/* ------------------------------------------------ */

.consent__actions { flex-wrap: wrap; }
.consent__actions .consent__btn { flex: 1 1 0; min-width: 9rem; }
.consent__settings { margin: 1.6rem 0 2rem; }
.consent__cats {
  list-style: none; margin: 0 0 1.6rem; padding: 0;
  display: grid; gap: 1rem;
  max-height: 42vh; overflow-y: auto; overscroll-behavior: contain;
}
.consent__cat { padding: 1.2rem 1.4rem; border: 1px solid var(--st-muted); border-radius: 1.2rem; }
.consent__cat-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.consent__cat-name { color: var(--t-bright); font-size: 1.5rem; cursor: pointer; }
.consent__always { font-size: 1.3rem; color: var(--t-medium); }
.consent__cat-desc { margin: 0.6rem 0 0; font-size: 1.4rem; line-height: 1.45; color: var(--t-medium); }
.consent__details { margin-top: 0.6rem; font-size: 1.3rem; color: var(--t-medium); }
.consent__details summary { cursor: pointer; color: var(--t-bright); }
.consent__details dl { margin: 0.8rem 0 0; display: grid; grid-template-columns: auto 1fr; gap: 0.4rem 1.2rem; }
.consent__details dt { color: var(--t-bright); }
.consent__details dd { margin: 0; }
.consent__switch {
  -webkit-appearance: none; appearance: none; margin: 0; flex-shrink: 0;
  position: relative; width: 4.4rem; height: 2.4rem; border-radius: 2.4rem;
  background: var(--st-muted); border: 1px solid var(--st-medium); cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}
.consent__switch::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 1.8rem; height: 1.8rem; border-radius: 50%;
  background: var(--t-bright); transition: transform 0.2s ease;
}
.consent__switch:checked { background: var(--accent); border-color: var(--accent); }
.consent__switch:checked::after { transform: translateX(2rem); background: var(--pt-t-dark-bright); }
.consent__switch:focus-visible { outline: 2px solid var(--t-bright); outline-offset: 2px; }
.consent__btn--save { width: 100%; }
@media (prefers-reduced-motion: reduce) {
  .consent__switch, .consent__switch::after { transition: none; }
}

/* ------------------------------------------------ */
/* Contact modal                                      */
/* ------------------------------------------------ */

.contact-modal {
  width: min(90rem, calc(100vw - 3.2rem));
  max-width: none;
  max-height: calc(100dvh - 3.2rem);
  padding: 0; border: 0; border-radius: 2.4rem;
  background: var(--base-tint); color: var(--t-bright);
  overflow: auto; overscroll-behavior: contain;
}
.contact-modal::backdrop { background: rgba(0, 0, 0, 0.72); }
.contact-modal__inner { position: relative; padding: 4.4rem 2.4rem 3rem; }
@media only screen and (min-width: 768px) {
  .contact-modal__inner { padding: 5rem 5rem 4rem; }
}
.contact-modal__close {
  position: absolute; top: 1.6rem; right: 1.6rem;
  width: 4.4rem; height: 4.4rem; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid var(--st-medium); background: transparent; color: var(--t-bright); cursor: pointer;
}
.no-touch .contact-modal__close:hover { background: var(--st-muted); }
.contact-modal__title { font-size: clamp(2.6rem, 3vw, 4rem); line-height: 1.1; margin: 0 5.6rem 1rem 0; color: var(--t-bright); }
.contact-modal__text { margin: 0 0 2.4rem; color: var(--t-medium); }
.contact-modal :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.contact-modal .form-container { position: relative; }

/* ------------------------------------------------ */
/* Contact forms — budget and consent                 */
/* ------------------------------------------------ */

.form-budget { border: 0; margin: 0 0 0.4rem; padding: 0; min-width: 0; }
.form-budget__legend { float: none; padding: 0; margin: 0 0 1.2rem; font-size: 1.6rem; color: var(--t-medium); }
.form-budget__options { display: flex; flex-wrap: wrap; gap: 0.8rem; }
.form-budget__option { position: relative; cursor: pointer; }
.form .form-budget__option input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; padding: 0; border: 0; opacity: 0; cursor: pointer;
}
.form-budget__option span {
  display: inline-block; padding: 1rem 1.8rem;
  border: 1px solid var(--st-medium); border-radius: 4rem;
  font-size: 1.5rem; line-height: 1.2; color: var(--t-bright);
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.no-touch .form-budget__option:hover span { border-color: var(--t-bright); }
.form-budget__option input:checked + span { background: var(--accent); border-color: var(--accent); color: var(--pt-t-dark-bright); }
.form-budget__option input:focus-visible + span { outline: 2px solid var(--t-bright); outline-offset: 2px; }

.form-consent {
  display: flex; align-items: flex-start; gap: 1.2rem;
  margin: 0.4rem 0 1rem; font-size: 1.5rem; line-height: 1.5; color: var(--t-medium); cursor: pointer;
}
.form .form-consent input {
  -webkit-appearance: auto; appearance: auto;
  flex-shrink: 0; width: 2rem; height: 2rem; margin: 0.2rem 0 0; padding: 0;
  border: 0; background: none; box-shadow: none; accent-color: var(--accent); cursor: pointer;
}
.form-consent a { color: var(--t-bright); text-decoration: underline; text-underline-offset: 3px; }
.form textarea[rows] { height: auto; min-height: 0; }

/* Cookie table in the privacy policy. */
.legal-table { overflow-x: auto; margin: 1.6rem 0 2.4rem; }
.legal-table table { width: 100%; min-width: 64rem; border-collapse: collapse; font-size: 1.5rem; }
.legal-table th, .legal-table td { text-align: left; vertical-align: top; padding: 1.2rem 1.4rem; border-bottom: 1px solid var(--st-muted); }
.legal-table th { color: var(--t-bright); font-weight: 500; }

/*
 * The modal should fit one screen without scrolling. At its full 90rem width the
 * six budget buttons fit on one line, so from 992px (where it reaches that
 * width) they are held to one; narrower, they wrap rather than overflow.
 */
@media only screen and (min-width: 992px) {
  .contact-modal .form-budget__options { flex-wrap: nowrap; }
}

/*
 * One screen, no scroll, down to a 1366x768 laptop. Measured there, the form's
 * eight rows carried 30px each above them — 240px of gaps against a 31px
 * overflow. 22px keeps the rhythm and gives the room back. The submit drops to
 * 44px tall: about half the page form's 84px, still a comfortable touch target.
 */
.contact-modal .form .row > .mxd-grid-item { margin-top: 2.2rem; }
.contact-modal .form button[type="submit"].btn {
  height: 4.4rem;
  padding: 0 2.2rem;
  font-size: 1.7rem;
}
.contact-modal .form button[type="submit"].btn svg { width: 1.6rem; height: 1.6rem; }

/*
 * Text fields in the modal show focus the way the theme does everywhere else —
 * the bottom line brightens (main.css: form input:focus) — not with a boxed
 * outline around the whole field, which read as heavy and did not match the
 * contact page form. The outline stays on buttons, the checkbox and the budget
 * options, where it appears only for keyboard focus.
 */
.contact-modal input:not([type="radio"]):not([type="checkbox"]):focus-visible,
.contact-modal textarea:focus-visible {
  outline: none;
}

/* ------------------------------------------------ */
/* Services — tech stack                              */
/* ------------------------------------------------ */

/* A list for the markup's sake; the theme's grid does the layout. */
.mc-tech-stack .mxd-tech-stack-cards { list-style: none; margin: 0; padding: 0; }

/* The theme's columns are plain 1fr, which never shrink below a card's
   content: tile, gap, padding and label come to about 235px, so two columns
   ran off a phone and three off a tablet. minmax(0, 1fr) lets them shrink,
   and below 1200px the tile sits above the name so the card fits the narrower
   column. */
.mc-tech-stack .mxd-tech-stack-cards.grid-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-column-gap: 1.6rem; grid-row-gap: 1.6rem; }
@media only screen and (min-width: 768px) {
  .mc-tech-stack .mxd-tech-stack-cards.grid-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-column-gap: 2rem; grid-row-gap: 2rem; }
}
@media only screen and (max-width: 1199px) {
  .mc-tech-stack .mxd-tech-stack-cards__inner { flex-direction: column; align-items: flex-start; gap: 1.6rem; padding: 2rem; }
  .mc-tech-stack .mxd-tech-stack-cards__logo { width: 6.4rem; height: 6.4rem; }
  .mc-tech-stack .mxd-tech-stack-cards__logo svg { width: 3.6rem; }
  .mc-tech-stack .mc-tech-mark { font-size: 1.8rem; }
  /* The title stacks above the grid here; the theme leaves no gap between them. */
  .mc-tech-stack .mxd-block__name { margin-bottom: 3.2rem; }
}
/* On the desktop grid the theme's 3rem gaps come back. */
@media only screen and (min-width: 1200px) {
  .mc-tech-stack .mxd-tech-stack-cards.grid-cards { grid-column-gap: 3rem; grid-row-gap: 3rem; }
}

/* The theme goes to four columns from 1400px; nine technologies want a 3x3. */
@media only screen and (min-width: 1400px) {
  .mc-tech-stack .mxd-tech-stack-cards.grid-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Wordmark tiles (iOS, .NET, C#): the name set in the site font, sized to sit
   where a logo would. */
.mc-tech-mark { font-size: 2.2rem; font-weight: 500; line-height: 1; letter-spacing: -0.02em; }

/* ------------------------------------------------ */
/* Home — client logos                                */
/* ------------------------------------------------ */

/* Two columns on phones rather than the theme's one: eight 250px cards
   stacked ran to over 2000px of logos. The 170px logo is sized for a wide
   column, so it shrinks to fit the half-width one. */
@media only screen and (max-width: 767px) {
  .mxd-partners-cards__item { height: 14rem; }
  .mxd-partners-cards__logo { width: 72%; }
}
@media only screen and (min-width: 768px) and (max-width: 1199px) {
  .mxd-partners-cards__item { height: 20rem; }
}

/* ------------------------------------------------ */
/* Home — image marquee, caption cards                */
/* ------------------------------------------------ */

/* The caption cards sit on the lime accent in both themes. The theme colours
   their text and outline button with the "opposite" tokens, which are light on
   the dark theme — white on lime. Dark ink here, whatever the theme. */
.marquee__item.has-caption p,
.marquee__item.has-caption .btn-outline.opposite {
  color: var(--pt-t-dark-bright);
}
.marquee__item.has-caption .btn-outline.opposite {
  border-color: var(--pt-t-dark-bright);
}

