/* Hand-written layer, loaded after the Webflow CSS so it always wins.
   Never edit zipli.webflow.css, webflow.css or normalize.css. Those are
   Webflow output and a re-export overwrites them. Everything we add lives
   here so a re-export stays a clean drop-in. */

/* ---------------------------------------------------------------------
   Design tokens
   The palette lifted out of the Webflow export. Use these in new work
   instead of pasting hex values, so a brand change is one edit here.
   --------------------------------------------------------------------- */
:root {
  /* Brand */
  --zipli-lime: #adf85a;          /* primary accent, h1, CTA hover */
  --zipli-lime-soft: #b6f075;     /* secondary accent surfaces */
  --zipli-green: #085f33;         /* deep green, CTA text and fills */
  --zipli-blush: #fff0f2;         /* pale pink section background */
  --zipli-purple: #d08cfb;        /* Webflow --secondary */
  --zipli-lavender: #f8d5ff;      /* pale purple section background, matches process-steps art */
  --zipli-blue: #0050bd;          /* links and highlights */
  --zipli-red: #fe5858;           /* alerts */

  /* Neutrals */
  --zipli-black: #000;
  --zipli-ink: #171717;
  --zipli-graphite: #2f2f2f;
  --zipli-grey: #7b7b7b;
  --zipli-line: #d6d6d6;
  --zipli-mist: #ebebeb;
  --zipli-white: #fff;

  /* Type. The site scales with the viewport: body font-size is 0.8333vw,
     so 1em is 1/120th of the viewport width above 991px and 16px below.
     Size new work in em to inherit that scaling. */
  --zipli-font-display: "Space Grotesk", sans-serif;
  --zipli-font-body: "Satoshi Variable", Satoshi, Arial, sans-serif;

  /* Layout */
  --zipli-container: 90em;        /* .main-container.new-home */
  --zipli-gutter: 2em;            /* .padding-global */
  --zipli-radius-pill: 4.5625em;  /* .nav-cta */
}

/* ---------------------------------------------------------------------
   Accessibility and polish the export does not cover
   --------------------------------------------------------------------- */

/* Webflow ships no visible focus ring. Keyboard users need one. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: 3px solid var(--zipli-blue);
  outline-offset: 3px;
  border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------------
   Styleguide page only. Scoped so it can never leak into a real page.
   --------------------------------------------------------------------- */
.sg-body { background: var(--zipli-white); }

.sg-section {
  max-width: var(--zipli-container);
  margin: 0 auto;
  padding: 4em var(--zipli-gutter);
  border-bottom: 1px solid var(--zipli-line);
}

.sg-label {
  font-family: var(--zipli-font-body);
  font-size: 1em;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--zipli-grey);
  margin: 0 0 1.5em;
}

.sg-note {
  font-family: var(--zipli-font-body);
  font-size: 1.1em;
  font-weight: 300;
  line-height: 1.5;
  color: var(--zipli-graphite);
  margin: 0.75em 0 0;
  max-width: 46em;
}

.sg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(12em, 1fr));
  gap: 1.5em;
}

.sg-swatch {
  border: 1px solid var(--zipli-line);
  border-radius: 0.75em;
  overflow: hidden;
}

.sg-swatch__chip { height: 6em; }

.sg-swatch__meta {
  padding: 0.9em 1em;
  font-family: var(--zipli-font-body);
  font-size: 1em;
  line-height: 1.5;
}

.sg-swatch__name { font-weight: 500; display: block; }
.sg-swatch__hex { color: var(--zipli-grey); }

.sg-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5em;
  align-items: center;
}

.sg-dark {
  background: var(--zipli-black);
  padding: 2em;
  border-radius: 0.75em;
}

/* ---------------------------------------------------------------------
   Language switcher
   A plain text link rather than a third white pill, so it does not eat
   horizontal room in a navbar that is already tight.
   --------------------------------------------------------------------- */
.lang-switch {
  align-self: center;
  margin-left: 1.25em;
  color: var(--zipli-white);
  font-family: var(--zipli-font-body);
  font-size: 1.3125em;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  text-decoration: none;
  transition: color 0.3s;
}

.lang-switch:hover { color: var(--zipli-lime); }

/* ---------------------------------------------------------------------
   Non-English navbar
   The Webflow nav is sized for four short English words at a fixed 7em
   gap. Finnish labels are far longer and overflow it. Scoped by lang so
   the English layout stays byte-for-byte what Webflow shipped.
   --------------------------------------------------------------------- */
html:not([lang="en"]) .nav-menu-two {
  grid-column-gap: 2.5em;
  grid-row-gap: 2.5em;
}

html:not([lang="en"]) .nav-link,
html:not([lang="en"]) .nav-cta-text {
  white-space: nowrap;
  font-size: 1.6em;
}

html:not([lang="en"]) .nav-cta-text { font-size: 1.15em; }

html:not([lang="en"]) .navbar-wrapper {
  grid-column-gap: 1.5em;
  grid-row-gap: 1.5em;
}

/* Styleguide sits under the overlaid navbar, so its first block needs
   clearance the real pages get from their hero. */
.sg-section:first-of-type { padding-top: 10em; }

/* The navbar is white-on-transparent, designed to sit over the dark hero.
   The styleguide has no hero, so give it a dark strip to sit on. */
.sg-body .navbar-logo-left { background: var(--zipli-black); }

/* The navbar sits fixed over the hero fully transparent, which is the
   intended look (you can see the hero image behind it). But it also stays
   fixed over every section below the hero, where transparent lets page
   copy show through and collide with the nav links. js/nav-scroll.js adds
   .navbar-scrolled once the page scrolls past the hero so the backdrop
   only turns opaque there.

   This uses box-shadow instead of background: Webflow's own scroll
   interaction runs a Web Animations API tween on background-color, and
   per spec a running animation wins over even an !important background
   rule, so a background here gets silently faded back out. box-shadow
   is a property that interaction never touches. */
