/* ==========================================================================
   Unisolva base and component styles
   --------------------------------------------------------------------------
   Every directional property here is logical (margin-inline, padding-inline,
   border-inline-start, text-align: start/end). Nothing uses left/right.
   That is what makes Arabic a direction change rather than a second
   stylesheet. See §5.3.
   ========================================================================== */

/* Font declarations live in tokens.css, because the admin loads tokens
   without loading this file and would otherwise silently fall back to
   system-ui while claiming to use Outfit. */

/* -- reset ---------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

/* The whole system runs one step large. Scaled here rather than token by
   token.
 *
 * Every --text-* and --space-* value is in rem, so they are all relative to
 * this. The v6 mockup's dominant text sizes are 13px and 14px against our
 * 14px and 16px, and its section padding is 78px against our 80. That is
 * not a spacing problem or a type problem, it is the same problem twice:
 * the scale itself is a step too big, which is why trimming padding kept
 * looking like it had not worked.
 *
 * 93.75% of 16px is 15px, which lands --text-sm on 13.1px, --text-base on
 * 15px and --space-20 on 75px: the mockup's proportions, reached by moving
 * one number instead of forty.
 *
 * A percentage, never a px value. This scales the user's own browser font
 * setting rather than overriding it, so someone who has chosen larger text
 * still gets larger text. */
html { font-size: 93.75%; -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

body {
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--text);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg, video { display: block; max-inline-size: 100%; height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* -- typography ----------------------------------------------------------- */

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

h1 { font-size: clamp(2.25rem, 5vw, var(--text-6xl)); }
h2 { font-size: clamp(1.75rem, 3.4vw, var(--text-4xl)); }
h3 { font-size: var(--text-xl); letter-spacing: var(--tracking-snug); }
h4 { font-size: var(--text-lg); letter-spacing: var(--tracking-snug); }

p { text-wrap: pretty; }

/* The rule is a marker, not a separator: it sits on the reading edge of the
   label so a section opening has a fixed left edge to hang off. Flex rather
   than a positioned pseudo-element, so it mirrors in Arabic for free. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-body);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent);
}
.eyebrow::before {
  content: "";
  flex: none;
  inline-size: var(--space-6);
  block-size: 2px;
  background: currentColor;
  border-radius: var(--radius-pill);
}
.section-head--center .eyebrow { justify-content: center; }

.lede {
  font-size: var(--text-lg);
  color: var(--text-muted);
  max-inline-size: 62ch;
}

.muted { color: var(--text-muted); }
.subtle { color: var(--text-subtle); }

/* -- layout --------------------------------------------------------------- */

.container {
  inline-size: 100%;
  max-inline-size: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-6);
}

/* Section rhythm, tightened 16 August 2026.

   This was clamp(4rem, 8vw, 6rem), so on a wide screen every section carried
   96px top and bottom and the gap between any two was 192px. Sections do not
   read as separated at that distance, they read as unrelated, and a page of
   eight sections became a page you scroll through rather than read.

   The v6 mockup uses a flat 78px, dropping to 58px on narrow screens. This
   matches that: 80px at the top of the clamp, 48px at the bottom, so it is
   slightly tighter than the mockup on a phone where vertical space is worth
   most.

   Global on purpose. Every page uses .section, so this is the one place the
   whole site's vertical rhythm is set, blog posts included. */
.section { padding-block: clamp(var(--space-12), 6vw, var(--space-20)); }
.section--tight { padding-block: var(--space-10); }

/* Adjacent sections share one gap instead of stacking two.
 *
 * This is the real cause of the spacing, and reducing the padding was
 * treating the symptom. In the mockup the plans, the consult band and the
 * toolkit are siblings inside a single section, separated by a 26px margin.
 * Here every block renders its own <section>, which the CMS requires, so
 * between any two the gap was one section's bottom padding plus the next
 * one's top padding: 160px where the mockup has 26px.
 *
 * One padding now provides the gap and the other is removed, which is what
 * a margin would have given us if blocks could be siblings.
 */
.section + .section { padding-block-start: 0; }

/* And the surviving padding is the gap, so it is sized as a gap.
 *
 * Removing the doubled padding left 80px between blocks, which is still
 * three times the mockup: it spaces related blocks with 24 to 30px margins.
 * A section followed by another gets a gap-sized bottom padding; the last
 * one on the page keeps its full padding, because that space is separating
 * the content from the footer rather than from a sibling. */
.section:has(+ .section) { padding-block-end: var(--space-8); }

/* Coloured sections keep theirs. Here the padding is inside the band, so
 * trimming it crops the colour against the text rather than closing a gap. */
.section--raised:has(+ .section),
.section--inverse:has(+ .section),
[class*='vstack-section--']:has(+ .section) { padding-block-end: clamp(var(--space-12), 6vw, var(--space-20)); }

/* Restored wherever a section paints its own ground, in both directions.
 * The padding is what keeps the colour off the text, and the section after
 * a coloured band must not sit flush against its edge. */
.section + .section--raised,
.section + .section--inverse,
.section + [class*='vstack-section--'],
.section--raised + .section,
.section--inverse + .section,
[class*='vstack-section--'] + .section {
  padding-block-start: clamp(var(--space-12), 6vw, var(--space-20));
}

/* After the platform strip, a flat gap rather than the clamp.
 *
 * The strip is a hairline band, not a coloured section, so what follows it
 * needs separating from a rule rather than clearing a block of colour. The
 * clamp would open to 75px on a wide screen and reintroduce exactly the
 * distance we have spent this whole pass closing. --space-12 is 45px at the
 * current root, which is the value the client arrived at independently in
 * devtools. */
.section--rule + .section { padding-block-start: var(--space-12); }
.section--raised { background: var(--surface-raised); }
/* A band that sits flush under whatever precedes it, marked off by a single
   rule rather than a change of ground. Used by the platform strip. */
/* 2px, and the :has() selector is repeated so this beats the gap rule above,
   which is more specific than a plain class and was adding 30px underneath.
 *
 * Almost no padding is correct here rather than mean: the cells inside the
 * strip carry their own, so anything on the section is padding on padding.
 * It is a band of five facts between the hero and the page, and it should
 * be exactly as tall as its contents. */
.section--rule,
.section--rule:has(+ .section) { background: var(--surface); border-block-end: 1px solid var(--border); padding-block: 2px; }
.section--inverse { background: var(--surface-inverse); color: var(--text-inverse); }
.section--inverse .lede,
.section--inverse .muted { color: var(--grey-200); }

.stack { display: flex; flex-direction: column; }
.stack-2 { gap: var(--space-2); }
.stack-3 { gap: var(--space-3); }
.stack-4 { gap: var(--space-4); }
.stack-6 { gap: var(--space-6); }
.stack-8 { gap: var(--space-8); }

.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-block-end: var(--space-12);
  max-inline-size: 68ch;
}
.section-head--center { align-items: center; text-align: center; margin-inline: auto; }

/* -- buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-6);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease),
              border-color var(--duration-fast) var(--ease),
              color var(--duration-fast) var(--ease);
  white-space: nowrap;
}

.btn--primary { background: var(--accent); color: var(--text-on-accent); }
.btn--primary:hover { background: var(--accent-hover); }

.btn--secondary { background: transparent; color: var(--text); border-color: var(--border-strong); }
.btn--secondary:hover { border-color: var(--accent); color: var(--accent); }

.btn--onaccent { background: var(--white); color: var(--blue-600); }
.btn--onaccent:hover { background: var(--grey-100); }

.btn--ondark { background: var(--blue-400); color: var(--ink-900); }
.btn--ondark:hover { background: var(--white); }

/* On a photograph neither theme's tokens apply: the ground is the image,
   which is dark in both. These two are the only buttons in the system that
   are deliberately theme-independent. */
.btn--onmedia { background: var(--white); color: var(--ink-900); }
.btn--onmedia:hover { background: var(--grey-100); }

.btn--outline { background: rgb(255 255 255 / 0.06); color: var(--white); border-color: rgb(255 255 255 / 0.38); }
.btn--outline:hover { background: rgb(255 255 255 / 0.14); border-color: var(--white); }

.btn--lg { padding: var(--space-4) var(--space-8); font-size: var(--text-base); }

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

.hero {
  /* Was clamp(4rem, 10vw, 8rem): up to 128px top and bottom, on the first
     screen of every page on the site. The mockup uses 62px over 58px, and
     the extra height was not doing anything a visitor wanted, it just
     pushed the content they came for below the fold.

     Kept slightly taller than a plain section, because a hero should still
     read as the opening rather than as the first item in a list. */
  padding-block: clamp(var(--space-12), 7vw, var(--space-24));
  border-block-end: 1px solid var(--border);
}
/* rem rather than ch for the same reason as the media hero: `ch` here
   resolves against the 16px body font while the thing being constrained is
   a 60px display heading, so 54ch was about 470px and every headline of
   more than six words came out as a narrow column. */
.hero__inner { display: flex; flex-direction: column; gap: var(--space-6); max-inline-size: min(42rem, 100%); }
.hero h1 { margin-block-start: var(--space-2); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-block-start: var(--space-4); }

/* -- hero, with a photograph ----------------------------------------------
   The image is a real <img> stacked behind the content rather than a CSS
   background, so it keeps its srcset, its dimensions and its place in the
   preload scanner. `isolation` keeps the negative z-indexes from escaping
   into the page behind. */

.hero--media ,
.hero--fx {
  position: relative;
  isolation: isolate;
  display: grid;
  align-items: center;
  min-block-size: clamp(30rem, 62vw, 44rem);
  padding-block: clamp(var(--space-20), 12vw, var(--space-32));
  border-block-end: 0;
  background: var(--ink-900);
  color: var(--white);
}
.hero--compact { min-block-size: clamp(22rem, 44vw, 32rem); }

.hero__media { position: absolute; inset: 0; z-index: -2; }