.navbar-logo-left.navbar-scrolled {
  box-shadow: inset 0 0 0 999px rgba(8, 10, 9, 0.85);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

/* On the homepage only, Webflow's own scroll interaction also swaps the
   nav link/logo color to black once you scroll (built for the hero's
   dark-to-white handoff there). Our dark scrolled backdrop needs the
   opposite, so force it back to white while .navbar-scrolled is set.

   Desktop only for .nav-link/.nav-cta-text: below 992px the open nav
   becomes Webflow's full-screen white dropdown, which sets .nav-link to
   dark green itself so it reads against that white panel. Forcing white
   here too, regardless of scroll position, made the links invisible
   against that same white panel.

   The logo isn't subject to that: it always sits in the fixed header
   bar itself, never inside the white dropdown, so it stays black on the
   dark scrolled backdrop at every width unless forced white too - which
   is exactly the black-on-black bug this fixes on mobile. */
@media screen and (min-width: 992px) {
  .navbar-logo-left.navbar-scrolled .nav-link,
  .navbar-logo-left.navbar-scrolled .nav-cta-text {
    color: #fff !important;
  }
}

.navbar-logo-left.navbar-scrolled .logo-svg { color: #fff !important; }

/* ---------------------------------------------------------------------
   Mobile nav menu (open state)

   Webflow's own script drives the open/close animation by measuring
   .nav-menu-wrapper's real content height and tweening .w-nav-overlay's
   height to match it (it sets that inline style itself, so we don't
   touch it here). Our own height:100vh on .nav-menu-wrapper
   (zipli.webflow.css, <=991px) fights that measurement: the wrapper
   claims a full-viewport height regardless of how much content it
   holds, so the animated overlay opens far past the two-item nav's
   actual content, leaving dead white space below the CTA. Letting the
   wrapper size to its content instead removes the overshoot.
   border-radius on the wrapper/card at this breakpoint also leaves
   rounded corner gaps that the page background bleeds through; a
   square panel avoids that. (position/top are left untouched: forcing
   those to escape Webflow's own absolute/top:100% broke the animation
   entirely, since the overlay's height math assumes the wrapper stays
   in that positioning scheme.)
   --------------------------------------------------------------------- */
@media screen and (max-width: 991px) {
  .nav-menu-wrapper[data-nav-menu-open] {
    height: auto !important;
    min-height: 0 !important;
    border-radius: 0 !important;
  }

  .nav-menu-wrapper[data-nav-menu-open] .nav-menu-two {
    border-radius: 0;
  }
}

/* The rule above forces the CTA label white, but the pill behind it is only
   darkened by Webflow's interaction on the home page. Everywhere else the
   pill stays white and the button reads as blank. Pin the scrolled pill to
   green so the white label is legible on every page.

   box-shadow instead of background-color, same reason as
   .navbar-logo-left.navbar-scrolled above: Webflow's own scroll
   interaction also tweens this button's background-color on the home
   page, and that running animation silently wins over a plain (even
   !important) background rule for a beat right as .navbar-scrolled
   gets added. The label had already flipped white by then, so for
   that window it read as white text on the not-yet-green pill,
   invisible. box-shadow is a property that interaction never touches. */
.navbar-logo-left.navbar-scrolled .nav-cta {
  box-shadow: inset 0 0 0 999px var(--zipli-green);
}

/* The logo SVG is inline by default, which leaves a baseline/descender
   gap below it that isn't part of the visible mark, so the wordmark
   reads as hugging the top of the navbar with extra space underneath.
   display: block removes that gap so it centers like the nav links. */
.logo-svg {
  display: block;
}

/* =====================================================================
   CLIENT-FIRST UTILITIES
   =====================================================================
   The Webflow export already ships part of Client-First: padding-global,
   main-container, the padding-{size} spacers, and a few text-* utilities.
   This block fills in the tiers that were missing so the system is
   complete and predictable.

   Everything here is in em on purpose. The site scales fluidly (body is
   0.8333vw above 991px), so an em utility scales with the layout and a
   px or rem one does not. Never add a px or rem value to this block.

   Nothing here overrides an existing Webflow class. It is all additive,
   so the current pages render exactly as before.
   ===================================================================== */

/* ---------------------------------------------------------------------
   Spacers. An empty div whose only job is vertical space.
     <div class="padding-large"></div>
   Sets padding-top only, so spacers never collapse into each other and
   the space between two blocks is always one predictable value.
   The five middle tiers are Webflow's originals and are listed here for
   reference only, they are not redefined.
   --------------------------------------------------------------------- */
.padding-xxsmall { padding-top: 0.5em; }
.padding-xsmall  { padding-top: 1em; }
.padding-xsmall-2 { padding-top: 1.5em; }
/* .padding-small    2.5em   from zipli.webflow.css */
/* .padding-medium   3em     from zipli.webflow.css */
/* .padding-large    5.5em   from zipli.webflow.css */
/* .padding-xlarge   8em     from zipli.webflow.css */
/* .padding-xxlarge  10em    from zipli.webflow.css */
.padding-huge    { padding-top: 14em; }
.padding-xhuge   { padding-top: 18em; }

/* ---------------------------------------------------------------------
   Section padding. Top and bottom in one class, for the outer shell of
   a section. Use this instead of a spacer at each end when the section
   has a background colour, so the colour covers the padding.
   --------------------------------------------------------------------- */
.padding-section-small  { padding-top: 5.5em;  padding-bottom: 5.5em; }
.padding-section-medium { padding-top: 8em;    padding-bottom: 8em; }
.padding-section-large  { padding-top: 10em;   padding-bottom: 10em; }

@media screen and (max-width: 767px) {
  .padding-section-small  { padding-top: 3em; padding-bottom: 3em; }
  .padding-section-medium { padding-top: 4em; padding-bottom: 4em; }
  .padding-section-large  { padding-top: 5em; padding-bottom: 5em; }
}

/* ---------------------------------------------------------------------
   Content max widths. Sit inside main-container to narrow a block of
   text or a card row. The container controls the page frame, these
   control the measure.
   --------------------------------------------------------------------- */
.max-width-xxsmall { width: 100%; max-width: 20em; }
.max-width-xsmall  { width: 100%; max-width: 25em; }
.max-width-small   { width: 100%; max-width: 30em; }
.max-width-medium  { width: 100%; max-width: 40em; }
.max-width-large   { width: 100%; max-width: 48em; }
.max-width-xlarge  { width: 100%; max-width: 60em; }
.max-width-xxlarge { width: 100%; max-width: 70em; }
.max-width-full    { width: 100%; max-width: none; }

/* Pair with a container to centre it. */
.align-center { margin-left: auto; margin-right: auto; }

/* ---------------------------------------------------------------------
   Text alignment. Several Webflow classes hardcode text-align: center
   (h1, h2, .heading.cta), so these exist to override without writing a
   one-off class each time.
   --------------------------------------------------------------------- */
.text-align-left   { text-align: left; }
.text-align-center { text-align: center; }
.text-align-right  { text-align: right; }

/* ---------------------------------------------------------------------
   Type scale. text-size-small already exists in the Webflow CSS but is
   set in rem, so it does not scale with the rest of the page. These are
   the scaling tiers. Prefer them for new work.
   --------------------------------------------------------------------- */
.text-size-tiny   { font-size: 0.875em; line-height: 1.4; }
.text-size-regular { font-size: 1.125em; line-height: 1.4; }
.text-size-medium { font-size: 1.5em;   line-height: 1.3; }
.text-size-large  { font-size: 2em;     line-height: 1.3; }
.text-size-xlarge { font-size: 2.75em;  line-height: 1.2; }

.text-weight-medium { font-weight: 500; }
.text-weight-bold   { font-weight: 700; }

.text-color-green { color: var(--zipli-green); }
.text-color-lime  { color: var(--zipli-lime); }
.text-color-grey  { color: var(--zipli-grey); }

/* Styleguide: spacing scale visualiser. */
.sg-scale { display: grid; gap: 0.5em; margin-top: 2em; }

.sg-scale__row {
  display: grid;
  grid-template-columns: 14em 5em 1fr;
  align-items: center;
  gap: 1em;
  font-family: var(--zipli-font-body);
  font-size: 1em;
}

.sg-scale__name { font-weight: 500; }
.sg-scale__val { color: var(--zipli-grey); }
.sg-scale__bar { background: var(--zipli-lime); height: 1.25em; border-radius: 2px; }
.sg-scale__bar--inherited { background: var(--zipli-lime-soft); }

.sg-flag {
  border-left: 4px solid var(--zipli-red);
  background: var(--zipli-blush);
  padding: 1.25em 1.5em;
  border-radius: 0 0.5em 0.5em 0;
  margin-top: 1.5em;
}

/* =====================================================================
   LAYOUT GRIDS
   =====================================================================
   Before this block the whole stylesheet contained exactly two
   grid-template-columns declarations, both one-off. Every new multi-column
   section had to hand-roll its own grid. These are the reusable tiers.

   Base class carries display and gap, a combo class carries the column
   count, matching the house pattern of .b2b-solution-card.reverse and
   .main-container.new-home.

     <div class="layout-grid cols-3"> ... </div>

   Collapse follows what the home page already does: the 4-up help card
   grid drops to one column at 767px, so nothing here collapses earlier
   than the site already would.
   ===================================================================== */
.layout-grid {
  display: grid;
  grid-column-gap: 2em;
  grid-row-gap: 2em;
  grid-auto-columns: 1fr;
}

.layout-grid.cols-2 { grid-template-columns: 1fr 1fr; }
.layout-grid.cols-3 { grid-template-columns: 1fr 1fr 1fr; }
.layout-grid.cols-4 { grid-template-columns: 1fr 1fr 1fr 1fr; }

/* Asymmetric pair. Narrow rail beside a wide body, for a sticky heading
   next to a list, or a caption beside an image. */
.layout-grid.cols-sidebar     { grid-template-columns: 1fr 2fr; }
.layout-grid.cols-sidebar-end { grid-template-columns: 2fr 1fr; }

/* Column count follows the available width rather than being declared.
   Use when the number of items is not known at build time. */
.layout-grid.cols-auto { grid-template-columns: repeat(auto-fit, minmax(18em, 1fr)); }

/* Gap overrides. Default is 2em, taken from the existing help card grid. */
.layout-grid.gap-small  { grid-column-gap: 1em; grid-row-gap: 1em; }
.layout-grid.gap-large  { grid-column-gap: 4em; grid-row-gap: 4em; }
.layout-grid.gap-xlarge { grid-column-gap: 6em; grid-row-gap: 6em; }

/* Vertical alignment within a row of unequal-height items. Named items-*
   rather than align-* because .align-center is already taken above, where
   it means margin auto. */
.layout-grid.items-start  { align-items: start; }
.layout-grid.items-center { align-items: center; }

@media screen and (max-width: 991px) {
  .layout-grid.cols-3,
  .layout-grid.cols-4 { grid-template-columns: 1fr 1fr; }
  .layout-grid.cols-sidebar,
  .layout-grid.cols-sidebar-end { grid-template-columns: 1fr; }
}

@media screen and (max-width: 767px) {
  .layout-grid.cols-2,
  .layout-grid.cols-3,
  .layout-grid.cols-4,
  .layout-grid.cols-auto { grid-template-columns: 1fr; }
  .layout-grid.gap-large,
  .layout-grid.gap-xlarge { grid-column-gap: 2.5em; grid-row-gap: 2.5em; }
}

/* =====================================================================
   COMPONENTS
   =====================================================================
   Every component here is extracted from a pattern the home page already
   uses, not invented. The point is that the guide describes the site we
   have rather than a second, parallel design language. Each block names
   the Webflow class it generalises, so if the original ever changes you
   know what to check.

   Shared traits pulled out of the export, applied consistently here:
     - large radius, 2.5em, not the 8-12px a component library defaults to
     - flat pastel fills, no borders and no shadows anywhere
     - large light display type, weight 300-500, not bold
   ===================================================================== */

:root {
  --zipli-radius-card: 2.5em;     /* .how-can-we-help-card */
  --zipli-radius-soft: 0.75em;    /* small surfaces, images */
}

/* ---------------------------------------------------------------------
   Section header
   Eyebrow, heading and lede as one unit. The home page rebuilds this by
   hand in every section, which is why the gap above the lede is a
   different value in each one. Centred by default because h2 is.
     <div class="section-header">
       <p class="section-header-eyebrow">Eyebrow</p>
       <h2>Heading</h2>
       <p class="section-header-lede">One or two sentences.</p>
     </div>
   --------------------------------------------------------------------- */
.section-header {
  width: 100%;
  max-width: 56em;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

.section-header.wide { max-width: 70em; }

.section-header.left {
  margin-left: 0;
  margin-right: 0;
  text-align: left;
}

.section-header.left h2,
.section-header.left .section-header-lede { text-align: left; }

.section-header-eyebrow {
  font-family: var(--zipli-font-body);
  font-size: 1.125em;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--zipli-grey);
  margin: 0 0 1em;
}

.section-header-lede {
  font-size: 1.5em;
  font-weight: 300;
  line-height: 1.4;
  margin: 1em 0 0;
}

/* ---------------------------------------------------------------------
   Feature card
   Generalises .how-can-we-help-card, the four-up on the home page. Same
   fill, radius and padding; the difference is that the icon and the title
   pill are optional and the fill is swappable.
     <div class="feature-card">
       <div class="feature-card-top">
         <div class="feature-card-icon"> svg </div>
         <div class="feature-card-tag"><p class="feature-card-tag-text">Label</p></div>
       </div>
       <div class="padding-small"></div>
       <h4>Title</h4>
       <p class="feature-card-body">Body copy.</p>
     </div>
   --------------------------------------------------------------------- */
.feature-card {
  background-color: var(--zipli-blush);
  border-radius: var(--zipli-radius-card);
  padding: 1.8em 1.8em 2.5em;
}

/* Fill variants. Blush is the default because that is what the home page
   uses. Dark inverts the text colour with it. */
.feature-card.lime  { background-color: var(--zipli-lime-soft); }
.feature-card.mist  { background-color: var(--zipli-mist); }
.feature-card.white { background-color: var(--zipli-white); }

.feature-card.dark {
  background-color: var(--zipli-ink);
  color: var(--zipli-white);
}

.feature-card.dark h4,
.feature-card.dark .feature-card-body { color: var(--zipli-white); }

.feature-card-top {
  display: flex;
  justify-content: space-between;
  align-items: stretch;
  grid-column-gap: 1em;
  grid-row-gap: 1em;
}

/* The white circle behind the icon. Sized from .how-can-we-help-icon-wrapper. */
.feature-card-icon {
  background-color: var(--zipli-white);
  border-radius: 50%;
  justify-content: center;
  align-items: center;
  width: 5.06em;
  height: 5.06em;
  padding: 1.05em;
  flex: none;
  display: flex;
}

.feature-card-icon svg,
.feature-card-icon img { width: 100%; height: auto; }

/* The white pill holding the category label. From .help-card-top-title-wrapper.
   The export set 2.5em of side padding and none at all top to bottom, so the
   height came only from the text's line box: a long thin lozenge. These are
   proportioned as a pill instead, sides a little over twice the top and
   bottom, which is what keeps the end caps reading as semicircles rather
   than as a stretched stadium. */
.feature-card-tag {
  background-color: var(--zipli-white);
  border-radius: 7.5em;
  justify-content: center;
  align-items: center;
  padding: 0.55em 1.25em;
  display: flex;
}

.feature-card-tag-text {
  font-size: 1.425em;
  font-weight: 400;
  margin: 0;
}

.feature-card-body {
  font-size: 1.5em;
  font-weight: 300;
  line-height: 1.3;
  margin: 0.6em 0 0;
}

/* ---------------------------------------------------------------------
   Media split
   Generalises .b2b-solution-card, the alternating image-and-text rows.
   Add .reverse to flip the image to the other side. Built on flex like
   the original rather than the grid above, because the two halves are
   not equal width and the image sizes itself.
     <div class="media-split">
       <div class="media-split-content"> heading + copy </div>
       <div class="media-split-media"><img ...></div>
     </div>
   --------------------------------------------------------------------- */
.media-split {
  justify-content: space-between;
  align-items: center;
  grid-column-gap: 4em;
  grid-row-gap: 4em;
  max-width: 84.8em;
  margin-left: auto;
  margin-right: auto;
  display: flex;
}

.media-split.reverse { flex-flow: row-reverse; }

.media-split-content {
  width: 100%;
  max-width: 38em;
}

.media-split-media { width: 100%; max-width: 38em; }

.media-split-media img {
  width: 100%;
  height: auto;
  display: block;
}

/* Rounded photo variant. The home page product shots are cut-outs with no
   background, so they get no radius; a real photograph needs one. */
.media-split-media.rounded img { border-radius: var(--zipli-radius-card); }

/* Phone variant, for a portrait app screenshot. At the default 38em max
   width a ~9:19.5 screenshot runs far taller than the text column beside
   it, so this narrows the image instead of letting it set the row's
   height. */
.media-split-media.phone { max-width: 18em; margin: 0 auto; }

/* A raw screenshot with no device around it reads as a floating rectangle.
   A tighter radius plus a hairline border and a soft lift give it just
   enough of a device edge to feel like a phone, without building a full
   bezel/notch mockup. */
.media-split-media.phone img {
  border-radius: 1.8em;
  border: 1px solid rgba(0, 0, 0, 0.08);
  box-shadow: 0 1.5em 3em -1.2em rgba(0, 0, 0, 0.3);
}

.media-split-title {
  text-align: left;
  font-family: var(--zipli-font-body);
  font-size: 3em;
  font-weight: 300;
  line-height: 1.2;
  margin: 0;
}

@media screen and (max-width: 767px) {
  .media-split,
  .media-split.reverse {
    flex-flow: column;
    grid-column-gap: 2.5em;
    grid-row-gap: 2.5em;
  }
  .media-split-content,
  .media-split-media { max-width: none; }
  .media-split-title { font-size: 2.25em; }
}

/* ---------------------------------------------------------------------
   Step list
   Generalises the .how-it-work-timeline-card row in the black section.
   A numbered circle beside a title. Horizontal by default, matching the
   home page; add .stacked for a vertical list.
     <div class="step-list">
       <div class="step-item">
         <div class="step-number">1</div>
         <h4 class="step-title">Do the thing</h4>
       </div>
     </div>
   --------------------------------------------------------------------- */
.step-list {
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  grid-column-gap: 4em;
  grid-row-gap: 4em;
  flex-wrap: wrap;
}

.step-list.stacked {
  flex-flow: column;
  grid-column-gap: 2.5em;
  grid-row-gap: 2.5em;
}

/* Columns variant. The default row puts the number beside the title, which
   works when a step is two or three words. Once each step carries a
   sentence underneath, side by side leaves the numbers stranded in a
   narrow gutter down the left and the text running the full page width.
   This turns each step into a column with the number above its title,
   which is how a three-step explainer usually reads. Use it for three or
   four steps; beyond that the columns get too narrow to hold a sentence. */
.step-list.columns {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-column-gap: 3em;
  grid-row-gap: 3em;
  align-items: start;
}

.step-list.columns.four { grid-template-columns: repeat(4, 1fr); }

.step-list.columns .step-item {
  flex-direction: column;
  align-items: flex-start;
  grid-column-gap: 1.2em;
  grid-row-gap: 1.2em;
}

/* In a row the title has no sentence under it, so it needs no space
   below. In a column it does. */
.step-list.columns .step-title { margin-bottom: 0.5em; }

@media screen and (max-width: 991px) {
  .step-list.columns,
  .step-list.columns.four { grid-template-columns: 1fr 1fr; }
}

@media screen and (max-width: 767px) {
  .step-list.columns,
  .step-list.columns.four { grid-template-columns: 1fr; }
}

.step-item {
  display: flex;
  align-items: center;
  grid-column-gap: 1.5em;
  grid-row-gap: 1.5em;
}

/* Lime-soft circle, from .numbering-box. */
.step-number {
  background-color: var(--zipli-lime-soft);
  border-radius: 50%;
  justify-content: center;
  align-items: center;
  width: 3.5em;
  height: 3.5em;
  flex: none;
  font-family: var(--zipli-font-display);
  font-size: 1.5em;
  font-weight: 500;
  color: var(--zipli-black);
  display: flex;
}

.step-title {
  font-family: var(--zipli-font-body);
  font-size: 2.5em;
  font-weight: 400;
  line-height: 1.2;
  margin: 0;
}

/* The home page runs this on the black section, so give it an inverted
   variant rather than making callers write a colour override. */
.step-list.on-dark .step-title { color: var(--zipli-white); }

@media screen and (max-width: 767px) {
  .step-list { flex-flow: column; grid-column-gap: 2.5em; grid-row-gap: 2.5em; }
  .step-title { font-size: 1.75em; }
}

/* The home page testimonial (Heta Hyvärinen) still uses the raw Webflow
   export classes rather than .pull-quote below, and the export's 3em/2em
   sizing runs heavier than the rest of the page's editorial quotes. */
.project-manager-quate { font-size: 2em; overflow-wrap: break-word; }
.project-manager-profile-info { font-size: 1.375em; }

@media screen and (max-width: 767px) {
  /* This unqualified 2em rule above beats the Webflow export's own
     1.5em mobile rule regardless of source order (same specificity,
     later in the cascade), so the FI quote ("...vuoteen 2030
     mennessä.") was overflowing the green card on phones. */
  .project-manager-quate { font-size: 1.5em; }
}

/* ---------------------------------------------------------------------
   Pull quote
   Generalises the .project-manager-quate block. A large light quote with
   an attribution, optionally beside a portrait. Pair with .media-split
   when you want the portrait alongside.
     <blockquote class="pull-quote">
       <p class="pull-quote-text">Quote.</p>
       <p class="pull-quote-attrib">Name</p>
       <p class="pull-quote-role">Role, Organisation</p>
     </blockquote>
   --------------------------------------------------------------------- */
/* Webflow's base stylesheet gives every blockquote a 5px grey border-left
   and fixed px padding (webflow.css:268). That contradicts the no-borders
   rule this system follows, and the px padding does not scale, so reset
   both here rather than at each call site. */
.pull-quote {
  margin: 0;
  padding: 0;
  border-left: 0;
}

.pull-quote-text {
  text-align: left;
  font-family: var(--zipli-font-display);
  font-size: 3em;
  font-weight: 500;
  line-height: 1.2;
  margin: 0;
}

.pull-quote-attrib {
  font-family: var(--zipli-font-display);
  font-size: 2em;
  font-weight: 500;
  line-height: 1.3;
  margin: 1em 0 0;
}

.pull-quote-role {
  font-family: var(--zipli-font-body);
  font-size: 1.5em;
  font-weight: 300;
  line-height: 1.3;
  color: var(--zipli-grey);
  margin: 0.25em 0 0;
}

.pull-quote.on-dark .pull-quote-text,
.pull-quote.on-dark .pull-quote-attrib { color: var(--zipli-white); }
.pull-quote.on-dark .pull-quote-role { color: var(--zipli-line); }

@media screen and (max-width: 767px) {
  .pull-quote-text { font-size: 2em; }
  .pull-quote-attrib { font-size: 1.5em; }
}

/* Paired variant: two testimonials side by side in a .layout-grid.cols-2.
   The full 3em treatment is sized for a quote that owns the full measure,
   and in half the width it wraps to eight or nine lines and shouts over
   everything around it. This is the same quote, not a different object, so
   it keeps the display face and the weight and only steps the size down.
   It is not .small, which is italic and is for a one-line tagline. */
.pull-quote.paired .pull-quote-text {
  font-size: 1.75em;
  line-height: 1.3;
}

.pull-quote.paired .pull-quote-attrib { font-size: 1.25em; }

@media screen and (max-width: 767px) {
  .pull-quote.paired .pull-quote-text { font-size: 1.5em; }
}

/* Small variant: a one-line personal tagline rather than an editorial
   pull quote, e.g. a founder motto under a bio. Same shape, quieter type. */
.pull-quote.small .pull-quote-text {
  font-size: 1.5em;
  font-weight: 400;
  font-style: italic;
}

.pull-quote.small .pull-quote-attrib {
  font-size: 1.125em;
  margin-top: 0.75em;
}


/* ---------------------------------------------------------------------
   Person card
   Generalises the pair of contacts in the CTA section. Portrait, name,
   role and a contact line. The original sets letter-spacing in px, which
   is one of the fixed-unit bugs, so this uses em instead.
     <div class="person-card">
       <img class="person-card-photo" src="..." alt="">
       <h4 class="person-card-name">Name</h4>
       <p class="person-card-role">Role</p>
       <a class="person-card-link" href="mailto:...">email</a>
     </div>
   --------------------------------------------------------------------- */
.person-card { text-align: left; }

.person-card-photo {
  width: 100%;
  height: auto;
  border-radius: var(--zipli-radius-card);
  display: block;
  margin-bottom: 1.5em;
}

.person-card-name {
  font-family: var(--zipli-font-display);
  font-size: 2.5em;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.03em;
  margin: 0;
}

.person-card-role {
  font-family: var(--zipli-font-body);
  font-size: 1.5em;
  font-weight: 300;
  line-height: 1.3;
  color: var(--zipli-grey);
  margin: 0.3em 0 0;
}

.person-card-link {
  font-family: var(--zipli-font-body);
  font-size: 1.5em;
  font-weight: 400;
  line-height: 1.3;
  color: var(--zipli-black);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  display: inline-block;
  margin-top: 0.5em;
  transition: color 0.3s;
}

.person-card-link:hover { color: var(--zipli-green); }

.person-card.on-dark .person-card-name,
.person-card.on-dark .person-card-link { color: var(--zipli-white); }
.person-card.on-dark .person-card-role { color: var(--zipli-line); }
.person-card.on-dark .person-card-link:hover { color: var(--zipli-lime); }

/* =====================================================================
   COMPONENTS, TIER 2
   =====================================================================
   The stat band and the image card extend patterns the export already
   had. The logo cloud, the accordion and the CTA band are genuinely new,
   because the export has no precedent for them, so they are built from
   the same three traits as everything above: 2.5em radius, flat fills
   with no borders or shadows, and large light type.
   ===================================================================== */

/* ---------------------------------------------------------------------
   Stat band
   The number styling is lifted from .box-title, the big figure in the
   black hero-bottom box: Space Grotesk 500 at a tight line-height with
   negative tracking. The original sets letter-spacing in px, which does
   not scale, so this uses em.
     <div class="stat-band layout-grid cols-3">
       <div class="stat-item">
         <p class="stat-number">1/3</p>
         <p class="stat-label">of all food produced is wasted</p>
       </div>
     </div>
   --------------------------------------------------------------------- */
.stat-band { width: 100%; }

.stat-item { text-align: left; }
.stat-item.center { text-align: center; }

.stat-number {
  font-family: var(--zipli-font-display);
  font-size: 8em;
  font-weight: 500;
  line-height: 0.93;
  letter-spacing: -0.04em;
  color: var(--zipli-black);
  margin: 0;
}

/* The 8em figure is right for two or three across. Four across at that
   size overflows on a laptop, so step it down. */
.stat-band.compact .stat-number { font-size: 5.5em; }

.stat-label {
  font-size: 2.25em;
  font-weight: 300;
  line-height: 1.2;
  margin: 0.65em 0 0;
}

.stat-band.on-dark .stat-number,
.stat-band.on-dark .stat-label { color: var(--zipli-white); }

/* .lime colours just the figure, and only works on a dark ground: lime on
   white is 1.28:1, well under the 3:1 that large text needs, so the number
   all but disappears. On a light section use .green, which is the same
   brand accent at 7.8:1 and is already the colour of the CTA label and the
   read-more link. */
.stat-band.lime .stat-number { color: var(--zipli-lime); }

.stat-band.green .stat-number { color: var(--zipli-green); }

@media screen and (max-width: 991px) {
  .stat-number { font-size: 5.5em; }
  .stat-band.compact .stat-number { font-size: 4em; }
}

/* ---------------------------------------------------------------------
   Logo cloud
   For partner, customer and funder marks. Sized by height rather than
   width so marks of different proportions sit on one optical line, which
   is the thing most logo rows get wrong.

   Deliberately no greyscale-to-colour hover. The marks used here are
   already flat black, and the effect is the single clearest tell of a
   template site.
     <div class="logo-cloud">
       <img class="logo-cloud-item" src="..." alt="City of Helsinki">
     </div>
   --------------------------------------------------------------------- */
.logo-cloud {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  grid-column-gap: 5.5em;
  grid-row-gap: 3em;
}

.logo-cloud.left { justify-content: flex-start; }

.logo-cloud-item {
  height: 4em;
  width: auto;
  max-width: 14em;
  object-fit: contain;
  flex: none;
}

/* Marks with a lot of internal padding, or a tall stacked lockup, need to
   run larger to look the same weight as a plain wordmark. */
.logo-cloud-item.tall { height: 6em; }

.logo-cloud.on-dark .logo-cloud-item { filter: invert(1); }

@media screen and (max-width: 767px) {
  .logo-cloud { grid-column-gap: 3em; grid-row-gap: 2.5em; }
  .logo-cloud-item { height: 3em; }
  .logo-cloud-item.tall { height: 4.5em; }
}

/* ---------------------------------------------------------------------
   FAQ accordion
   Built on <details> and <summary>, so it opens and closes with no
   JavaScript at all and is keyboard accessible for free. The only work
   here is hiding the browser's default triangle and drawing our own
   plus, which becomes a minus when the item is open.
     <div class="faq-list">
       <details class="faq-item">
         <summary class="faq-question">Question text</summary>
         <div class="faq-answer"><p>Answer.</p></div>
       </details>
     </div>
   --------------------------------------------------------------------- */
.faq-list {
  width: 100%;
  max-width: 60em;
}

.faq-item {
  background-color: var(--zipli-blush);
  border-radius: var(--zipli-radius-card);
  padding: 1.8em 2.5em;
  margin-bottom: 1em;
}

.faq-item.mist { background-color: var(--zipli-mist); }

.faq-question {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  grid-column-gap: 2em;
  padding-right: 0.5em;
  font-family: var(--zipli-font-body);
  font-size: 2em;
  font-weight: 400;
  line-height: 1.3;
  cursor: pointer;
  list-style: none;
}

/* Both are needed: the pseudo-element for WebKit, list-style for the rest. */
.faq-question::-webkit-details-marker { display: none; }

/* The plus. Two gradient layers on one box, so there is no icon file to
   keep in sync and it inherits the current colour. Layer one is the
   horizontal bar, layer two the vertical one. */
.faq-question::after {
  content: "";
  flex: none;
  width: 0.75em;
  height: 0.75em;
  background:
    linear-gradient(currentColor, currentColor) center no-repeat,
    linear-gradient(currentColor, currentColor) center no-repeat;
  background-size: 100% 0.09em, 0.09em 100%;
  transition: background-size 0.3s;
}

/* Open collapses the vertical bar to nothing, leaving the horizontal one,
   so the plus becomes a minus. Note this cannot be done by rotating the
   box: a plus turned a quarter turn is still a plus. */
.faq-item[open] .faq-question::after {
  background-size: 100% 0.09em, 0.09em 0;
}

.faq-answer {
  font-size: 1.5em;
  font-weight: 300;
  line-height: 1.4;
  padding-top: 0.5em;
  max-width: 34em;
}

.faq-answer p { margin: 0; font-size: inherit; line-height: inherit; }

/* An answer can point at another page. Same quiet treatment as .plan-note:
   Webflow's default blue underline is louder than the answer around it. */
.faq-answer a {
  color: var(--zipli-green);
  font-weight: 500;
  text-decoration: none;
}

.faq-answer a:hover { text-decoration: underline; }

@media screen and (max-width: 767px) {
  .faq-item { padding: 1.5em 1.8em; }
  .faq-question { font-size: 1.5em; }
}

/* ---------------------------------------------------------------------
   Image card
   The feature card with a picture on top instead of an icon. The image
   sits flush to the card edges, so it takes the card radius on its top
   two corners and the padding moves onto an inner wrapper.
     <div class="feature-card image-card">
       <img class="image-card-media" src="..." alt="">
       <div class="image-card-body">
         <h4>Title</h4>
         <p class="feature-card-body">Copy.</p>
       </div>
     </div>
   --------------------------------------------------------------------- */
.feature-card.image-card { padding: 0; overflow: hidden; }

.image-card-media {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  display: block;
}

/* Portrait crop, for a person rather than a scene. */
.image-card-media.portrait { aspect-ratio: 1; }

.image-card-body { padding: 1.8em 1.8em 2.5em; }

/* ---------------------------------------------------------------------
   CTA band
   A closing call to action. Lime by default because that is the brand
   accent, dark as the alternative. Rounded rather than full-bleed, so it
   reads as a block on the page like every other surface here.
     <div class="cta-band">
       <h2 class="cta-band-title">Heading</h2>
       <p class="cta-band-lede">Supporting line.</p>
       <a href="#" class="main-button w-button">Book a demo →</a>
     </div>
   --------------------------------------------------------------------- */
.cta-band {
  background-color: var(--zipli-lime);
  border-radius: var(--zipli-radius-card);
  padding: 5.5em 4em;
  text-align: center;
}

.cta-band.dark { background-color: var(--zipli-black); }
.cta-band.blush { background-color: var(--zipli-blush); }

.cta-band-title {
  font-family: var(--zipli-font-display);
  font-size: 4em;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.03em;
  color: var(--zipli-black);
  margin: 0;
  text-align: center;
  overflow-wrap: break-word;
}

/* The donor band's question reads more like a qualifier than a headline,
   so it doesn't need the full 4em .cta-band-title weight the other bands
   (the app download CTA) use. */
.donor-band .cta-band-title { font-size: 2.75em; }

.cta-band-lede {
  font-size: 1.75em;
  font-weight: 300;
  line-height: 1.4;
  max-width: 30em;
  margin: 0.8em auto 0;
}

.cta-band-actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  grid-column-gap: 1.5em;
  grid-row-gap: 1.5em;
  margin-top: 2.5em;
}

.cta-band.dark .cta-band-title,
.cta-band.dark .cta-band-lede { color: var(--zipli-white); }

@media screen and (max-width: 767px) {
  .cta-band { padding: 3em 1.8em; }
  .cta-band-title { font-size: 2.5em; }

  /* .donor-band .cta-band-title (two classes) outranks the rule above
     (one class) on specificity regardless of source order, so the donor
     band's title was staying at its wider-viewport 2.75em size here and
     overflowing the band's own padding. Needs its own mobile override. */
  .donor-band .cta-band-title { font-size: 1.9em; }
}

/* ---------------------------------------------------------------------
   Page hero

   The home page hero is a full-height statement: 5em heading, 18em of
   padding above it, nothing else. A subpage cannot afford that, it has to
   say what the page is about before the fold. So this is the same hero
   shell on the same photograph, only shorter, with room for a lede line
   under the heading. Put .page on the wrapper and the lede inside it.
   --------------------------------------------------------------------- */

.hero-wrapper.page {
  padding-top: 12em;
  padding-bottom: 8em;
}

/* A subpage headline is a sentence, not a two-word statement, so it needs a
   step down from the 5em the home page uses or it fills the screen. */
.heading.page {
  max-width: 16em;
  text-align: center;
  font-size: 3.75em;
  line-height: 1.05;
}

.hero-lede {
  max-width: 46em;
  color: var(--zipli-white);
  text-align: center;
  font-size: 1.75em;
  font-weight: 300;
  line-height: 1.4;
  margin: 0;
}

@media screen and (max-width: 991px) {
  .hero-wrapper.page {
    padding-top: 9em;
    padding-bottom: 5em;
  }
}

/* ---------------------------------------------------------------------
   Nav gap

   The Webflow nav sets a 7em gap between links, which was tuned for the
   four anchor links the home page used to be the whole site. The sitemap
   now carries three audience pages plus How it works and Contact, and at
   five items that gap pushes the first link into the logo and wraps the
   longest label. Narrowing the gap fits five without touching the type.

   Non-English keeps its own tighter gap in the lang-scoped block above,
   which outranks this on specificity, so this only affects English.
   --------------------------------------------------------------------- */

@media screen and (min-width: 992px) {
  .nav-menu-two {
    grid-column-gap: 3em;
    grid-row-gap: 3em;
  }
}

/* ---------------------------------------------------------------------
   Nav link and CTA type, scaled down from the Webflow export's 2em / 1.3125em
   (looked oversized against the rest of the nav bar). Desktop only, so
   tablet/mobile keep the export's own smaller breakpoint sizes.
   --------------------------------------------------------------------- */
@media screen and (min-width: 992px) {
  .nav-link {
    font-size: 1.5em;
  }

  .nav-cta-text {
    font-size: 1.15em;
  }
}

/* ---------------------------------------------------------------------
   Nav dropdown
   "Resources" groups Blog and Find a Terminal under one nav item. Built
   on <details>/<summary>, the same no-JS pattern as .faq-item, so it
   opens and closes with no extra script and stays keyboard reachable.
   The toggle reuses .nav-link so it matches every other top-level item;
   only the caret and the menu underneath are new.

   Below 992px the nav collapses into Webflow's full-screen white menu
   (.nav-menu-two), so the dropdown just expands inline there, pushing
   the items below it down, the same way a mobile accordion behaves.
   At 992px and up it floats as a small white card under the toggle so
   opening it does not shift the rest of the nav row.
   --------------------------------------------------------------------- */
.nav-dropdown-toggle {
  display: flex;
  align-items: center;
  gap: 0.4em;
  cursor: pointer;
  list-style: none;
}

.nav-dropdown-toggle::-webkit-details-marker { display: none; }

.nav-dropdown-caret {
  width: 0.6em;
  height: 0.6em;
  flex: none;
  border-right: 0.14em solid currentColor;
  border-bottom: 0.14em solid currentColor;
  transform: rotate(45deg);
  margin-top: -0.3em;
  transition: transform 0.2s;
}

.nav-dropdown[open] .nav-dropdown-caret {
  transform: rotate(-135deg);
  margin-top: 0.15em;
}

.nav-dropdown-menu {
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav-dropdown-link {
  display: block;
  font-family: var(--zipli-font-body);
  text-decoration: none;
  transition: color 0.3s;
}

@media screen and (max-width: 991px) {
  .nav-dropdown-menu {
    padding: 0.75em 0 0 1em;
    display: grid;
    gap: 0.75em;
  }

  .nav-dropdown-link {
    color: var(--zipli-green);
    font-size: 1.25em;
    font-weight: 500;
  }

  .nav-dropdown-link:hover { color: var(--zipli-black); }
}

@media screen and (min-width: 992px) {
  .nav-item-dropdown { position: relative; }

  .nav-dropdown-menu {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 1em;
    min-width: 12em;
    background-color: var(--zipli-white);
    border-radius: var(--zipli-radius-soft);
    padding: 0.75em;
    display: grid;
    gap: 0.25em;
  }

  .nav-dropdown-link {
    color: var(--zipli-green);
    font-size: 1.125em;
    font-weight: 500;
    padding: 0.5em 0.75em;
    border-radius: 0.4em;
  }

  .nav-dropdown-link:hover {
    color: var(--zipli-green);
    background-color: var(--zipli-blush);
  }
}

/* ---------------------------------------------------------------------
   Ordered step list
   A numbered instructional list, sized to match feature-card body copy.
   Webflow's base ol/ul (webflow.css:283) sets padding-left in px, which
   breaks the em-scaling rule, so this overrides it for callers who need
   a real <ol> instead of the .step-list circle-and-title component.
   Pairs naturally with .media-split for "numbered steps beside a
   screenshot".
     <ol class="step-ordered-list">
       <li>Do the first thing.</li>
     </ol>
   --------------------------------------------------------------------- */
.step-ordered-list {
  font-size: 1.5em;
  font-weight: 300;
  line-height: 1.5;
  padding-left: 1.4em;
  margin: 0;
}

.step-ordered-list li { margin-bottom: 0.75em; }
.step-ordered-list li:last-child { margin-bottom: 0; }
.step-ordered-list li::marker { font-weight: 500; color: var(--zipli-green); }

/* =====================================================================
   BLOG
   =====================================================================
   The article template and its listing card. No blog existed in the
   Webflow export, so these are new rather than generalised from an
   existing block, but every value reuses what is already on the page:
   .section-header-eyebrow for the theme label, .feature-card for the
   listing card, .pull-quote for in-article quotes, .cta-band for the
   closing CTA. Nothing here introduces a new type size, radius or fill
   the system doesn't already have.
   ===================================================================== */

/* ---------------------------------------------------------------------
   Article hero
   Same shell as .hero-wrapper.page (see How it works), with an eyebrow
   naming the audience above the h1.
     <div class="hero-wrapper page">
       <p class="section-header-eyebrow on-dark">For terminal coordinators</p>
       <h1 class="heading page">Title</h1>
       <div class="padding-medium"></div>
       <p class="hero-lede">One sentence of context.</p>
     </div>
   --------------------------------------------------------------------- */
.section-header-eyebrow.on-dark { color: var(--zipli-lime-soft); }

.hero-wrapper.page .section-header-eyebrow {
  text-align: center;
  margin: 0 0 0.75em;
}

/* ---------------------------------------------------------------------
   Article meta
   Author, publish date and read time, in the white space between the
   hero and the first paragraph. Avatar reuses .person-card-photo sizing
   logic at a smaller scale rather than a new size.
     <div class="article-meta">
       <img class="article-meta-avatar" src="{{base}}images/people/name.png" alt="Author name">
       <div>
         <p class="article-meta-name">Author Name</p>
         <p class="article-meta-detail">19 August 2026 &middot; 7 min read</p>
       </div>
     </div>
   --------------------------------------------------------------------- */
.article-meta {
  display: flex;
  align-items: center;
  grid-column-gap: 1em;
  width: 100%;
  max-width: 48em;
  margin: 0 auto 2.5em;
}

.article-meta-avatar {
  width: 4em;
  height: 4em;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}

.article-meta-name {
  font-size: 1.125em;
  font-weight: 500;
  margin: 0;
}

.article-meta-detail {
  font-size: 1.125em;
  font-weight: 300;
  color: var(--zipli-grey);
  margin: 0.2em 0 0;
}

/* ---------------------------------------------------------------------
   Article body
   Wraps the running text of a blog post. Left-aligned and narrower than
   a full section, because h2 is centred site-wide by default and a
   48em column reads like body copy, not a page section. h3 in
   zipli.webflow.css is fixed px (24px/30px), which breaks the em-scaling
   rule, so it is redeclared here in em.
     <div class="article-body">
       <p>Lede paragraph.</p>
       <h2>Section heading</h2>
       <p>Body copy.</p>
       <h3>Subheading</h3>
       <p>Body copy.</p>
       <ul><li>...</li></ul>
       <blockquote class="pull-quote">...</blockquote>
       <p class="article-sources">Source: ...</p>
     </div>
   --------------------------------------------------------------------- */
.article-body {
  width: 100%;
  max-width: 56em;
  margin: 0 auto;
  text-align: left;
}

.article-body > * + * { margin-top: 1.2em; }

/* Lede: the opening line, set larger and heavier than running body copy
   so it reads as the hook rather than the first line of the argument,
   the way a magazine or news site opens a piece. */
.article-body > p:first-of-type {
  font-size: 1.875em;
  font-weight: 400;
  line-height: 1.4;
}

.article-body h2 {
  text-align: left;
  font-size: 2em;
  line-height: 1.25;
  margin-top: 1.8em;
}

.article-body h3 {
  font-family: var(--zipli-font-display);
  text-align: left;
  font-size: 1.375em;
  font-weight: 500;
  line-height: 1.3;
  margin-top: 1.5em;
}

.article-body p,
.article-body li {
  font-size: 1.5em;
  font-weight: 300;
  line-height: 1.6;
}

/* ---------------------------------------------------------------------
   In-article read-more
   Built on <details>/<summary>, the same no-JS pattern as .faq-item and
   .nav-dropdown, so it expands in place with no JavaScript and is
   keyboard accessible for free. Once open there's nothing left to
   collapse back to, so the toggle just disappears rather than turning
   into a "show less" - the content itself is now the ending.
     <div class="article-body">
       <p>Always visible.</p>
       <details class="article-more">
         <summary class="article-more-toggle">Lue lisää</summary>
         <div class="article-more-content">
           <p>Revealed on click.</p>
         </div>
       </details>
     </div>
   --------------------------------------------------------------------- */
.article-more-toggle {
  display: inline-block;
  font-size: 1.5em;
  font-weight: 500;
  color: var(--zipli-green);
  cursor: pointer;
  list-style: none;
}

.article-more-toggle::-webkit-details-marker { display: none; }
.article-more-toggle:hover { text-decoration: underline; }

.article-more[open] .article-more-toggle { display: none; }

/* Revealed content sits one element deeper than .article-body's own
   children, so it needs its own copy of the same child spacing rule. */
.article-more-content > * + * { margin-top: 1.2em; }

.article-body ul,
.article-body ol {
  padding-left: 1.4em;
  margin: 0;
}

.article-body li + li { margin-top: 0.5em; }
.article-body li::marker { font-weight: 500; color: var(--zipli-green); }

.article-body a {
  color: var(--zipli-blue);
  text-decoration: underline;
}

.article-body a:hover { color: var(--zipli-green); }

.article-body .pull-quote {
  margin-top: 2em;
  margin-bottom: 0;
}

/* Source citations at the end of a post. Small and grey, like .sg-note,
   so it reads as reference material rather than part of the argument. */
.article-sources {
  font-size: 1em !important;
  font-weight: 400 !important;
  line-height: 1.5 !important;
  color: var(--zipli-grey);
  border-top: 1px solid var(--zipli-line);
  padding-top: 1.5em;
  margin-top: 3em !important;
}

.article-sources a { color: var(--zipli-grey); }

/* Same-theme cross-links at the end of a post, before the sources line. */
.article-crosslinks {
  border-top: 1px solid var(--zipli-line);
  padding-top: 1.5em;
  margin-top: 3em !important;
}

.article-crosslinks-label {
  font-size: 1.125em !important;
  font-weight: 500 !important;
  color: var(--zipli-grey);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 0.75em !important;
}

/* Each related-post link gets a lime dot instead of a generic bullet, and
   reads as a link list rather than another inline citation, so it drops
   the blue/underline treatment .article-body a uses for in-text sources. */
.article-crosslinks p:not(.article-crosslinks-label) {
  position: relative;
  padding-left: 1.3em;
  margin-top: 0.6em !important;
}

.article-crosslinks p:not(.article-crosslinks-label)::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: var(--zipli-lime);
}

.article-crosslinks p:not(.article-crosslinks-label) a {
  color: var(--zipli-ink);
  font-weight: 500;
  text-decoration: none;
}

.article-crosslinks p:not(.article-crosslinks-label) a:hover {
  color: var(--zipli-green);
  text-decoration: underline;
}

@media screen and (max-width: 767px) {
  .article-body h2 { font-size: 1.6em; }
  .article-body h3 { font-size: 1.25em; }
  .article-body p,
  .article-body li { font-size: 1.25em; }
  .article-more-toggle { font-size: 1.25em; }
}

/* ---------------------------------------------------------------------
   Blog card
   .feature-card.image-card (see Image card above) with a theme tag and
   a length badge, for the /blog/ index. Three cards can share a topic
   (short/medium/long), so the badge is what tells them apart at a
   glance.
     <a href="..." class="feature-card image-card blog-card mist">
       <img class="image-card-media" src="{{base}}images/blog/slug.jpg" alt="...">
       <div class="image-card-body">
         <div class="feature-card-top">
           <div class="feature-card-tag"><p class="feature-card-tag-text">Theme</p></div>
           <p class="blog-card-length">Short read &middot; 2 min</p>
         </div>
         <div class="padding-small"></div>
         <h4>Title</h4>
         <p class="feature-card-body">Dek.</p>
         <p class="blog-card-readmore">Read more →</p>
       </div>
     </a>
   --------------------------------------------------------------------- */
a.feature-card.blog-card {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: transform 0.15s ease;
}

/* The tag pill and the "Short read · 2 min" text are each one line, not
   two columns fighting for space, so let the pair wrap onto its own line
   rather than squeezing the pill's text mid-word. */
.blog-card .feature-card-top { flex-wrap: wrap; }
.blog-card .feature-card-tag { flex: none; }
.blog-card .feature-card-tag-text { white-space: nowrap; }

a.feature-card.blog-card:hover { transform: translateY(-0.25em); }

.blog-card-length {
  font-size: 1.125em;
  font-weight: 500;
  color: var(--zipli-grey);
  align-self: center;
  white-space: nowrap;
  margin: 0;
}

.blog-card .feature-card-body {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* CTA text link at the foot of a card. Same green as .nav-cta-text, so it
   reads as an action rather than more body copy. */
.blog-card-readmore {
  display: inline-block;
  font-size: 1.125em;
  font-weight: 500;
  color: var(--zipli-green);
  margin-top: 1em !important;
}

a.feature-card.blog-card:hover .blog-card-readmore { text-decoration: underline; }

/* ---------------------------------------------------------------------
   Blog theme group
   One .section-header per theme on the /blog/ index, left-aligned so
   five stacked sections don't read as five separate hero moments.
   --------------------------------------------------------------------- */
.blog-theme-group + .blog-theme-group { margin-top: 5.5em; }

/* ---------------------------------------------------------------------
   Photo placeholder
   Stand-in for a real photograph that hasn't been dropped in yet. Same
   pastel-fill, large-radius language as .feature-card, so an unfilled
   slot still looks like part of the system rather than a broken image.
   Swap for a real <img> in .media-split-media (or drop straight in)
   using the same aspect ratio; delete the icon and the placeholder line.
   Cap the height with max-height alongside aspect-ratio on a wide slot
   (21/9, 16/9), so a wide placeholder never grows into a tall block of
   near-empty colour with the caption stranded in the middle of it.
     <div class="photo-placeholder" style="aspect-ratio:4/3">
       <svg class="photo-placeholder-icon" ...></svg>
       <p class="photo-placeholder-label">Caption of the intended photo.</p>
     </div>
   --------------------------------------------------------------------- */
.photo-placeholder {
  width: 100%;
  background-color: var(--zipli-blush);
  border-radius: var(--zipli-radius-card);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 0.75em;
  padding: 2em;
}

.photo-placeholder.lime  { background-color: var(--zipli-lime-soft); }
.photo-placeholder.mist  { background-color: var(--zipli-mist); }

/* For a placeholder sitting inside a coloured card, where the default
   blush fill competes with the card rather than reading as an empty
   media slot. */
.photo-placeholder.white { background-color: var(--zipli-white); }

.photo-placeholder-icon {
  width: 2.25em;
  height: 2.25em;
  color: var(--zipli-grey);
  opacity: 0.6;
  flex: none;
}

.photo-placeholder-label {
  font-size: 1.125em;
  font-weight: 500;
  color: var(--zipli-grey);
  max-width: 24em;
  margin: 0;
}

/* ---------------------------------------------------------------------
   Plan card
   A pricing tier. Built as a modifier on .feature-card, the same way
   .image-card is, so the radius, padding and the four fills come for
   free and a row of tiers is just a .layout-grid of them. The price
   figure is .stat-number's treatment stepped down: Space Grotesk 500,
   tight line-height, negative tracking. Nothing here is bordered or
   shadowed, so the "recommended" tier is marked with a fill rather than
   with an outline the rest of the system never uses.
     <div class="layout-grid cols-3">
       <div class="feature-card plan-card white">
         <p class="plan-name">Mini</p>
         <p class="plan-scope">1 to 3 sites</p>
         <p class="plan-price">&euro;87<span class="plan-price-unit">/site/mo</span></p>
         <ul class="plan-features">
           <li>Connects sites to one data platform</li>
         </ul>
       </div>
     </div>
   The feature list is pushed to the foot of the card, so the lists line
   up across a row even when one tier has a longer price line than the
   next. Collapse is inherited from .layout-grid, which is why there are
   no breakpoints of its own here.
   --------------------------------------------------------------------- */
.feature-card.plan-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 2.2em 1.8em 2.5em;
}

.plan-name {
  font-family: var(--zipli-font-display);
  font-size: 2em;
  font-weight: 500;
  line-height: 1.2;
  margin: 0;
}

.plan-scope {
  font-size: 1.125em;
  font-weight: 400;
  line-height: 1.3;
  color: var(--zipli-grey);
  margin: 0.4em 0 0;
}

.plan-price {
  font-family: var(--zipli-font-display);
  font-size: 4em;
  font-weight: 500;
  line-height: 0.95;
  letter-spacing: -0.04em;
  margin: 0.35em 0 0;
}

/* The unit rides on the figure, so it is sized as a fraction of it and
   drops back to body type: at 4em a matching display unit reads as part
   of the number rather than as a qualifier. */
.plan-price-unit {
  font-family: var(--zipli-font-body);
  font-size: 0.28em;
  font-weight: 300;
  letter-spacing: 0;
  margin-left: 0.35em;
}

/* A tier with no number. "Ask for a quote" set at 4em wraps to three
   lines and shouts louder than the tiers that do have a price. */
.plan-price.quote {
  font-size: 2.25em;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

.plan-features {
  list-style: none;
  margin: auto 0 0;
  padding: 1.6em 0 0;
}

.plan-features li {
  position: relative;
  font-size: 1.25em;
  font-weight: 300;
  line-height: 1.35;
  padding-left: 1.3em;
}

.plan-features li + li { margin-top: 0.6em; }

/* Same lime dot as .article-crosslinks, for the same reason: a list marker
   that belongs to this system rather than the browser's bullet. */
.plan-features li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.5em;
  width: 0.45em;
  height: 0.45em;
  border-radius: 50%;
  background: var(--zipli-lime);
}

/* Lime on lime disappears, so the dot goes deep green on the lime fill. */
.feature-card.lime .plan-features li::before { background: var(--zipli-green); }

/* .feature-card.dark only inverts h4 and .feature-card-body, because those
   were the only text parts it had. The plan card's parts need it too. */
.feature-card.dark .plan-name,
.feature-card.dark .plan-price,
.feature-card.dark .plan-features li { color: var(--zipli-white); }
.feature-card.dark .plan-scope { color: var(--zipli-line); }

/* Wide variant: one tier on its own rather than a row of them, for a page
   where there is a single price (kitchens) or none yet (terminals). Same
   card, turned on its side so a lone tier does not sit as a narrow column
   with three quarters of the row empty beside it. Needs the extra
   .plan-card-head wrapper; the column version does not. */
.feature-card.plan-card.wide {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-column-gap: 4em;
  grid-row-gap: 2em;
  align-items: center;
  padding: 2.5em 2.5em 3em;
}

.feature-card.plan-card.wide .plan-features { margin: 0; padding: 0; }

@media screen and (max-width: 767px) {
  .feature-card.plan-card.wide { grid-template-columns: 1fr; }
  .plan-price { font-size: 3em; }
}

/* The billing small print under a row of tiers. Grey and centred, so it
   reads as a footnote to the row rather than as another claim. */
.plan-note {
  font-size: 1.125em;
  font-weight: 400;
  line-height: 1.4;
  color: var(--zipli-grey);
  text-align: center;
  max-width: 44em;
  margin: 0 auto;
}

/* A footnote may point at another page, e.g. kitchens sending a multi-site
   operator to the dashboard pricing. Webflow's default link is blue and
   underlined, which shouts louder than the grey line around it. */
.plan-note a {
  color: var(--zipli-green);
  font-weight: 500;
  text-decoration: none;
}

.plan-note a:hover { text-decoration: underline; }

/* =====================================================================
   FIND A TERMINAL
   =====================================================================
   Built for the "find a terminal" donor lookup page. Two new components:
   the lookup bar and the form. Everything else on that page is existing
   system (.feature-card for the result panels, .faq-list for objections).
   See docs/plan-find-a-terminal-page.md for the full brief.
   ===================================================================== */

/* Webflow's export never resets link underline on this class, since every
   original use case is a <span>, not an <a>. Now that some call sites are
   real links (the two home track card buttons, the hero CTAs), the
   underline needs killing once here rather than per variant. */
.main-button { text-decoration: none; }

/* A soft lift so the header pill reads as a raised, clickable surface
   against a photo or video background, not just a flat shape. Subtle by
   design: this should be felt more than seen. Kept to .nav-cta only:
   tried on .main-button too, but on the in-page CTAs (solid colour,
   sitting on a flat section background rather than a photo) it just
   read as unwanted fuzz around the pill. */
.nav-cta {
  box-shadow: 0 6px 18px -4px rgba(0, 0, 0, 0.28);
}

/* Webflow's export padded .nav-cta asymmetrically (1.87em left, 2.19em
   right) to leave room for a call icon that this site never uses, so the
   label sits visibly left of centre in the pill. Even padding both
   sides. */
.nav-cta { padding: 0.75em 2em; }

/* ---------------------------------------------------------------------
   Dark button
   .main-button is white-on-transparent, built for sitting on a photo or
   a lime CTA band. This page's CTAs sit on white and mist backgrounds,
   where that reads as invisible, so this inverts the fill rather than
   introducing a second button shape.
     <a href="#" class="main-button dark w-button">Become a Donor →</a>
   --------------------------------------------------------------------- */
.main-button.dark {
  background-color: var(--zipli-ink);
  color: var(--zipli-white);
  transition: background-color 0.3s;
}

.main-button.dark:hover { background-color: var(--zipli-green); }

/* ---------------------------------------------------------------------
   Lime button
   The brand accent as a fill rather than a text colour, for a CTA that
   needs to read as a real button on a white background without going
   as heavy as .dark, e.g. a secondary action sitting in open space.
     <a href="#" class="main-button lime w-button">Read our story →</a>
   --------------------------------------------------------------------- */
.main-button.lime {
  background-color: var(--zipli-lime);
  color: var(--zipli-black);
  transition: background-color 0.3s;
}

.main-button.lime:hover { background-color: var(--zipli-lime-soft); }

/* ---------------------------------------------------------------------
   Green button
   Deep green fill, white text. Same shape as .dark/.lime, quieter than
   the lime accent, for a CTA that needs to read as calmer than a bright
   accent color without going all the way to black.
     <a href="#" class="main-button green compact w-button">Book a demo →</a>
   --------------------------------------------------------------------- */
.main-button.green {
  background-color: var(--zipli-green);
  color: var(--zipli-white);
  transition: background-color 0.3s;
}

.main-button.green:hover { background-color: var(--zipli-ink); }

/* ---------------------------------------------------------------------
   Lookup bar
   The single input-plus-button search used to check a city. Same fill
   and radius language as .feature-card, sized as a bar rather than a
   block.
     <form class="lookup-bar">
       <input class="lookup-bar-input" type="text" placeholder="Enter your city">
       <button class="main-button dark w-button" type="submit">Check</button>
     </form>
   --------------------------------------------------------------------- */
.lookup-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1em;
  background-color: var(--zipli-mist);
  border-radius: var(--zipli-radius-card);
  padding: 1em 1em 1em 2em;
}