/* The drawn background. Sized by CSS and painted by hero.js; it ships empty
   so a visitor with no JavaScript gets the hero as it already is rather
   than a gap. Explicit width and height because a canvas defaults to
   300x150 regardless of its box, and the script reads its rendered size. */
.hero__fx { position: absolute; inset: 0; z-index: -2; inline-size: 100%; block-size: 100%; display: block; }
.hero__media img { inline-size: 100%; block-size: 100%; object-fit: cover; object-position: center; }

/* Two layers on purpose. The linear pass keeps the headline legible over
   whatever the photograph is doing; the radial pass puts a little of the
   brand azure back into the dark side so the result reads as ours rather
   than as a stock image with a black box on it. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(circle at var(--scrim-focus, 18%) 42%,
      color-mix(in srgb, var(--blue-400) 16%, transparent), transparent 30%),
    linear-gradient(var(--scrim-angle, 90deg),
      rgb(4 22 34 / 0.97) 0%,
      rgb(4 22 34 / 0.92) 34%,
      rgb(4 22 34 / 0.62) 58%,
      rgb(4 22 34 / 0.18) 86%);
}
/* Arabic reads the other way, so the clear side of the scrim has to be on
   the other side too. Gradients have no logical form, hence the flip. */
[dir='rtl'] .hero__scrim { --scrim-angle: 270deg; --scrim-focus: 82%; }

/* And the photograph mirrors with it.
   Flipping the scrim alone moves the dark edge but leaves the image where
   it was, so a picture composed with its quiet side under the headline in
   English has its busy side there in Arabic. Mirroring both keeps the
   relationship between image and text identical in the two languages, which
   is what allows a hero photograph to have a subject on one side at all.
   Transform sits on the img rather than the <picture>, because a transform
   on the wrapper would create a stacking context and the negative z-index
   layering underneath depends on there not being one.
   Requires that images carry no text and nothing with obvious handedness,
   which the art direction already rules out. */
[dir='rtl'] .hero__media img { transform: scaleX(-1); }

/* rem, not ch. `ch` on this element resolves against the 16px body font,
   so 46ch is about 400px and the display headline sitting inside it wrapped
   to six lines. The measure has to be set in a unit the heading's own size
   does not silently disagree with. */
.hero--media .hero__inner ,
.hero--fx .hero__inner { max-inline-size: min(44rem, 100%); }
.hero--media .eyebrow ,
.hero--fx .eyebrow { color: var(--blue-400); }
.hero--media h1 ,
.hero--fx h1 {
  font-size: clamp(2.4rem, 4.8vw, 4rem);
  letter-spacing: -0.03em;
}
.hero--media .lede ,
.hero--fx .lede { color: var(--grey-200); max-inline-size: 36rem; }

/* Below the fold on a phone the scrim has nowhere to fade to, so it becomes
   a flat wash and the image sits behind the whole block instead of beside
   it. */
@media (max-width: 700px) {
  .hero__scrim { background: rgb(4 22 34 / 0.80); }
  .hero--media .hero__inner ,
  .hero--fx .hero__inner { max-inline-size: none; }
}

/* -- in-page jump links ---------------------------------------------------
   A single row inside the hero, aligned to the end of the text above it.
   Never wraps: four pills broken two and two stop reading as navigation
   and start reading as a block of buttons. On a narrow screen the row
   scrolls sideways instead. */

.hero__jump {
  display: flex; gap: var(--space-2);
  /* Wraps by default, which is what the reference does on a phone. A
     nowrap row here was forcing the hero's grid item wider than the
     viewport and giving the whole page a horizontal scrollbar at 375px. */
  flex-wrap: wrap;
  margin-block-start: var(--space-10);
}
@media (min-width: 1000px) {
  /* One line from tablet up, where there is room for four pills and the
     alignment reads as deliberate. overflow-x catches a page that ever
     grows a sixth link rather than letting it break the layout. */
  .hero__jump {
    flex-wrap: nowrap; justify-content: flex-end;
    overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .hero__jump::-webkit-scrollbar { display: none; }
}

/* A grid item's min-width is auto, so the container refuses to shrink
   below its widest child and pushes the page sideways instead. This is
   what let the pill row widen the whole document. */
.hero > .container { min-inline-size: 0; }

.hero__jump a {
  display: inline-flex; align-items: center; flex: none;
  padding: var(--space-2) var(--space-5);
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-muted); white-space: nowrap;
  transition: border-color var(--duration-fast) var(--ease),
              color var(--duration-fast) var(--ease),
              background var(--duration-fast) var(--ease);
}
.hero__jump a:hover { border-color: var(--accent); color: var(--accent); }

/* On a photograph the tokens do not apply: the ground is the image, which
   is dark in both themes. Same reasoning as .btn--outline. */
.hero--media .hero__jump a ,
.hero--fx .hero__jump a { border-color: rgb(255 255 255 / 0.38); color: var(--white); }
.hero--media .hero__jump a:hover ,
.hero--fx .hero__jump a:hover { background: rgb(255 255 255 / 0.14); border-color: var(--white); }

/* A jump target must clear the sticky header, or the heading you asked for
   lands underneath it.

   On the sections themselves rather than on :target, which only matches
   once a fragment is active and so relies on the browser applying it
   before it computes where to scroll. This way the offset also holds for
   back and forward navigation and for anything calling scrollIntoView. */
main > section[id] { scroll-margin-block-start: 7rem; }

/* -- chips ---------------------------------------------------------------- */

.chips { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); margin-block-start: var(--space-4); }
.chip {
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--text-muted);
  background: var(--surface-raised);
}
.hero--media .chip ,
.hero--fx .chip {
  border-color: rgb(255 255 255 / 0.22);
  background: rgb(4 22 34 / 0.45);
  color: var(--grey-200);
  backdrop-filter: blur(4px);
}

/* -- plan table ----------------------------------------------------------- */

/* The controls and the cards are held to one measure by their shared
 * wrapper, so the term switcher's outer edge is the first card's edge and the
 * currency switcher's is the last card's. The cap used to sit on the grid
 * alone, which left the switchers spanning the full container and overhanging
 * the cards on both sides.
 *
 * Nothing here shrinks a narrow viewport: below the cap the wrapper is simply
 * the container's width, so the phone layout is untouched.
 */
.plans-scope { max-inline-size: 64rem; margin-inline: auto; }

.plans {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  align-items: start;
}
@media (min-width: 900px) {
  .plans { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* Narrower tables stay centred rather than stretching a two-card table
     across a three-card measure, which makes each card wider than it has
     content to fill. The measure moves to the wrapper so the controls come
     with it; capping the grid on its own is what broke the alignment. */
  .plans-scope--1 { max-inline-size: 24rem; }
  .plans-scope--2 { max-inline-size: 52rem; }
  .plans--1 { grid-template-columns: minmax(0, 1fr); }
  .plans--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .plans--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* White cards with a coloured edge, per the v6 mockup.
 *
 * These were solid --plan-bg with white text, citing the brand guide's
 * inverted treatment. Three saturated blue slabs side by side made the
 * prices compete with each other instead of with a competitor's, and every
 * feature list had to be read as white-on-blue. The mockup puts the colour
 * in a 4px bar at the top of each card, which marks the tier without
 * shouting, and lets the price be the loudest thing on the card.
 *
 * Worth knowing this diverges from the brand guide reference in the old
 * comment here. It follows the client's own newer mockup instead.
 */
.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-6);
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-base);
  overflow: hidden;
}

/* The tier's colour, as an edge rather than a ground. */
.plan::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 4px;
  background: var(--border-strong);
}

/* The popular tier inverts, per brand guide page 8. Its surface comes from
   a component token rather than a literal, because #161616 on a white page
   is a strong statement and #161616 on a #161616 page is an invisible card.
   On dark grounds it lifts to --ink-800 and takes an accent edge. */
.plan--featured {
  background: var(--surface);
  border: 1px solid var(--plan-featured-border);
  box-shadow: var(--plan-featured-shadow);
}
/* Lifted and given a thicker accent edge, so the recommended tier is
   obvious without being a different colour of card. */
.plan--featured::before { block-size: 6px; background: linear-gradient(90deg, var(--accent), var(--blue-400)); }
@media (min-width: 60rem) { .plan--featured { transform: translateY(-7px); } }

/* In the corner, not straddling the top edge.

   Centred on the border it had to sit outside the card, which meant the
   card could not clip its own overflow and the badge collided with the
   accent bar. The mockup puts it inside, at the end of the first line,
   where it reads as a note on the tier rather than a flag planted on it. */
.plan__badge {
  position: absolute;
  inset-block-start: var(--space-5);
  inset-inline-end: var(--space-5);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--blue-700);
  font-size: 0.625rem;
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  white-space: nowrap;
}

/* The tier name, back to the mockup's weight.

   It had become a --text-2xs uppercase label at 0.85 opacity, centred: the
   treatment this system uses for eyebrows, which are framing text. But the
   plan name is not framing anything. It is the word the whole card is about
   and the one a reader says out loud when they choose. Set as a heading, at
   the start edge, so the card opens with what it is rather than with a
   caption above a price. */
.plan__name {
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  letter-spacing: -0.01em;
  line-height: var(--leading-snug);
  margin-block-end: var(--space-1);
}

/* Three positions, not a centred row: term to the left, saving in the
   middle, currency to the right. Two switchers side by side read as one
   control with six options; pushed apart, each is obviously its own
   question, and the saving sits between them where it explains the left one
   and is seen on the way to the right. */
.pricectl {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--space-3) var(--space-4); margin-block-end: var(--space-8);
}
.pricectl__term { justify-self: start; }
.pricectl__currency { justify-self: end; }
.pricectl__group { display: grid; gap: var(--space-2); justify-items: inherit; }
.pricectl__label { font-size: var(--text-2xs); text-transform: uppercase;
  letter-spacing: var(--tracking-wide); font-weight: var(--weight-bold); color: var(--text-subtle); }

@media (max-width: 48rem) {
  .pricectl { grid-template-columns: 1fr; justify-items: center; }
  .pricectl__term, .pricectl__currency { justify-self: center; }
}

/* Narrower cards. Three full-width columns pushed the price, the features
   and the value band all to line lengths that read as a table rather than a
   card. The measure itself lives on .plans-scope above, shared with the
   controls. */
.pricectl .switch { border-color: var(--border-strong); }
.pricectl .switch__opt { border: 0; background: none; cursor: pointer; font-family: var(--font-body); }
.pricectl__save {
  /* Brand blue, not green. This was the last state colour doing marketing
     work: --state-success exists for form feedback, and a saving is not a
     success message. */
  font-size: var(--text-xs); font-weight: var(--weight-semibold); color: var(--accent-muted);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill);
}
.plans__note {
  text-align: center; margin-block-start: var(--space-6);
  font-size: var(--text-xs); color: var(--text-subtle); max-inline-size: 72ch;
  margin-inline: auto;
}

/* Start-aligned, following the mockup and the name above it. A centred
   price under a start-aligned name gave the card two competing axes.

   The rule underneath was `1px solid rgb(255 255 255 / 0.22)`, written for
   the dark featured card and invisible on white, exactly like the domain
   badge was. The mockup has no divider here at all, so it goes rather than
   being recoloured into existence. */
.plan__price {
  display: flex;
  align-items: baseline;
  justify-content: flex-start;
  gap: var(--space-2);
  font-variant-numeric: tabular-nums;
  padding-block-end: var(--space-2);
  min-block-size: 3.1rem;
}
/* Every currency and term is in the DOM; only one box is unhidden. */
.plan__pricebox { display: flex; align-items: baseline; gap: var(--space-2); }
.plan__pricebox[hidden] { display: none; }
.btn--muted { background: rgb(255 255 255 / 0.16); color: rgb(255 255 255 / 0.72); cursor: not-allowed; }
/* These two assumed a dark card. On a white one the translucent white was
   invisible and the light blue text failed against the ground. */
.plan--featured .btn--muted { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.plan__amount {
  font-family: var(--font-display);
  font-size: var(--text-4xl);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.03em;
  line-height: 1;
}
/* The billing period is not optional. It was specified in the brand book
   and lost in the last build; a bare number is a 12x ambiguity. */
.plan__term { font-size: var(--text-sm); opacity: 0.82; }

/* min-height so a one-line and a two-line description still leave the three
   prices sitting on the same baseline. Without it the cards stagger. */
.plan__for { margin: 0; color: var(--text-muted); font-size: var(--text-sm); min-block-size: 2.8em; }

.plan__billing { display: grid; gap: 2px; margin-block-start: calc(var(--space-3) * -1); }
.plan__billing span { font-size: var(--text-2xs); color: var(--text-subtle); }
.plan__renewal { font-weight: var(--weight-medium); }

.plan__specs { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.plan__specs li { border: 1px solid var(--border); border-radius: var(--radius-xs); padding: var(--space-2) var(--space-3); }
.plan__spec-label { display: block; font-size: var(--text-2xs); text-transform: uppercase;
  letter-spacing: var(--tracking-wide); color: var(--text-subtle); }
.plan__spec-value { display: block; font-weight: var(--weight-semibold); font-size: var(--text-sm); }

.plan__bundle {
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  padding: var(--space-3);
  border-radius: var(--radius-xs);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--accent-muted);
}
.plan--featured .plan__bundle { background: color-mix(in srgb, var(--accent) 8%, transparent); color: var(--accent-muted); }
.plan__bundle strong { font-weight: var(--weight-semibold); }

/* Itemised bundle. Collapsed by default so the card stays scannable, open
   in one click because a total nobody can check persuades nobody. */
/* The value, sized to be seen. It was a small inline run of text and the
   page's best argument was invisible unless somebody opened a disclosure. */
/* Stacked and centred: the figure on its own line, the sentence under it.
   Side by side, the number had to compete with the text for the same
   baseline and neither won. */
.plan__bundle-head { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-1); flex: 1 1 100%; }
.plan__bundle-sum { font-size: var(--text-3xl); font-weight: var(--weight-bold); line-height: 1; letter-spacing: -0.02em; }
/* One line, so "included free" reads as part of the same sentence rather
   than a second thought below it. */
.plan__bundle-of { font-size: var(--text-xs); line-height: 1.4; }
.plan__bundle-of strong { display: inline; }
.plan__bundle summary { cursor: pointer; list-style: none; display: flex; flex-wrap: wrap;
  flex-direction: column; align-items: center; text-align: center; gap: var(--space-2); }
.plan__bundle summary::-webkit-details-marker { display: none; }
.plan__bundle-more { text-decoration: underline; text-underline-offset: 2px; opacity: 0.75; }
.plan__bundle[open] .plan__bundle-more { opacity: 0.5; }
.plan__bundle ul { list-style: none; margin: var(--space-3) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-1); }
/* Grid, not flex with space-between.

   The names got longer when the products stopped being named: "Premium theme
   and template library" is three times the width of "Astra Pro". Under
   flex the name pushed the price out of the row and "$69.00" broke across
   two lines inside the card. A two-column grid lets the name wrap in its own
   column while the price stays put on the first line, right aligned and
   never wrapping. */
.plan__bundle li { display: grid; grid-template-columns: 1fr auto; gap: var(--space-2) var(--space-3);
  align-items: baseline; font-variant-numeric: tabular-nums; }
.plan__bundle li > :last-child { white-space: nowrap; text-align: end; }
/* The licence tier, not decoration: $49 for one site and $249 for unlimited
   are both true of SiteSEO, and only one of them is the number being added
   up here. Naming the tier is what makes the total checkable. */
.plan__bundle li small { opacity: 0.6; font-size: 0.9em; }
.plan__bundle li small::before { content: "\00a0"; }
.plan__bundle-note { margin-block-start: var(--space-2); opacity: 0.7; font-size: 0.6875rem; }

.plan__features { list-style: none; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); font-size: var(--text-sm); }
.plan__features li { display: flex; gap: var(--space-2); align-items: flex-start; line-height: var(--leading-snug); }
.plan__features svg { flex: none; inline-size: 14px; block-size: 14px; margin-block-start: 3px; }
.plan__cta { margin-block-start: auto; }

/* -- feature grid --------------------------------------------------------- */

.features { display: grid; grid-template-columns: 1fr; gap: var(--space-5); counter-reset: feature; }
@media (min-width: 700px) { .features--2, .features--4 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) {
  .features--3 { grid-template-columns: repeat(3, 1fr); }
  .features--4 { grid-template-columns: repeat(4, 1fr); }
}

/* A card rather than a run of text.
   Eight services set as bare paragraphs read as one wall; the border and
   the ground are what make them eight things. The index is a counter, not
   markup, so reordering blocks in the editor cannot produce 01, 02, 04. */
.feature {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  transition: border-color var(--duration-base) var(--ease),
              box-shadow var(--duration-base) var(--ease),
              transform var(--duration-base) var(--ease);
}
.section--raised .feature { background: var(--surface); }
.feature__title { font-family: var(--font-display); font-size: var(--text-lg); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-snug); }
.feature__body { font-size: var(--text-sm); color: var(--text-muted); }

/* Was a 2px accent bar. Same element, same aria-hidden, now carrying the
   number, which gives the card a fixed top edge to align on whatever the
   heading wraps to. */
.feature__rule {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide);
  color: var(--accent);
  margin-block-end: var(--space-6);
  font-variant-numeric: tabular-nums;
}
.feature__rule::before {
  counter-increment: feature;
  content: counter(feature, decimal-leading-zero);
}

/* -- proof band ----------------------------------------------------------- */

/* ------------------------------------------------------------- value stack

   The figure to one side, what it buys to the other. Two columns on a wide
   screen, stacked on a narrow one, because a seven-item list beside a large
   number is unreadable on a phone. */
/* Ported from the v6 mockup. Two differences from the original, both
   required here and neither optional:

   Colours come from tokens rather than the mockup's own hex values, so the
   panel follows the theme instead of introducing a second palette. The
   mockup has no dark mode; this site does, and a hardcoded #fff panel would
   stay white while everything around it went dark.

   Everything is logical: the mockup used a few physical properties, which
   mirror wrong on the Arabic pages. */
.vstack { display: grid; grid-template-columns: 0.72fr 1.28fr; overflow: hidden;
  border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow-sm); }

.vstack__panel { display: flex; flex-direction: column; justify-content: center; gap: var(--space-2);
  padding: var(--space-8); border-inline-end: 1px solid var(--border);
  background:
    radial-gradient(circle at 30% 28%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 35%),
    linear-gradient(135deg, color-mix(in srgb, var(--accent) 4%, var(--surface)), color-mix(in srgb, var(--accent) 9%, var(--surface))); }
/* --accent-contrast, for the same reason as .shared__label: --accent-muted
   on this wash is 2.42:1 and this is 11.7px bold, which is small text. */
.vstack__label { margin: 0; font-size: var(--text-xs); color: var(--accent-contrast); text-transform: uppercase;
  letter-spacing: var(--tracking-wide); font-weight: var(--weight-bold); }
.vstack__value { margin: 0; font-size: var(--text-6xl); line-height: 1; letter-spacing: -0.05em;
  color: var(--accent); font-weight: var(--weight-bold); }
.vstack__value small { display: block; margin-block-start: var(--space-1); font-size: var(--text-sm);
  letter-spacing: 0; color: var(--text-muted); font-weight: var(--weight-semibold); }
.vstack__body { margin: 0; color: var(--text-muted); font-size: var(--text-sm); max-inline-size: 34ch; }

.vstack__content { padding: var(--space-6) var(--space-8); }
.vstack__heading { margin: 0 0 var(--space-1); font-size: var(--text-xl); }
.vstack__intro { margin: 0 0 var(--space-5); color: var(--text-muted); font-size: var(--text-sm); }