.lookup-bar-input {
  flex: 1 1 16em;
  min-width: 0;
  border: none;
  background: none;
  font-family: var(--zipli-font-body);
  font-size: 1.5em;
  font-weight: 300;
  color: var(--zipli-black);
  padding: 0.5em 0;
}

.lookup-bar-input::placeholder { color: var(--zipli-grey); }
.lookup-bar-input:focus { outline: none; }

.lookup-bar .main-button {
  flex: none;
  font-size: 1.5em;
  padding: 0.7em 1.6em;
  border: none;
  cursor: pointer;
}

@media screen and (max-width: 767px) {
  .lookup-bar { padding: 1.2em; }
  .lookup-bar .main-button { width: 100%; }
}

/* Result panels reuse .feature-card.lime / .feature-card.mist. Only the
   show/hide behaviour is new, driven by a `hidden` attribute so the page
   works with no JavaScript beyond the small mock lookup script. */
.lookup-result { margin-top: 2em; }

.lookup-reassurance {
  font-size: 1.125em;
  font-weight: 400;
  color: var(--zipli-grey);
  text-align: center;
  margin: 1.5em auto 0;
  max-width: 40em;
}

/* ---------------------------------------------------------------------
   Simple form
   The site's first form component. Same visual language as everything
   else in this system: mist fill, 0.75em radius, no borders, sized in
   em. Two fields can share a row via .form-row.
     <form class="simple-form">
       <div class="form-row">
         <div class="form-field">
           <label class="form-label" for="x">Label</label>
           <input class="form-input" id="x" type="text">
         </div>
       </div>
       <button class="main-button dark w-button" type="submit">Send →</button>
     </form>
   --------------------------------------------------------------------- */