.vstack__groups { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); }
/* The group title is the tile. It replaced a fixed 38px square holding the
   group's initial, with the name set as plain text underneath: "B" above
   "Build" is the same word twice, and the square was the unreadable copy.

   inline-flex so the tile is exactly as wide as its label, which is why a
   one-word group and "Backup and recovery" both sit correctly rather than
   being padded to a common width.

   Three things here were wrong on the first cut and are worth naming.

   Colour: the label was --accent-muted on a 12% accent tint, which composites
   to #00A6EF on #E0EFF9 and measures 2.32:1. The page sets 1rem to 93.75%, so
   --text-base is 15px and this is normal-size text needing 4.5:1, not large
   text needing 3. It was roughly half the required ratio, and it replaced
   plain --text on white at 18:1. --accent-contrast is the token that exists
   for text on an accent ground and resolves per theme, giving 9.1:1 light.

   Wrapping: inline-flex shrink-to-fits, so a label wider than its grid column
   clamps to the column and wraps instead of hugging. "PROTECT AND EXTEND"
   needed 228px in a 212px column, so the third tile became a half-empty
   full-width bar two lines tall and pushed its own bullet list 16px out of
   line with the other two. nowrap stops a tile ever becoming a block.

   Tracking: 0.14em is a label treatment and cost about 40px on the longest
   string, which is what made it not fit in the first place. --tracking-wide
   is 0.06em and buys back roughly 25px, which is the difference between
   "BACKUP AND RECOVERY" having 2px of headroom at 1121px and having 25px. */
.vstack__group-title { display: inline-flex; align-items: center;
  margin: 0 0 var(--space-3); padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent-contrast);
  font-size: var(--text-base); font-weight: var(--weight-bold);
  text-transform: uppercase; letter-spacing: var(--tracking-wide);
  white-space: nowrap; }

/* Arabic is cursive and joins. Letter-spacing pulls the joins apart, adding
   about 31% to the width of these labels while breaking the letterforms
   themselves. uppercase is simply a no-op here. */
[dir='rtl'] .vstack__group-title { letter-spacing: normal; }
.vstack__group-items, .vstack__items { list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-1); }
.vstack__group-items li, .vstack__items li { position: relative; padding-inline-start: var(--space-4);
  font-size: var(--text-sm); color: var(--text-muted); }
/* Drawn, not list-style, so it keeps its colour and sits on the correct
   side in Arabic with no extra rules. */
.vstack__group-items li::before, .vstack__items li::before { content: ""; position: absolute;
  inset-inline-start: 0; inset-block-start: 0.6em; inline-size: 5px; block-size: 5px;
  border-radius: 50%; background: var(--accent); }
.vstack__note { margin-block-start: var(--space-5); padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--border); font-size: var(--text-xs); color: var(--text-subtle); }

/* The panel stacks above the capabilities before the columns get too narrow
   to read. */
@media (max-width: 60rem) {
  .vstack { grid-template-columns: 1fr; }
  .vstack__panel { border-inline-end: 0; border-block-end: 1px solid var(--border); }
}

/* Three columns of three items is unreadable on a phone, so each group
   becomes its own stacked row.

   This used to be a two-column grid with a 38px track reserved for the
   initial tile. The tile is now the title and sizes to its own text, so a
   fixed track would either clip a long label or strand a short one. Plain
   stacking instead. */
@media (max-width: 40rem) {
  .vstack__content { padding: var(--space-6) var(--space-5); }
  .vstack__groups { display: block; }
  .vstack__group { padding-block: var(--space-4); border-block-start: 1px solid var(--border); }
  .vstack__group:first-child { border-block-start: 0; padding-block-start: 0; }
  /* --border is --grey-200 in the light token set, and the dark section
     hard-sets its own near-black ground rather than switching tokens, so
     the separator above would be a bright hairline on black. --vstack__note
     already carries this same correction; the group rule did not, because
     the group had no separator until it stopped being a grid. */
  .vstack--dark .vstack__group { border-block-start-color: color-mix(in srgb, var(--white) 15%, transparent); }
}

/* Free-domain pill on a plan card, from the v6 mockup.

   Green rather than the accent blue on purpose: it is a different kind of
   claim from the plan itself, and next to a blue "Most popular" badge a
   second blue pill would read as more of the same. */
/* White ground, brand blue text. No green.

   Two mistakes, both worth naming. The first version used a translucent
   tint, which takes the colour of whatever is behind it, so a pill sitting
   on solid accent blue simply dissolved. The second was the colour itself:
   --state-success is a *state* token, for form feedback, and it is not in
   the brand palette. It got used only because the mockup's badge was green,
   which is not a reason.

   Third mistake, and the one that shipped: white. It was chosen when the
   featured tier still inverted to near-black, where a white pill reads
   clearly. That inversion is gone. Every card now sits on --surface, so a
   white pill on a white card is a pill nobody can see, and the badge has
   been rendering as loose blue text on the live page ever since.

   So the ground is a wash of the brand accent, the same 8% mix the bundle
   band already uses, with an accent edge to give the pill an outline the
   card cannot swallow. Blue throughout, and it cannot dissolve again
   because the tint and the border move together with --accent. */
.plan__domain { display: inline-flex; align-items: center; margin: var(--space-2) 0 var(--space-1);
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--blue-700);
  font-size: var(--text-xs); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-wide); }

/* -------------------------------------------------------- resource table

   The per-account limits, plan by plan. A real table because it is a real
   comparison: the reader's question is "how do these differ", and tabs
   answer one plan at a time.

   The wrapper scrolls rather than the page, and carries tabindex so the
   right-hand columns are reachable by keyboard on a narrow screen. */

/* The progression above the table. Shape of the difference first, exact
   figures immediately under it.

   Every bar is drawn from a value the table also prints, so the block is
   aria-hidden and this is styling with no semantic weight. Bars use the
   accent, not a spectrum: three tiers of one product are more of the same
   thing, and colour-coding them would imply they are different things.

   Hidden below 60rem. Three bars per row in a phone-width column are
   thinner than the caption under them, and the table is already the
   readable form on a narrow screen. */
.progression { display: none; }

@media (min-width: 60rem) {
  .progression {
    display: grid;
    grid-template-columns: minmax(8rem, 1fr) repeat(var(--progression-cols, 3), 1fr);
    gap: var(--space-4) var(--space-5);
    align-items: center;
    margin-block-end: var(--space-6);
    padding-block-end: var(--space-6);
    border-block-end: 1px solid var(--border);
  }
  .progression__head {
    font-size: var(--text-2xs); text-transform: uppercase;
    letter-spacing: var(--tracking-label); font-weight: var(--weight-bold);
    color: var(--text-subtle); text-align: center; margin: 0;
  }
  .progression__label {
    font-size: var(--text-sm); font-weight: var(--weight-semibold); margin: 0;
  }
  .progression__cell { display: flex; flex-direction: column; gap: var(--space-1); }
  .meter {
    block-size: 8px; border-radius: var(--radius-pill);
    background: var(--surface-raised); overflow: hidden;
  }
  /* Logical, so the bar fills from the inline start and reverses with the
     document in Arabic rather than growing away from the label. */
  .meter span {
    display: block; block-size: 100%; border-radius: var(--radius-pill);
    background: var(--accent);
  }
  .meter__caption {
    font-size: var(--text-2xs); color: var(--text-muted); text-align: center;
  }
}

.rtable { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); }
.rtable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rtable table { inline-size: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.rtable th, .rtable td { padding: var(--space-3) var(--space-4); text-align: start;
  border-block-end: 1px solid var(--border); white-space: nowrap; }
.rtable thead th { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-wide);
  color: var(--text-subtle); background: var(--surface-sunken); }