.simple-form {
  width: 100%;
  max-width: 44em;
  margin: 0 auto;
  text-align: left;
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5em;
}

.form-row + .form-row { margin-top: 1.5em; }

.form-field.full { grid-column: 1 / -1; }

.form-label {
  display: block;
  font-family: var(--zipli-font-body);
  font-size: 1.125em;
  font-weight: 500;
  margin: 0 0 0.5em;
}

.form-input,
.form-textarea {
  width: 100%;
  background-color: var(--zipli-mist);
  border: none;
  border-radius: var(--zipli-radius-soft);
  font-family: var(--zipli-font-body);
  font-size: 1.25em;
  font-weight: 300;
  color: var(--zipli-black);
  padding: 0.85em 1.1em;
}

.form-input::placeholder,
.form-textarea::placeholder { color: var(--zipli-grey); }

.form-textarea { resize: vertical; min-height: 6em; }

.form-note {
  font-size: 1em;
  font-weight: 400;
  color: var(--zipli-grey);
  margin: 1em 0 1.5em;
}

.simple-form .main-button {
  font-size: 1.5em;
  border: none;
  cursor: pointer;
}

/* Swapped in over the form on mock submit. */
.form-success {
  background-color: var(--zipli-lime-soft);
  border-radius: var(--zipli-radius-card);
  padding: 2.5em;
  text-align: center;
}