.rtable tbody th { font-weight: var(--weight-medium); color: var(--text-base); }
.rtable tbody td { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.rtable tbody tr:last-child th, .rtable tbody tr:last-child td { border-block-end: 0; }
.rtable tbody tr:hover th, .rtable tbody tr:hover td { background: var(--surface-raised); }
/* The featured plan is the middle column, and it earns emphasis here for
   the same reason it does in the plan cards. */
.rtable tbody td:nth-child(3), .rtable thead th:nth-child(3) { color: var(--text-base); font-weight: var(--weight-medium); }
.rtable__note { margin-block-start: var(--space-4); font-size: var(--text-xs); color: var(--text-subtle); }

/* Screen-reader-only text. The table's corner cell needs a name for the
   header row without printing one. */
.visually-hidden { position: absolute; inline-size: 1px; block-size: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* Dark variant: the platform section.

   No figure panel, so the groups take the full width and go to four across.
   Runs dark because it is the one section on the page making an engineering
   argument rather than a commercial one, and the change of ground is what
   marks that shift. */
.vstack-section--dark { background: var(--ink-900); color: var(--grey-100); }
.vstack--dark { grid-template-columns: 1fr; border: 0; background: none; box-shadow: none; }
.vstack--dark .vstack__panel { display: none; }
.vstack--dark .vstack__content { padding: 0; }
.vstack--dark .vstack__heading { color: var(--white); font-size: var(--text-3xl); }
.vstack--dark .vstack__intro { color: var(--grey-200); font-size: var(--text-base); max-inline-size: 68ch; }
.vstack--dark .vstack__groups { grid-template-columns: repeat(4, 1fr); gap: var(--space-8); }
/* Same tile treatment on the dark ground, lifted to --blue-400 because
   --accent-muted is unreadable against near-black. Four groups here rather
   than three, one of them two words long, which the inline-flex tile
   handles without a fixed width. */
/* --accent-contrast is --blue-900 in the light token set, which is invisible
   on this near-black ground, so the dark section keeps --blue-400 against a
   22% tint. That composites to #00A6EF on #112C3E and measures 5.3:1, which
   passes on its own merits rather than by inheriting the light rule. */
.vstack--dark .vstack__group-title {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--blue-400);
}
.vstack--dark .vstack__group-items li { color: var(--grey-100); padding-block-end: var(--space-3); }
.vstack--dark .vstack__group-items li::before { background: var(--blue-400); }
/* The explanation under each item, which is what stops this reading as a
   list of vendor names. */
.vstack--dark .vstack__group-items li small { display: block; color: var(--text-subtle); margin-block-start: 2px; }
.vstack--dark .vstack__note { border-block-start-color: color-mix(in srgb, var(--white) 15%, transparent); color: var(--grey-200); }

@media (max-width: 70rem) {
  .vstack--dark .vstack__groups { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 40rem) {
  .vstack--dark .vstack__groups { grid-template-columns: 1fr; }
}

/* Platform strip, from the v6 mockup.

   proof_band's `variant` field already existed for exactly this: same block,
   same {value, label} pairs, different job. Here the value is a name rather
   than a number, so it is sized as a label instead of a headline, and the
   cells are divided by rules rather than spaced apart.

   03_Competitive_Teardown.md found Unisolva was the only host in the set
   with no trust signals while running the strongest stack in it. This is
   that stack, said once, near the top. */
/* The rules themselves live below the base .proof block, because they share
   its specificity and would otherwise lose to it. See there. */

/* A single stat is a statement, not a grid cell: centred, with the figure
   over the sentence, matching the band inside the plan cards. Two or more
   still lay out as columns. */
.proof:has(.stat:only-child) { grid-template-columns: 1fr; justify-items: center; text-align: center; }
.proof:has(.stat:only-child) .stat__value { font-size: var(--text-5xl); }
.proof:has(.stat:only-child) .stat__label { font-size: var(--text-base); max-inline-size: 46ch; }
.proof { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-8); }
@media (min-width: 800px) { .proof { grid-template-columns: repeat(4, 1fr); } }

/* Platform strip variant, declared here on purpose.

   `.proof--platform` and `.proof` are both a single class, so they carry
   identical specificity and source order decides. Declared above the base,
   as it first was, the plain `.proof` rule and its 800px media query simply
   overwrote it: five cells rendered as four plus an orphan, with the base
   gap instead of the dividers. Nothing about the variant was wrong, it just
   never applied.

   Anything overriding `.proof` belongs after both rules above. */
.proof--platform { grid-template-columns: repeat(5, 1fr); gap: 0; }
.proof--platform .stat { padding: var(--space-4) var(--space-3); text-align: center;
  border-inline-end: 1px solid var(--border); gap: var(--space-1); }
.proof--platform .stat:last-child { border-inline-end: 0; }
.proof--platform .stat__value { font-size: var(--text-sm); font-weight: var(--weight-bold);
  color: var(--text-base); letter-spacing: 0; }
.proof--platform .stat__label { font-size: var(--text-xs); }

/* Five across is unreadable on a phone. Two columns keeps each name with
   its description, and the dividers move with them. */
@media (max-width: 60rem) {
  .proof--platform { grid-template-columns: repeat(2, 1fr); }
  .proof--platform .stat { border-block-end: 1px solid var(--border); }
  .proof--platform .stat:nth-child(even) { border-inline-end: 0; }
}

.stat { display: flex; flex-direction: column; gap: var(--space-1); }
.stat__value {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}
.stat__label { font-size: var(--text-xs); color: var(--text-muted); line-height: var(--leading-snug); }

/* -- faq ------------------------------------------------------------------ */

/* Separated cards, from the v6 mockup, rather than a hairline list.

   The list read as one dense block where every question ran into the next.
   As cards each question is a thing you can aim at, which is what a FAQ is
   for: nobody reads it, they scan it for the one question they arrived
   with. The gap does that work, the borders just finish it.

   Colours are tokens rather than the mockup's hex, so this follows the
   theme instead of staying white when the page goes dark. */
/* Questions beside the offer of a person, not stacked above it.

   The aside only appears when one is authored, so the single-column layout
   is the default and nothing shifts on a page that does not use it. It
   drops below the list under 62rem: a 360px illustration in a phone column
   is a scroll tax paid before reaching the answers. */
.faq-layout { display: grid; gap: var(--space-8); }

@media (min-width: 62rem) {
  .faq-layout--aside { grid-template-columns: minmax(0, 1fr) 20rem; align-items: start; }
}

.faq__aside {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-4);
  text-align: center; color: var(--text-muted);
}
.faq__art { inline-size: min(15rem, 70%); block-size: auto; color: var(--accent); }
.faq__aside-body { margin: 0; font-size: var(--text-sm); max-inline-size: 30ch; }

@media (max-width: 61.999rem) {
  .faq__art { display: none; }
}

.faq { display: grid; gap: var(--space-3); max-inline-size: 76ch; }
.faq__item { border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); box-shadow: var(--shadow-sm); overflow: hidden;
  transition: border-color var(--duration-base) var(--ease); }
.faq__item:hover { border-color: var(--border-strong); }
/* The open card is the one being read, so it gets the accent edge. */
.faq__item[open] { border-color: var(--accent); }
.faq__q {
  inline-size: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5);
  background: none;
  border: 0;
  text-align: start;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-snug);
  cursor: pointer;
}
/* A circled control, like the mockup, but keeping our rotating cross: it
   animates between states, where a swapped "+" and "−" glyph cannot. */
.faq__icon { flex: none; inline-size: 28px; block-size: 28px; padding: 6px;
  border: 2px solid currentColor; border-radius: 50%; color: var(--accent);
  transition: transform var(--duration-base) var(--ease); }
.faq__item[open] .faq__icon { transform: rotate(45deg); }
.faq__a { padding: 0 var(--space-5) var(--space-5); color: var(--text-muted); max-inline-size: 68ch; }
.faq__item summary { list-style: none; }
.faq__item summary::-webkit-details-marker { display: none; }

/* -- cta band ------------------------------------------------------------- */

.cta-band {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  padding: clamp(var(--space-10), 6vw, var(--space-16));
  text-align: center;
}
/* The logo is built on a ring. Repeating it oversized and cropped at the
   corner is the one piece of brand geometry the flat build never used, and
   it costs a pseudo-element. Sized in ch so it scales with the band. */
.cta-band::after {
  content: "";
  position: absolute;
  inline-size: 26rem;
  aspect-ratio: 1;
  inset-block-start: -14rem;
  inset-inline-end: -10rem;
  border: 1px solid rgb(255 255 255 / 0.16);
  border-radius: 50%;
  pointer-events: none;
}
.cta-band__inner { position: relative; }
.cta-band--accent { background: var(--blue-600); color: var(--white); }
.cta-band--dark { background: var(--ink-900); color: var(--grey-100); }

/* Soft, for a band that is an offer rather than a close.

   The migration band sits four sections above the real call to action. On
   accent it read as a second ending and pulled attention off the one that
   matters; on dark it was the third heavy band in a row. A pale wash of the
   accent, the same mix the bundle panel and the domain pill use, keeps it
   inside the palette while leaving the closing band the loudest thing on
   the page. */
.cta-band--soft {
  background: linear-gradient(115deg,
    color-mix(in srgb, var(--accent) 9%, transparent),
    color-mix(in srgb, var(--accent) 2%, transparent));
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--text-base);
}
.cta-band--soft::before { display: none; }

/* Consult band, from the v6 mockup: a helper rather than a close.

   A third `cta_band` variant instead of a new block, because the content is
   already exactly what cta_band holds: heading, body, one button. The only
   difference is posture, so only the layout changes.

   Horizontal, and quieter than the accent band. It sits under the plans and
   answers "which of these do I want", so it must not compete with the plan
   cards it is helping the reader choose between. The closing CTA at the foot
   of the page stays loud; this one does not. */
.cta-band--consult { background: var(--ink-900); color: var(--grey-100);
  background-image: radial-gradient(circle at 70% 30%, color-mix(in srgb, var(--accent) 18%, transparent), transparent 26%); }
.cta-band--consult .cta-band__inner { display: grid; grid-template-columns: 1fr auto;
  gap: var(--space-5) var(--space-8); align-items: center; text-align: start; max-inline-size: none; }
.cta-band--consult h2 { font-size: var(--text-xl); margin: 0 0 var(--space-1); }
.cta-band--consult .lede { font-size: var(--text-sm); margin: 0; }
.cta-band--consult .cta-band__actions { grid-column: 2; grid-row: 1 / span 2; }

@media (max-width: 48rem) {
  .cta-band--consult .cta-band__inner { grid-template-columns: 1fr; }
  .cta-band--consult .cta-band__actions { grid-column: 1; grid-row: auto; }
}
.cta-band__inner { display: flex; flex-direction: column; align-items: center; gap: var(--space-4); max-inline-size: 56ch; margin-inline: auto; }
.cta-band h2 { color: inherit; }
.cta-band__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); justify-content: center; margin-block-start: var(--space-2); }

/* -- logo strip ----------------------------------------------------------- */

/* The accreditation strip, rebuilt after measuring what was actually being
   drawn.

   It looked like a weighting problem and was not. `block-size: 28px` sets
   the height of the *canvas*, and two of the four files had most of their
   canvas empty: Google Workspace carried 43% dead space above and below its
   wordmark, Odoo 30%. So at a nominal 28px the artwork was rendering 3.97px
   tall for Google and 11px for Odoo, against 28px for cPanel and Microsoft.
   The strip was not badly balanced, it was drawing two logos at a seventh
   and a third of the intended size. Both viewBoxes have been recropped to
   their measured bounding boxes, which is lossless and changes no artwork.

   A grid rather than a centred flex row: equal cells give an even rhythm
   whatever each mark's width, where equal *gaps* between unequal marks do
   not. Capping both axes means no future logo can dominate the row however
   its canvas is shaped.

   Full colour, per brand decision. The greyscale was also flattening
   Google's yellow to near-white, so the mark was losing a quarter of itself
   on top of being 4px tall.

   Grouped, not distributed. Four equal cells sounded right and looked
   wrong: the marks are 265, 219, 173 and 89 pixels wide, so centring each
   in an equal cell produced four different-sized gaps and the row read as
   randomly spaced. Letting one gap do the spacing makes the four marks a
   single object, which is what a credential strip is.

   The gap scales with the viewport so the group stays proportionate rather
   than bunching on a laptop and stranding on a wide monitor. */