.form-success h3 {
  font-family: var(--zipli-font-display);
  font-size: 2em;
  font-weight: 500;
  margin: 0;
}

.form-success p {
  font-size: 1.25em;
  font-weight: 300;
  color: var(--zipli-graphite);
  margin: 0.6em 0 0;
}

@media screen and (max-width: 767px) {
  .form-row { grid-template-columns: 1fr; gap: 1.5em; }
  .form-row + .form-row,
  .form-field + .form-field { margin-top: 1.5em; }
}

/* =====================================================================
   RESPONSIVE REPAIRS
   =====================================================================
   Three overflow bugs found by sweeping every page from 320px to 1920px.
   All three predate this layer and all three live in Webflow output, so
   they are corrected here rather than in the export.
   ===================================================================== */

/* ---------------------------------------------------------------------
   Footer link row
   The export stacks the footer links into a column at 767px, but between
   768 and 991 they stay in one horizontal row. That is the band where the
   body font-size jumps back to a fixed 16px, so the row is suddenly at
   full size in a narrower window and runs off the right edge on every
   page. Letting the row wrap fixes the whole band without a breakpoint of
   its own, and costs nothing above 992px where it already fits on one line.
   --------------------------------------------------------------------- */
.footer_links {
  flex-wrap: wrap;
  grid-row-gap: 1.5em;
}