.logos { display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: clamp(2rem, 5vw, 3.75rem); }
.logos img { max-inline-size: 100%; max-block-size: 28px;
  inline-size: auto; block-size: auto; }

/* Tighter once the row has to wrap, or the two lines drift apart. */
@media (max-width: 48rem) {
  .logos { gap: var(--space-8) var(--space-10); }
}
.logos__caption { text-align: center; font-size: var(--text-xs); color: var(--text-subtle); margin-block-end: var(--space-6); letter-spacing: var(--tracking-wide); text-transform: uppercase; }
/* Names of included products that have no mark to show. Deliberately quieter
   than the logos, but the same claim, so it stays legible rather than
   becoming fine print. */
.logos__also { margin-block-start: var(--space-5); text-align: center; font-size: var(--text-sm); color: var(--text-muted); }

/* The "every plan includes" strip: what does not change between tiers, on
   one rule directly under the tiers.

   One panel, the label bold and inline, names separated by an accent
   bullet. This was outlined pills under a centred uppercase caption, which
   read as a second row of things to choose from sitting under an actual row
   of things to choose from. The strip is not a choice, it is one sentence
   ruling a question out, so it is set as a line rather than as objects. */
/* The label leads on its own band, the items sit in a grid beneath it.

   All nine were on one wrapping line, so the label read as the first item in
   the list rather than as its heading, and the wrap fell wherever the
   container ran out: eight items across, then one stranded underneath.

   The band takes the same wash as the value_stack panel so the two blocks on
   this page that introduce a list are introduced the same way. padding sits
   on the parts rather than the container, so the band can run edge to edge
   inside the border. */
.shared { display: grid; gap: var(--space-4); justify-items: start; }

/* The label is its own object, sized to itself, on the accent wash the
   value_stack panel uses so the two blocks that introduce a list on this
   page are introduced the same way.

   Colour is --accent-contrast, not --accent-muted. --accent-muted on this
   wash measures 2.42:1 at the darker end of the gradient, and at 11.7px
   bold that is small text needing 4.5. --accent itself only reaches 4.00
   here, so the semantic token that exists for text on an accent ground is
   the one to use: 9.50:1, and it flips with the theme. */
.shared__label {
  margin: 0;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm);
  background:
    radial-gradient(circle at 30% 28%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 35%),
    linear-gradient(135deg, color-mix(in srgb, var(--accent) 4%, var(--surface)), color-mix(in srgb, var(--accent) 9%, var(--surface)));
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--accent-contrast);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

/* The grid does the aligning and shows nothing of itself: no rule, no box,
   no ground. Four across and two down, fixed rather than auto-fit, because
   auto-fit reflows to whatever happens to fit and hands back the stranded
   single item the grid was introduced to prevent. */
.shared__items {
  list-style: none; margin: 0; padding: 0; inline-size: 100%;
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--space-3) var(--space-5);
}
/* Hanging indent, so a label that wraps aligns its second line under its
   first character rather than under the bullet. */
.shared__items li {
  font-size: var(--text-sm); color: var(--text-muted);
  padding-inline-start: 1.15em; text-indent: -1.15em;
}
/* Decorative, so it is a pseudo-element rather than a character in the
   name: a screen reader reads the list without a bullet announced between
   every item. */
.shared__items li::before {
  content: "\2022"; color: var(--accent); margin-inline-end: var(--space-2);
}

/* 64rem, not 60. At 60rem the four columns are 201px and "WHOIS privacy
   where supported" needs 205px, so it wrapped in a 14px band just above the
   breakpoint. The widest item sets where four columns stop being honest. */
@media (max-width: 64rem) {
  .shared__items { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 34rem) {
  .shared__items { grid-template-columns: 1fr; }
}

/* Testimonials.

   Auto-fit rather than a fixed column count: two quotes should not sit in a
   three-column grid with a hole where the third would be, and the count is
   whatever the editor entered. The modifier class carries the count so a
   single quote can be centred and given a wider measure. */
.quotes { display: grid; gap: var(--space-6); grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); }
.quotes--1 { max-inline-size: 46rem; margin-inline: auto; }

.quote { margin: 0; padding: var(--space-6); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); display: flex; flex-direction: column; gap: var(--space-5); }
.quote__text { margin: 0; font-size: var(--text-lg); line-height: 1.6; color: var(--text-base); }
/* Typographic quotes via content, not in the data: an author pasting a
   quote should not have to think about which characters to use, and the
   marks must not end up inside the value that gets translated. */
.quote__text::before { content: '\201C'; }
.quote__text::after { content: '\201D'; }

.quote__by { display: flex; align-items: center; gap: var(--space-3); margin-block-start: auto; }
.quote__photo { inline-size: 40px; block-size: 40px; border-radius: 50%; object-fit: cover; flex: none; }
.quote__who { display: flex; flex-direction: column; line-height: 1.35; }
.quote__who strong { font-size: var(--text-sm); }
.quote__role { font-size: var(--text-xs); color: var(--text-subtle); }

/* -- post list ------------------------------------------------------------ */

.posts { display: grid; grid-template-columns: 1fr; gap: var(--space-6); }
@media (min-width: 720px) { .posts { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .posts { grid-template-columns: repeat(3, 1fr); } }

.post-card { border-block-start: 2px solid var(--border); transition: border-color var(--duration-base) var(--ease); }
.post-card:hover { border-block-start-color: var(--accent); }
.post-card__link { display: flex; flex-direction: column; gap: var(--space-3); padding-block-start: var(--space-4); block-size: 100%; }
.post-card__meta { display: flex; align-items: center; gap: var(--space-3); font-family: var(--font-mono);
                   font-size: var(--text-2xs); color: var(--text-subtle); letter-spacing: 0.04em;
                   font-variant-numeric: tabular-nums; }
.post-card__cat { color: var(--accent); text-transform: uppercase; letter-spacing: var(--tracking-label); }
.post-card__title { font-family: var(--font-display); font-size: var(--text-xl); font-weight: var(--weight-semibold);
                    letter-spacing: var(--tracking-snug); line-height: var(--leading-snug); text-wrap: balance; }
.post-card__excerpt { font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-normal); }
.post-card__more { margin-block-start: auto; font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--accent); }
/* the arrow points along the reading direction, not always right */
[dir='rtl'] .post-card__more span { display: inline-block; transform: scaleX(-1); }

/* -- figure --------------------------------------------------------------- */

.figure { margin: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.figure img { inline-size: 100%; block-size: auto; border-radius: var(--radius-md); background: var(--surface-raised); }
.figure figcaption { font-size: var(--text-xs); color: var(--text-subtle); line-height: var(--leading-snug); max-inline-size: 68ch; }
.figure--inline { max-inline-size: 520px; }
.figure--full { max-inline-size: none; }
.figure--full img { border-radius: 0; }

/* -- rich text ------------------------------------------------------------ */

.prose { max-inline-size: 68ch; display: flex; flex-direction: column; gap: var(--space-4); color: var(--text-muted); }
.prose h2, .prose h3 { color: var(--text); margin-block-start: var(--space-4); }
.prose ul { padding-inline-start: var(--space-6); display: flex; flex-direction: column; gap: var(--space-2); }
.prose--center { margin-inline: auto; text-align: center; }

/* Code inside prose, for a <code> or <pre> pasted into a rich_text body.
   The dedicated code block below is the better way in, but anything already
   written that way should not render at browser default. */
.prose :where(code) { font-family: var(--font-mono); font-size: 0.9em; background: var(--surface-sunken); border: 1px solid var(--border); border-radius: var(--radius-xs); padding: 0.1em 0.35em; overflow-wrap: anywhere; }
.prose :where(pre) { direction: ltr; text-align: start; overflow-x: auto; background: var(--surface-sunken); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--space-4); }
/* The inline treatment is wrong inside a block: no second border, no
   second background, and no shrunken text against the surrounding copy. */
.prose :where(pre) code { background: none; border: 0; padding: 0; font-size: var(--text-sm); }

/* ---------------------------------------------------------------- code block

   Deliberately plain. There is no syntax highlighter: one would mean a
   Composer dependency or a self-hosted JavaScript library, and neither is
   worth it for a monospace block that is already legible. What it does buy
   is a real surface, a scrollbar instead of an overflow, and a header
   naming the file the snippet belongs to. */
.codeblock { margin: 0; max-inline-size: 68ch; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; background: var(--surface-sunken); }
.codeblock__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-2) var(--space-4); border-block-end: 1px solid var(--border); background: var(--surface); }
.codeblock__name { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-muted); direction: ltr; }
.codeblock__lang { font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--text-subtle); text-transform: uppercase; letter-spacing: var(--tracking-wide); }

/* tabindex on the element makes this keyboard scrollable, which a scrolling
   region has to be: without it a long line is unreachable without a mouse. */
.codeblock__pre { margin: 0; padding: var(--space-4); overflow-x: auto; font-family: var(--font-mono); font-size: var(--text-sm); line-height: 1.6; color: var(--text-base); tab-size: 4; }
.codeblock__pre:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.codeblock__pre code { font: inherit; background: none; border: 0; padding: 0; white-space: pre; }
.codeblock__caption { margin: 0; padding: var(--space-3) var(--space-4); border-block-start: 1px solid var(--border); font-size: var(--text-xs); color: var(--text-subtle); }

/* -- contact form --------------------------------------------------------- */

.contact { display: grid; grid-template-columns: 1fr; gap: var(--space-12); align-items: start; }
@media (min-width: 900px) { .contact { grid-template-columns: 0.85fr 1.15fr; gap: var(--space-16); } }
.contact__intro { display: flex; flex-direction: column; gap: var(--space-3); }
.contact__list { margin: var(--space-6) 0 0; display: flex; flex-direction: column; gap: var(--space-4); }
.contact__list-head { font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-subtle); margin-block-end: var(--space-1); }
.contact__list dt { font-size: var(--text-xs); color: var(--text-subtle); }
.contact__list dd { margin: 2px 0 0; font-size: var(--text-base); }
.contact__list dd a:hover { color: var(--accent); }

.formgrid { display: grid; grid-template-columns: 1fr; gap: var(--space-4); }
@media (min-width: 640px) { .formgrid { grid-template-columns: 1fr 1fr; } }
.formfield--wide { grid-column: 1 / -1; }
.formfield { display: flex; flex-direction: column; gap: var(--space-2); }
.formfield > span { font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-subtle); }
.formfield > span i { font-style: normal; text-transform: none; letter-spacing: 0; opacity: 0.7; }
.formfield input, .formfield textarea {
  inline-size: 100%; padding: var(--space-3); border: 1px solid var(--border-strong);
  border-radius: var(--radius-xs); background: var(--surface); color: var(--text);
  font: inherit; font-size: var(--text-base);
}
.formfield textarea { resize: vertical; line-height: var(--leading-normal); }
.formfield input:focus, .formfield textarea:focus { border-color: var(--accent); outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.formfield em { font-style: normal; font-size: var(--text-xs); color: var(--state-danger); }
.contact__form form > .btn { margin-block-start: var(--space-6); }
.contact__form .cf-turnstile { margin-block-start: var(--space-5); }

.formnote { font-size: var(--text-sm); }
.formnote--fine { margin-block-start: var(--space-3); color: var(--text-subtle); font-size: var(--text-xs); }
.formnote--ok, .formnote--bad { padding: var(--space-3) var(--space-4); border-radius: var(--radius-xs);
  border: 1px solid; margin-block-end: var(--space-5); }
.formnote--ok { color: var(--state-success); border-color: color-mix(in srgb, var(--state-success) 40%, transparent);
  background: color-mix(in srgb, var(--state-success) 10%, transparent); }
.formnote--bad { color: var(--state-danger); border-color: color-mix(in srgb, var(--state-danger) 40%, transparent);
  background: color-mix(in srgb, var(--state-danger) 10%, transparent); }

/* honeypot: positioned away rather than display:none, which bots detect */
.hp { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* -- site chrome ---------------------------------------------------------- */

/* The header's own height, published so the sticky jump bar can sit
   directly beneath it and so jump targets clear both.
   Measured, not derived: the tallest thing in the header is the nav pill
   (2.5rem burger plus its 4px padding), not the 34px logo I first sized
   this from, which put it 15px short and tucked the jump bar under the
   header. Re-measure if the pill or the header padding changes. */
:root { --header-h: 5.25rem; }

.site-header {
  position: sticky;
  inset-block-start: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(10px);
  border-block-end: 1px solid var(--border);
}
.site-header__inner { display: flex; align-items: center; justify-content: space-between; gap: var(--space-6); padding-block: var(--space-4); }
.site-header__logo { display: flex; align-items: center; flex: none; }
/* Sized on height, and the file is cropped to the artwork, so this is the
   height the logo actually draws at. The old mark sat in a 500x500 box it
   filled 58% of, which is why 30px produced a 17px mark next to 18px text. */
.site-header__logo .logo-mark { block-size: 34px; inline-size: auto; }
/* The mark carries its own dark-to-azure gradient, which disappears on a
   dark ground. Two files, swapped by theme, rather than one that half
   vanishes. */
/* No OS-preference rule here, to match tokens.css: the page lands light
   whatever the machine prefers. Left in, this showed the white mark on the
   white header for anyone running their system dark, which is an invisible
   logo rather than a themed one. */
.logo-mark--dark { display: none; }
:root[data-theme='dark'] .logo-mark--light { display: none; }
:root[data-theme='dark'] .logo-mark--dark { display: block; }
:root[data-theme='light'] .logo-mark--light { display: block; }
:root[data-theme='light'] .logo-mark--dark { display: none; }

/* -- header controls ------------------------------------------------------ */

.skiplink {
  position: absolute; inset-inline-start: -9999px; inset-block-start: 0; z-index: 100;
  background: var(--accent); color: #fff; padding: var(--space-3) var(--space-5);
  border-end-end-radius: var(--radius-sm); font-size: var(--text-sm); font-weight: var(--weight-semibold);
}
.skiplink:focus { inset-inline-start: 0; }

.switch { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.switch__opt {
  padding: var(--space-2) var(--space-3); font-size: var(--text-xs); font-weight: var(--weight-semibold);
  color: var(--text-subtle); line-height: 1; display: inline-flex; align-items: center;
  transition: background var(--duration-fast) var(--ease), color var(--duration-fast) var(--ease);
}
.switch__opt:hover { color: var(--text); }
.switch__opt.is-on { background: var(--accent); color: var(--text-on-accent); }

.iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 34px; block-size: 34px; padding: 0;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: transparent; color: var(--text-muted); cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease), color var(--duration-fast) var(--ease);
}
.iconbtn:hover { border-color: var(--accent); color: var(--accent); }
/* Same reasoning as the logo: the icon reflects the theme actually being
   shown, not the operating system's preference. Light is the default, so
   the sun shows and the moon is hidden until dark is chosen. */
.iconbtn .i-moon { display: none; }
:root[data-theme='dark'] .iconbtn .i-sun { display: none; }
:root[data-theme='dark'] .iconbtn .i-moon { display: block; }
:root[data-theme='light'] .iconbtn .i-sun { display: block; }
:root[data-theme='light'] .iconbtn .i-moon { display: none; }

/* -- error pages ---------------------------------------------------------- */

.errorpage { min-block-size: 70vh; display: grid; place-items: center; padding: var(--space-16) var(--space-6); }
.errorpage__inner { max-inline-size: 52ch; display: flex; flex-direction: column; gap: var(--space-4); text-align: center; align-items: center; }
.errorpage__code { font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: var(--tracking-label);
                   text-transform: uppercase; color: var(--accent); }
.errorpage__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); justify-content: center; margin-block-start: var(--space-2); }
.site-header__actions { display: flex; align-items: center; gap: var(--space-3); }

/* The header is logo, language, theme and the pill. Tighten the gap on
   small screens; the CTA inside the pill drops out below 560px on its own,
   leaving just the trigger. My Account now lives in the overlay. */
@media (max-width: 879px) {
  .site-header__inner { gap: var(--space-3); }
}



/* -- footer ----------------------------------------------------------------
   Was a single centred strip: copyright, five links, tagline. That is a
   footer for a site with five pages. With thirty-two it has to be an index,
   which means columns, and columns mean the brand block can carry the things
   a visitor actually looks down here for: how to reach us, and where we are. */

.site-footer {
  border-block-start: 1px solid var(--border);
  padding-block: var(--space-16) var(--space-8);
  font-size: var(--text-sm);
  color: var(--text-subtle);
  background: var(--surface-raised);
}
.site-footer__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-10);
  align-items: start;
}
@media (min-width: 700px) { .site-footer__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .site-footer__grid { grid-template-columns: minmax(0, 1.6fr) repeat(auto-fit, minmax(0, 1fr)); } }

.site-footer__brand { display: flex; flex-direction: column; gap: var(--space-4); align-items: flex-start; }
.site-footer__logo { display: flex; align-items: center; }
.site-footer__logo .logo-mark { block-size: 32px; inline-size: auto; }
.site-footer__statement { color: var(--text-muted); max-inline-size: 40ch; line-height: var(--leading-normal); }
.site-footer__direct { display: flex; flex-direction: column; gap: var(--space-1); align-items: flex-start; }
.site-footer__direct a { color: var(--text); font-weight: var(--weight-medium); }
.site-footer__direct a:hover { color: var(--accent); }

.site-footer__col { display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
.site-footer__col h2 {
  font-family: var(--font-body);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text);
  margin-block-end: var(--space-2);
}
.site-footer__col a { color: var(--text-muted); }
.site-footer__col a:hover { color: var(--accent); }
.site-footer__col address { font-style: normal; color: var(--text-muted); line-height: var(--leading-normal); max-inline-size: 28ch; }

.site-footer__bottom {
  display: flex; flex-wrap: wrap; gap: var(--space-4) var(--space-8);
  justify-content: space-between; align-items: center;
  margin-block-start: var(--space-12);
  padding-block-start: var(--space-6);
  border-block-start: 1px solid var(--border);
  font-size: var(--text-xs);
}

/* -- header pill: one CTA, one trigger ------------------------------------
   The header carries no navigation at all now. Everything that used to sit
   across the top is in the overlay, which is why this pill is the only
   thing competing with the logo. */

.navpill {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-1); border-radius: var(--radius-pill);
  background: var(--surface-raised); border: 1px solid var(--border);
}
.navpill__cta {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-5); border-radius: var(--radius-pill);
  background: var(--accent); color: var(--text-on-accent);
  font-size: var(--text-sm); font-weight: var(--weight-semibold); white-space: nowrap;
  transition: background var(--duration-fast) var(--ease);
}
.navpill__cta:hover { background: var(--accent-hover); }
/* The arrow leans along the reading direction, so it points away from the
   text in Arabic too rather than back into it. */
[dir='rtl'] .navpill__cta svg { transform: scaleX(-1); }
.navpill__burger {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 2.5rem; block-size: 2.5rem; padding: 0;
  border: 0; border-radius: var(--radius-pill);
  background: var(--surface-inverse); color: var(--text-inverse); cursor: pointer;
  transition: opacity var(--duration-fast) var(--ease);
}
.navpill__burger:hover { opacity: 0.85; }
@media (max-width: 560px) { .navpill__cta { display: none; } }

/* -- navigation overlay ---------------------------------------------------
   Present in the DOM of every page and revealed with CSS. Never built by
   script: the site's entire internal link graph is in here, and a menu a
   crawler cannot read is a site of orphans. */

.sitemenu {
  position: fixed; inset: 0; z-index: 100;
  background: color-mix(in srgb, var(--surface-inverse) 55%, transparent);
  opacity: 0; transition: opacity var(--duration-base) var(--ease);
}
.sitemenu.is-open { opacity: 1; }
html.menu-open { overflow: hidden; }