.footer_links-wrapper {
  flex-wrap: wrap;
  grid-row-gap: 1em;
}

/* ---------------------------------------------------------------------
   Subpage headline steps
   .heading.page overrides the export's font-size with two classes, so the
   single-class steps the export applies to .heading at 991, 767 and 479
   never reach it and the subpage h1 stayed at 3.75em all the way down.
   On a 320 to 414 phone that is wide enough for one long word to break out
   of the viewport, which is what "accountability" does on the management
   page. These are the same three breakpoints the export uses, so the
   subpage headline now steps down in time with the home page one.
   --------------------------------------------------------------------- */
@media screen and (max-width: 991px) {
  .heading.page,
  .hero-section-about .hero-lede.lead { font-size: 3.25em; }
}

@media screen and (max-width: 767px) {
  .heading.page,
  .hero-section-about .hero-lede.lead { font-size: 2.75em; }
  .hero-lede { font-size: 1.5em; }
}

@media screen and (max-width: 479px) {
  .heading.page,
  .hero-section-about .hero-lede.lead { font-size: 2.25em; }
}

/* ---------------------------------------------------------------------
   Home page video embed
   The Cloudinary iframe carries width="370" height="650" as attributes.
   Below roughly 430px that is wider than the gutters leave, so cap it and
   let the aspect ratio hold the height rather than the attribute.
   --------------------------------------------------------------------- */