.sitemenu__panel {
  position: absolute; inset: 0;
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--surface);
  padding: clamp(var(--space-6), 5vw, var(--space-16));
  transform: translateY(-1.25rem);
  transition: transform var(--duration-base) var(--ease);
}
.sitemenu.is-open .sitemenu__panel { transform: none; }

.sitemenu__close {
  position: absolute; inset-block-start: var(--space-6); inset-inline-end: var(--space-6);
  inline-size: 2.75rem; block-size: 2.75rem; display: inline-flex;
  align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface-raised); color: var(--text); cursor: pointer;
}
.sitemenu__close:hover { border-color: var(--accent); color: var(--accent); }

.sitemenu__cols { display: grid; grid-template-columns: 1fr; gap: var(--space-12);
  max-inline-size: var(--container); margin-inline: auto; padding-block-start: var(--space-10); }
@media (min-width: 900px) {
  .sitemenu__cols { grid-template-columns: 1fr 1fr; gap: var(--space-16); align-items: start; }
}

.sitemenu__eyebrow {
  display: inline-block; font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-on-accent); background: var(--accent);
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-xs);
  margin-block-end: var(--space-6);
}
.sitemenu__top { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap; margin-block-end: var(--space-8); }
.sitemenu__top .sitemenu__eyebrow { margin-block-end: 0; }

.sitemenu__account {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-5);
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  font-size: var(--text-sm); font-weight: var(--weight-semibold); white-space: nowrap;
  transition: border-color var(--duration-fast) var(--ease), color var(--duration-fast) var(--ease);
}
.sitemenu__account:hover { border-color: var(--accent); color: var(--accent); }
[dir='rtl'] .sitemenu__account svg { transform: scaleX(-1); }

.sitemenu__nav { display: flex; flex-direction: column; }
.sitemenu__nav a {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.6vw, 2.4rem);
  font-weight: var(--weight-semibold); letter-spacing: var(--tracking-tight);
  padding-block: var(--space-3); border-block-end: 1px solid var(--border);
  transition: color var(--duration-fast) var(--ease), padding-inline-start var(--duration-base) var(--ease);
}
.sitemenu__nav a:hover { color: var(--accent); padding-inline-start: var(--space-3); }
.sitemenu__nav a[aria-current='page'] { color: var(--accent); }

.sitemenu__meta { display: flex; flex-direction: column; gap: var(--space-2);
  margin-block-start: var(--space-8); font-size: var(--text-sm); color: var(--text-subtle); }
.sitemenu__meta a { color: var(--text-muted); }
.sitemenu__meta a:hover { color: var(--accent); }
.sitemenu__meta address { font-style: normal; max-inline-size: 34ch; line-height: var(--leading-normal); }

.sitemenu__tabs { display: flex; gap: var(--space-6); margin-block-end: var(--space-8); }
.sitemenu__tabs button {
  padding: 0 0 var(--space-2); border: 0; border-block-end: 2px solid transparent;
  background: none; cursor: pointer; color: var(--text-subtle);
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
}
.sitemenu__tabs button[aria-selected='true'] { color: var(--text); border-block-end-color: var(--accent); }

.tiles { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); }
@media (min-width: 700px) { .tiles { grid-template-columns: repeat(3, 1fr); } }
/* `display: grid` above is an author rule and the UA's [hidden]{display:none}
   is not, so without this the inactive tab panel ignores its own hidden
   attribute and both grids stack in the open menu. Same trap as
   .site-nav-mobile earlier: any element given an explicit display has to be
   told what hidden means. */
.tiles[hidden] { display: none; }

.tile {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-3); min-block-size: 7rem; padding: var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  text-align: center; font-size: var(--text-xs); font-weight: var(--weight-medium);
  color: var(--text-muted);
  transition: border-color var(--duration-fast) var(--ease),
              color var(--duration-fast) var(--ease),
              transform var(--duration-fast) var(--ease);
}
.tile:hover { border-color: var(--accent); color: var(--text); transform: translateY(-2px); }
.tile[aria-current='page'] { border-color: var(--accent); color: var(--text); }
.tile__icon { color: var(--accent); }

/* Solutions read as numbered folders rather than icon tiles: they are
   outcomes, and an icon per outcome is a picture of an abstraction. */
.tile--solution { align-items: flex-start; justify-content: flex-start; text-align: start;
  font-family: var(--font-display); font-size: var(--text-lg);
  font-weight: var(--weight-semibold); color: var(--text); }
.tile__num { font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide); color: var(--accent); }

/* -- whatsapp -------------------------------------------------------------
   Fixed to the far side of the reading direction, so bottom right in
   English and bottom left in Arabic. Change inset-inline-end to right if
   you would rather it stayed physically right in both.

   z-index sits *below* the consent bar's 60 on purpose. The bar is
   full-width along the bottom and temporary; the button reappears the
   moment a choice is made. Raising it above would leave a green circle
   floating over the one thing a first-time visitor has to answer. */

.wa {
  position: fixed;
  inset-block-end: clamp(1rem, 3vw, 1.75rem);
  /* Physical left, not inset-inline-end. These two float over the page
     rather than sitting in the text flow, so mirroring them with the
     reading direction made them swap corners between languages for no
     reader benefit. Fixed positions, same in English and Arabic. */
  left: clamp(1rem, 3vw, 1.75rem);
  right: auto;
  z-index: 55;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 3.25rem;
  block-size: 3.25rem;
  border-radius: var(--radius-pill);
  /* WhatsApp brand green, not a site token: a recoloured WhatsApp icon
     reads as a fake. */
  background: #25D366;
  color: #fff;
  box-shadow: 0 6px 20px rgb(37 211 102 / 0.35), 0 2px 6px rgb(0 0 0 / 0.18);
  transition: transform var(--duration-base) var(--ease),
              box-shadow var(--duration-base) var(--ease);
}
.wa:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 28px rgb(37 211 102 / 0.45), 0 3px 8px rgb(0 0 0 / 0.22);
}
.wa:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 3px; }
.wa svg { display: block; }

@media print { .wa { display: none; } }

/* -- dev banner ----------------------------------------------------------- */

.devbar {
  background: var(--state-warning);
  color: #fff;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  text-align: center;
  padding-block: var(--space-2);
  letter-spacing: 0.04em;
}
/* Sticky and a different colour from the build banner, because the failure
   mode here is reading a draft and believing it is live. */
.devbar--preview { position: sticky; inset-block-start: 0; z-index: 50;
  background: var(--accent); font-weight: var(--weight-semibold); }

/* -- consent ---------------------------------------------------------------
   Rendered on every page and hidden by script once a choice exists, rather
   than decided server side. Deciding server side means reading a cookie,
   which means the page varies per visitor, which means it cannot be cached.
   Same reasoning as the currency switcher. */
/* A small floating card, not a full-width bar.
   Physically bottom right, opposite the WhatsApp button on the bottom
   left, and the same in both languages. Deliberately not mirrored: these
   float over the page rather than reading with it, and swapping their
   corners between English and Arabic moved furniture for no reason. */
.consent {
  position: fixed;
  inset-block-end: clamp(1rem, 3vw, 1.75rem);
  right: clamp(1rem, 3vw, 1.75rem);
  left: auto;
  z-index: 60;
  max-inline-size: min(23rem, calc(100vw - 2rem));
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  /* Translucent over the page, and theme-aware for free: --surface is
     white on light and near-black on dark, so the same rule reads
     correctly in both. */
  background: color-mix(in srgb, var(--surface) 86%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  box-shadow: var(--shadow-lg);
}
/* Without backdrop-filter the translucency is just poor contrast, so go
   opaque instead of pretty. */
@supports not (backdrop-filter: blur(1px)) {
  .consent { background: var(--surface); }
}
.consent__inner {
  max-inline-size: none; margin-inline: 0;
  padding: var(--space-4) var(--space-5);
  display: flex; flex-direction: column; gap: var(--space-3);
  align-items: stretch; justify-content: flex-start;
}
.consent p { margin: 0; font-size: var(--text-xs); line-height: var(--leading-snug);
  color: var(--text-muted); max-inline-size: none; }
/* Both choices the same width. Reject has to be as easy to reach as
   accept, so neither is a link hiding behind the other. */
.consent__actions { display: flex; gap: var(--space-2); flex: none; }
.consent__actions .btn { flex: 1; white-space: nowrap; }
.btn--sm { padding: var(--space-2) var(--space-4); font-size: var(--text-xs); }

/* A feature card that leads somewhere says so. The arrow is the only
   affordance; making the whole card look like a button would imply every
   card is clickable, and most are not. */
.feature--link:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-lg);
  transform: translateY(-2px);
}
.feature--link .feature__title { display: flex; align-items: center; gap: var(--space-2); }
.feature--link .feature__arrow { flex: none; opacity: 0; transform: translate(-2px, 2px);
  transition: opacity var(--duration-fast) var(--ease), transform var(--duration-fast) var(--ease); }
.feature--link:hover .feature__arrow,
.feature--link:focus-visible .feature__arrow { opacity: 1; transform: none; }
.feature--link:hover .feature__title { color: var(--accent); }
[dir='rtl'] .feature--link .feature__arrow { transform: scaleX(-1) translate(-2px, 2px); }
[dir='rtl'] .feature--link:hover .feature__arrow { transform: scaleX(-1); }

/* The slot the enhanced form writes into. Hidden until it has something to
   say, and it takes the same shape as the server-rendered notes so the two
   versions of this form look identical. */
.formnote-slot:focus { outline: none; }
.formnote-slot:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* Everything the form says about itself, gathered under the button that
   submits it. On a phone the top of the form is above the fold by the time
   you reach Send, so a confirmation up there is a confirmation nobody
   sees. */
.formactions { display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; }
.formactions .formnote { margin-block: 0; }
.formactions .btn { align-self: flex-start; }

/* Turnstile draws a fixed 300x65 iframe of its own; this only spaces it. */
.formturnstile { margin-block: var(--space-3); }
.formturnstile .formnote { margin-block-end: 0; }