#videoIframe {
  max-width: 100%;
  height: auto;
  aspect-ratio: 370 / 650;
}

/* ---------------------------------------------------------------------
   Quote profile row
   .profile-info sets the name block and the partner mark side by side in
   a flex row. The mark carries a max-width rather than a flex-basis, so
   on a narrow phone it refuses to shrink and pushes out of the card
   instead. Letting the row wrap puts the mark on its own line, which is
   where it wants to be at that width anyway.
   --------------------------------------------------------------------- */
@media screen and (max-width: 767px) {
  .profile-info {
    flex-wrap: wrap;
    grid-row-gap: 1em;
  }
}

/* =====================================================================
   TYPE SCALE BELOW 992PX
   =====================================================================
   Above 992px the body font-size is a vw value, so every em size on the
   page scales with the window and the hierarchy holds itself. Below 992px
   the base locks to a fixed 16px and that stops being true: the desktop
   em sizes are suddenly rendered at full size inside a narrow window.

   Sweeping the pages from 320px up showed the curve is not just large, it
   is inverted. The export sets h2 to 4em for everything from 991px down to
   480px, which is 64px on a 600px phone, and then to 1.6em below 479px,
   which is 25.6px, smaller than the 24px .feature-card-body sitting under
   it. So a section heading ends up smaller than the card copy it
   introduces, and there is nothing in between the two extremes.

   These three blocks replace that with one curve: a tablet step at 991,
   a phone step at 767 and a small-phone step at 479. Every value is still
   in em, so the relationships hold if the base ever changes. They sit at
   the end of the file so they win over the per-component 767 rules above,
   which were written before the whole scale was looked at together.
   ===================================================================== */

/* Tablet, 768 to 991. The window is narrow but the base is already 16px,
   so the display sizes need bringing down to fit the measure. */
@media screen and (max-width: 991px) {
  h2 { font-size: 2.75em; }
  .cta-band-title { font-size: 2.75em; }
  .pull-quote-text { font-size: 2.5em; }
  .media-split-title { font-size: 2.25em; }
  .step-title { font-size: 2em; }
  .plan-price { font-size: 3em; }
  .faq-question { font-size: 1.75em; }
}

/* Phone. Body copy comes down with the headings here: 24px running text on
   a 480px screen is around 26 characters a line, which reads as a large
   print edition rather than as a page. */
@media screen and (max-width: 767px) {
  h2 { font-size: 2.25em; }
  .cta-band-title { font-size: 2.25em; }
  .pull-quote-text { font-size: 2em; }
  .media-split-title { font-size: 2em; }
  .step-title { font-size: 1.75em; }
  .plan-price { font-size: 2.75em; }
  .faq-question { font-size: 1.5em; }
  .stat-label { font-size: 1.5em; }
  .feature-card-body,
  .faq-answer,
  .section-header-lede { font-size: 1.25em; }
}

/* Small phone, 320 to 479. h4 is lifted rather than lowered here, because
   the export drops it to 1.125em, which puts a card title below the card
   body under it. */
@media screen and (max-width: 479px) {
  h2 { font-size: 2em; }
  h4 { font-size: 1.25em; }
  .cta-band-title { font-size: 1.875em; }
  .media-split-title { font-size: 1.75em; }
  .pull-quote-text { font-size: 1.75em; }
  .step-title { font-size: 1.5em; }
  .plan-price { font-size: 2.5em; }
  .plan-name { font-size: 1.5em; }
  .faq-question { font-size: 1.375em; }
  .stat-label { font-size: 1.25em; }
  .feature-card-body,
  .faq-answer,
  .section-header-lede { font-size: 1.125em; }
}

/* =====================================================================
   Sitemap restructure, August 2026
   Patterns added for the Why Zipli / Product / Industries / Pricing /
   Resources structure. Everything below composes classes that already
   exist; none of it introduces a new visual language.
   ===================================================================== */

/* ---------------------------------------------------------------------
   Section fills
   Pages were setting section backgrounds with an inline
   style="background-color:#fff0f2", which means the palette is not
   greppable and a colour change is a find-and-replace across pages.
   These carry the same fills the cards already use.
     <section class="padding-section-medium section-blush"> ... </section>
   --------------------------------------------------------------------- */
.section-blush { background-color: var(--zipli-blush); }
.section-mist  { background-color: var(--zipli-mist); }

.section-dark {
  background-color: var(--zipli-ink);
  color: var(--zipli-white);
}

/* A dark section inverts its own headings and ledes, so a call site does
   not have to remember .text-color-white on each one. */
.section-dark h2,
.section-dark h3,
.section-dark h4,
.section-dark .section-header-lede,
.section-dark .plan-note { color: var(--zipli-white); }

/* ---------------------------------------------------------------------
   Audience column
   The Industries pages argue to two people at once: the person who uses
   Zipli daily and the person who signs for it. Each column is a tag,
   then "the challenge", then "the Zipli way". It is a composition of
   .feature-card-tag and .feature-card rather than a new component, so
   all this rule does is keep the tag left-aligned above the stack and
   stop the two columns drifting out of step.
     <div class="layout-grid cols-2">
       <div class="audience-column">
         <div class="feature-card-tag">...</div>
         <div class="feature-card mist">The challenge</div>
         <div class="feature-card lime">The Zipli way</div>
       </div>
     </div>
   --------------------------------------------------------------------- */
.audience-column {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* The column is headed by a label, not a pill. .feature-card-tag is the
   pill that sits inside a card; floating one on the page reads as a stray
   lozenge rather than as something that owns the two cards beneath it.
   This is .section-header-eyebrow's treatment in ink instead of grey, so
   it reads as a heading for the column rather than a caption. */
.audience-label {
  font-family: var(--zipli-font-body);
  font-size: 1.25em;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--zipli-ink);
  margin: 0 0 1.2em;
  padding-left: 0.1em;
}

/* Each card takes an equal share of the column, so the two columns line
   up row for row even when one challenge runs a line longer. */
.audience-column > .feature-card { flex: 1 1 auto; }

/* ---------------------------------------------------------------------
   Track card
   A .feature-card that is itself a link, used for the two-track split on
   the home page and the entry cards on Resources. The blog card already
   does this; this is the same behaviour without the image.
   --------------------------------------------------------------------- */
.track-card {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: transform 0.2s ease;
}

.track-card:hover { transform: translateY(-0.25em); }

.track-card .blog-card-readmore { margin-top: 1.5em; }

/* ---------------------------------------------------------------------
   Home hero
   The home headline is now a sentence rather than a two-word statement,
   so it uses the subpage hero shell. It keeps more room above it than a
   subpage does, so the home page still opens differently.
   --------------------------------------------------------------------- */
.hero-wrapper.page.home { padding-top: 15em; }

@media screen and (max-width: 991px) {
  .hero-wrapper.page.home { padding-top: 10em; }
}

@media screen and (max-width: 767px) {
  .hero-wrapper.page.home { padding-top: 6em; padding-bottom: 4em; }
}

/* The donor banner is a cross-sell to a different audience, so it sits
   quieter than a closing CTA: no fill of its own against the section it
   already sits on. */
.cta-band.donor-band { background-color: transparent; }

/* ---------------------------------------------------------------------
   Page notes
   Sources and caveats that would clutter the section they belong to.
   The figure carries a .note-ref asterisk, the explanation sits in a
   .page-note block at the foot of the page, ahead of the footer.
     <p class="stat-label">the claim<a class="note-ref" href="#notes">*</a></p>
     ...
     <section id="notes"><p class="page-note">* The explanation.</p></section>
   --------------------------------------------------------------------- */
.note-ref {
  color: var(--zipli-grey);
  text-decoration: none;
  font-size: 0.85em;
  vertical-align: super;
  line-height: 0;
  padding-left: 0.15em;
}

.note-ref:hover { color: var(--zipli-ink); }

.page-note {
  max-width: 60em;
  color: var(--zipli-grey);
  font-size: 1.15em;
  font-weight: 300;
  line-height: 1.5;
  margin: 0;
}

/* A placeholder standing in for a real photograph in an .image-card slot
   carries its own radius and centring, so it has to be pulled back to the
   bled-media geometry: square corners, because the card's overflow:hidden
   is what rounds them, and flex restored for the caption. */
.photo-placeholder.image-card-media {
  border-radius: 0;
  display: flex;
  aspect-ratio: 16 / 9;
  padding: 1.5em;
}

/* ---------------------------------------------------------------------
   Overlay tag
   The category pill sitting on the picture rather than above the heading.
   It frees a line of vertical space in the card body and ties the label
   to the image, and it is what most card systems do with a category.

   Its left inset matches .image-card-body's padding, so the pill and the
   heading below it sit on the same optical line. That alignment is the
   whole reason it reads as placed rather than floated.

   NOTE, the one shadow in this system. Everything else here is flat
   pastel with no borders and no shadows, deliberately. This is a scoped
   exception, not permission to start adding shadows: the pill has to stay
   legible over photography we do not control, and a white pill on a pale
   or blown-out image would otherwise dissolve into it. It is kept as low
   as it can be while still separating the pill from the picture. Do not
   reach for it anywhere the background is a known flat colour.
   --------------------------------------------------------------------- */
.image-card-media-wrap { position: relative; }

.feature-card-tag.overlay {
  position: absolute;
  top: 1.8em;
  left: 1.8em;
  box-shadow:
    0 1px 2px rgba(23, 23, 23, 0.10),
    0 4px 10px -6px rgba(23, 23, 23, 0.22);
}

@media screen and (max-width: 767px) {
  .feature-card-tag.overlay { top: 1.2em; left: 1.2em; }
}

/* ---------------------------------------------------------------------
   Compact button
   .main-button at 1.8em is sized to stand alone in open space. Inside a
   card it comes out larger than the card's own body copy and takes the
   card over, so this steps it down to sit under a paragraph.

   Note it is used on a <span>, not an <a>. The track cards are already
   links in their entirety, and putting an anchor inside an anchor is
   invalid and breaks keyboard navigation. The span is the affordance;
   the card is the link. Anywhere the card is NOT a link, use a real
   <a class="main-button dark compact w-button"> instead.
   --------------------------------------------------------------------- */
.main-button.compact {
  display: inline-block;
  font-size: 1.35em;
  /* Top and bottom are not equal on purpose. Measured in the browser, the
     line box sits dead centre, but the ink does not: this copy has no
     descenders, so the glyphs occupy the upper part of the line box and
     the button reads as bottom-heavy. Taking a little off the top and
     adding it to the bottom centres the ink rather than the box, which is
     what the eye actually judges. */
  padding: 0.71em 1.5em 0.79em;
}

/* ---------------------------------------------------------------------
   Store button
   .main-button with the platform mark ahead of the label. The arrow is
   dropped: an app store button goes to one known place, and the mark
   already says where, so the arrow was doing nothing the icon does not.

   The mark inherits currentColor, so it flips with the button fill and
   there is no second colour to keep in step.
     <a class="main-button dark store w-button">
       <svg class="store-button-icon">…</svg><span>Get it on Google Play</span>
     </a>
   --------------------------------------------------------------------- */
.main-button.store {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
}

.store-button-icon {
  width: 1.05em;
  height: 1.05em;
  flex: none;
  fill: currentColor;
  /* Both marks read slightly high against a cap-height label, the same
     optical problem as the button padding. */
  position: relative;
  top: -0.03em;
}

/* The icon circle's own fill is white, which disappears on a .white card.
   Give it the section fill instead so the circle still reads. */
.feature-card.white .feature-card-icon { background-color: var(--zipli-blush); }

/* ---------------------------------------------------------------------
   Stat rating
   A row of stars above a stat figure, for a satisfaction score where the
   number alone ("4/4") does not say what kind of measure it is. Sits
   above the figure rather than below, so it reads as a label on it.
     <div class="stat-item center">
       <div class="stat-rating">…svgs…</div>
       <p class="stat-number">4/4</p>
   Only for scores out of a scale. Never put stars on a volume or a
   count: a row of stars over "271 kg" implies a rating that is not there.
   --------------------------------------------------------------------- */
.stat-rating {
  display: flex;
  justify-content: center;
  gap: 0.2em;
  margin-bottom: 0.6em;
}

.stat-rating svg {
  width: 1.5em;
  height: 1.5em;
  fill: var(--zipli-lime);
  flex: none;
}

.stat-item:not(.center) .stat-rating { justify-content: flex-start; }

/* ---------------------------------------------------------------------
   Testimonial carousel
   A horizontal row of quote cards that scrolls when it overflows. Built
   on scroll-snap, so there is no JavaScript, no library and no state to
   go wrong; the browser handles the paging, and with a keyboard or a
   trackpad it behaves the way any scroll area does.

   Two or three cards fit at a desktop width and the rest scroll, which
   is the point: a wall of six quotes reads as filler, a row you can push
   through reads as a set.
     <div class="testimonial-row">
       <figure class="testimonial-card">
         <p class="testimonial-quote">…</p>
         <figcaption class="testimonial-author">
           <span class="testimonial-avatar">…</span>
           <span><span class="testimonial-name">…</span>
                 <span class="testimonial-role">…</span></span>
         </figcaption>
       </figure>
     </div>
   --------------------------------------------------------------------- */
.testimonial-row {
  display: flex;
  gap: 1.5em;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* Room for the cards' own shadowless edges and the scrollbar, so the
     first and last card are not clipped against the container. */
  padding-bottom: 1.5em;
  scrollbar-width: thin;
}

.testimonial-card {
  flex: 0 0 min(28em, 80%);
  scroll-snap-align: start;
  background-color: var(--zipli-white);
  border-radius: var(--zipli-radius-card);
  padding: 2.5em;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2em;
}

/* Green, not the lime used on a dark section: lime on white measures
   1.28:1, so a row of lime stars on a white card would all but vanish.
   Same rule as the stat figures, see the stat band flag in the guide. */
.testimonial-rating {
  display: flex;
  gap: 0.2em;
  margin: 0 0 -1em;
}

.testimonial-rating svg {
  width: 1.4em;
  height: 1.4em;
  fill: var(--zipli-green);
  flex: none;
}

.testimonial-quote {
  font-size: 1.5em;
  font-weight: 300;
  line-height: 1.4;
  color: var(--zipli-ink);
  margin: 0;
  flex: 1 1 auto;
}

.testimonial-author {
  display: flex;
  align-items: center;
  gap: 1em;
  /* webflow.css sets figcaption { text-align: center } on the element
     itself, so inheriting left from the card is not enough to beat it. */
  text-align: left;
}

.testimonial-avatar {
  width: 4em;
  height: 4em;
  border-radius: 50%;
  background-color: var(--zipli-mist);
  color: var(--zipli-grey);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  overflow: hidden;
}

.testimonial-avatar img { width: 100%; height: 100%; object-fit: cover; }
.testimonial-avatar svg { width: 55%; height: 55%; }

.testimonial-name,
.testimonial-role { display: block; line-height: 1.3; }

.testimonial-name {
  font-family: var(--zipli-font-display);
  font-size: 1.25em;
  font-weight: 500;
  color: var(--zipli-ink);
}

.testimonial-role {
  font-size: 1.05em;
  font-weight: 300;
  color: var(--zipli-grey);
}

/* The hint that there is more to the right, for a mouse user who has no
   scrollbar until they touch it. */
.testimonial-hint {
  font-size: 1.05em;
  font-weight: 300;
  color: var(--zipli-grey);
  margin: 0.5em 0 0;
}

.section-dark .testimonial-hint { color: var(--zipli-line); }

@media screen and (max-width: 767px) {
  .testimonial-card { flex-basis: 85%; padding: 2em; }
}


/* ---------------------------------------------------------------------
   Blog topic filter
   A row of pills over the blog index that narrows it to one theme group.

   No JavaScript. The pills are <label>s over a set of radio inputs, and
   one :has() rule per topic hides the groups that do not match. That
   buys real behaviour for nothing: keyboard support, arrow keys between
   options and a proper focus ring all come from the radio, and there is
   no state to load, hydrate or get out of sync with the markup.

   The inputs themselves are taken out of the layout rather than set to
   display:none, so they stay focusable and screen readers still announce
   the group.
     <fieldset class="blog-filter">
       <legend class="blog-filter-legend">Filter by topic</legend>
       <input class="blog-filter-input" type="radio" name="blog-topic" id="topic-all" checked>
       <label class="blog-filter-pill" for="topic-all">All</label>
       …
     </fieldset>
   --------------------------------------------------------------------- */
.blog-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75em;
  border: 0;
  margin: 0;
  padding: 0;
}

/* Visually hidden, still reachable. */
.blog-filter-legend,
.blog-filter-input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.blog-filter-pill {
  background-color: var(--zipli-mist);
  color: var(--zipli-ink);
  border-radius: 7.5em;
  padding: 0.55em 1.25em;
  font-size: 1.25em;
  font-weight: 400;
  line-height: 1.4;
  cursor: pointer;
  transition: background-color 0.2s, color 0.2s;
}

.blog-filter-pill:hover { background-color: var(--zipli-line); }

.blog-filter-input:checked + .blog-filter-pill {
  background-color: var(--zipli-ink);
  color: var(--zipli-white);
}

/* The focus ring has to be drawn from the input onto its label, because
   the input it belongs to is the thing that is off-screen. */
.blog-filter-input:focus-visible + .blog-filter-pill {
  outline: 2px solid var(--zipli-green);
  outline-offset: 3px;
}

.main-container:has(#topic-terminals:checked) .blog-theme-group:not([data-topic="terminals"]),
.main-container:has(#topic-reporting:checked) .blog-theme-group:not([data-topic="reporting"]),
.main-container:has(#topic-cities:checked) .blog-theme-group:not([data-topic="cities"]),
.main-container:has(#topic-impact:checked) .blog-theme-group:not([data-topic="impact"]),
.main-container:has(#topic-compare:checked) .blog-theme-group:not([data-topic="compare"]) {
  display: none;
}

/* ---------------------------------------------------------------------
   Process steps
   The 4-step donation-flow visual on the home page, styled to match the
   source deck's black-background slide: a green label above each panel,
   four separate illustrations (cropped from the source art so a real
   grid gap can sit between them) wrapped in a padded lavender pill, a
   numbered badge straddling each panel's top-left corner, and a white
   caption below. The labels/images/captions all use the same fixed
   4-column grid (not .layout-grid.cols-4) so they stay aligned with
   each other at every width.
     <section class="section-dark">
       <div class="process-steps">
         <div class="process-steps-labels"><p class="process-step-label">Logging</p>...</div>
         <div class="process-steps-image-wrap">
           <div class="process-steps-images">
             <div class="process-step-image-item">
               <img class="process-steps-image" src="...">
               <span class="process-step-badge">1</span>
             </div>
             ...
           </div>
         </div>
         <div class="process-steps-captions"><p class="process-step-caption">...</p>...</div>
       </div>
     </section>
   --------------------------------------------------------------------- */
.process-steps {
  max-width: 78em;
  margin: 0 auto;
}

.process-steps-labels,
.process-steps-images,
.process-steps-captions {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  column-gap: 1.25em;
}

.process-step-label {
  font-family: var(--zipli-font-display);
  font-weight: 700;
  color: var(--zipli-lime);
  text-align: center;
  margin: 0 0 1em;
}

.process-steps-image-wrap {
  background-color: var(--zipli-lavender);
  border-radius: 2em;
  padding: 1.5em 1em;
}

.process-step-image-item { position: relative; }

.process-steps-image {
  width: 100%;
  height: auto;
  border-radius: 1em;
  display: block;
}

/* Each illustration has different internal padding before its own
   rounded card starts (the logging and impact crops are nearly flush,
   matching and delivery have empty space above), so the badge's vertical
   offset is set per panel to land on that card's actual corner rather
   than the crop's bounding box. Percentages come from scanning each
   source image for where its content stops matching the page
   background. Left offset is the same for all four: every panel's card
   starts flush against the crop's left edge. */
.process-steps-images .process-step-image-item:nth-child(1) .process-step-badge { top: calc(0.4% - 1.125em); }
.process-steps-images .process-step-image-item:nth-child(2) .process-step-badge { top: calc(27.2% - 1.125em); }
.process-steps-images .process-step-image-item:nth-child(3) .process-step-badge { top: calc(31.1% - 1.125em); }
.process-steps-images .process-step-image-item:nth-child(4) .process-step-badge { top: calc(10.5% - 1.125em); }

.process-step-badge {
  position: absolute;
  left: -1.125em;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5em;
  height: 2.5em;
  border-radius: 50%;
  background-color: var(--zipli-lime);
  color: var(--zipli-ink);
  font-family: var(--zipli-font-display);
  font-size: 1.125em;
  font-weight: 700;
}

.process-steps-captions { margin-top: 1.5em; }

.process-step-caption {
  font-size: 1.125em;
  color: var(--zipli-white);
  text-align: center;
  margin: 0;
}

@media screen and (max-width: 767px) {
  .process-steps-labels,
  .process-steps-images,
  .process-steps-captions { grid-template-columns: repeat(2, 1fr); row-gap: 1.5em; }
  .process-step-label:nth-child(n+3) { margin-top: 0.5em; }
}

/* Hero background media. .hero-bg-video is shared by a plain <img> (the
   home page, since Aug 2026) and a <video> (About), so it only sets what
   both need: fill the section, sit behind the content. Content keeps
   stacking on top via the existing .padding-global > .main-container flow,
   which just needs a z-index once the media is in the same stacking
   context. */
.hero-section { position: relative; min-height: 50vh; }

.hero-section.has-bg-video { background-image: none; background-color: #111; }

.hero-section-about { min-height: 60vh; }
.hero-section-about .hero-bg-video { object-position: top; }
.hero-section-about .hero-wrapper.page { padding-top: 14em; padding-bottom: 11em; }

/* These desktop em values render at literal size on a phone (root locks
   to 16px below 992px, same issue as the TYPE SCALE section further
   down), so the hero's own vertical padding was pushing the CTA below
   the fold on top of an already-tall video. Cut both the padding and
   the section's own minimum height so the heading and CTA fit in the
   first screen. */
@media screen and (max-width: 767px) {
  .hero-section { min-height: 0; }
  .hero-section-about { min-height: 0; }
  .hero-section-about .hero-wrapper.page { padding-top: 7em; padding-bottom: 4em; }
}

/* Since "Our story" moved out of the hero and onto the section below it,
   the opening hero-lede line is standing in for a headline. Matches
   .heading.page's look exactly (color, font, weight, size steps) rather
   than just being a bigger .hero-lede, since it's playing that role. */
.hero-section-about .hero-lede.lead {
  max-width: 16em;
  color: var(--zipli-lime);
  font-family: var(--zipli-font-display);
  font-weight: 500;
  font-size: 3.75em;
  line-height: 1.05;
}

.hero-bg-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  transition: opacity 1s ease;
}

.hero-section::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  z-index: 1;
}

.hero-section > .padding-global { position: relative; z-index: 2; }

@media (prefers-reduced-motion: reduce) {
  video.hero-bg-video { display: none; }
}

.feature-card-list {
  display: flex;
  flex-direction: column;
  gap: 1em;
}

.article-photo {
  width: 100%;
  border-radius: var(--zipli-radius-card);
  display: block;
  object-fit: cover;
}

.article-photo-caption {
  font-size: 0.9375em;
  color: var(--zipli-grey);
  text-align: center;
  margin: 0.75em 0 0;
}
