/*
Theme Name: Greazy — Oil Slick
Theme URI: https://greazy.ai
Author: Greazy
Description: Dark, iridescent theme for Greazy.ai. Near-black base with a petrol
  shimmer accent ramp (magenta -> teal -> gold). Design tokens live in :root and
  are mirrored into the block editor palette via functions.php.
  Author is the brand, not a legal entity: the filed company name is a pending
  value in functions.php (gz_pending_values) and must be typed once, from the
  certificate, when it is known. It used to be asserted here and in footer.php
  independently, which is one string too many for something a merchant
  application is checked against.
Version: 0.5.0
Requires at least: 6.0
Requires PHP: 7.4
License: Proprietary
Text Domain: greazy
*/

/* ---------------------------------------------------------------
   TOKENS — change these, the whole site follows.
   --------------------------------------------------------------- */
:root {
  /* base */
  --gz-void:        #0B0B0F;   /* page background */
  --gz-surface:     #16151C;   /* cards, panels */
  --gz-surface-hi:  #1F1D28;   /* hover / raised */
  --gz-line:        #2A2833;   /* hairlines */

  /* accent ramp — the oil slick */
  --gz-magenta:     #FF3D8B;
  --gz-teal:        #00E0C6;
  --gz-gold:        #FFC24B;
  --gz-slick: linear-gradient(102deg, var(--gz-magenta) 0%, var(--gz-teal) 52%, var(--gz-gold) 100%);

  /* text */
  --gz-text:        #EDEAF2;
  --gz-muted:       #9B96A8;
  /* WAS #66626F, which is 3.31:1 on --gz-void and fails AA for the small text
     it is used on (.gz-fineprint, .gz-meta, .gz-per, and five more at
     .85-.9rem — body sizes, so the 4.5:1 floor applies, not 3:1). Raised to
     4.60:1 and deliberately no further: --gz-muted is 6.85:1 and if faint
     climbs to meet it the two stop being different tiers and the type loses a
     level of hierarchy. */
  --gz-faint:       #7D7889;

  /* type */
  --gz-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;

  /* geometry */
  /* THREE RADII, AND WHICH ONE A THING GETS IS DECIDED BY WHAT IT IS, not by
     the order it was written in. That distinction used to be missing, and the
     result was two radii in use with no rule behind them: a reader scrolling
     /face-swap/ passed a 22px hero frame, a 14px comparison strip, 22px cards,
     14px ladder rungs, a 22px table card and a 14px consent strip, and nothing
     in this file said why.

       --gz-r-lg  EVERY SURFACE THAT HOLDS CONTENT. A card, a price, a rung, a
                  panel, a table, a framed picture. If it has the hairline and
                  the --gz-surface ground, it has this radius. There are no
                  exceptions, and a new component that wants one is a new
                  component that has not been thought about.
       --gz-r     CHROME. Things that are not content: the dropdown panel, the
                  skip link. Interface furniture, not the page.
       --gz-r-sm  CHIPS. The tag and the status pill, which are type with a
                  background rather than surfaces. Was a bare `6px` in two
                  rules; it is the same 6px, spent from one place. */
  --gz-r-sm: 6px;
  --gz-r:    14px;
  --gz-r-lg: 22px;
  --gz-max:  1180px;

  /* THREE PADDINGS, TIED TO THE CONTAINER'S JOB. There were eight, across
     seven rules and one inline <style> block, and no two surfaces on the same
     screen agreed. A reader does not read a padding value, but they do read
     two cards sitting side by side with their text starting at different
     depths, and what that reads as is a page assembled rather than designed.

       rung   a short surface in a stack, or a deliberately smaller tile
       card   a tile in a row of tiles — the reference, .gz-card's own
       panel  a large standalone surface with a whole argument inside it

     They shrink together on a phone, at the bottom of this file, because the
     relationship between them is what has to hold, not the pixel value. */
  --gz-pad-rung:  16px 20px;
  --gz-pad-card:  22px 24px;
  --gz-pad-panel: 32px 34px;
}

/* ---------------------------------------------------------------
   RESET-ISH
   --------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--gz-void);
  color: var(--gz-text);
  font-family: var(--gz-font);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }
a   { color: var(--gz-teal); text-decoration: none; }
a:hover { color: var(--gz-magenta); }

h1, h2, h3 {
  font-weight: 680;
  letter-spacing: -0.03em;
  line-height: 1.08;
  margin: 0 0 .5em;
}
h1 { font-size: clamp(2.6rem, 6.5vw, 4.6rem); }
h2 { font-size: clamp(1.9rem, 3.8vw, 2.9rem); }
h3 { font-size: 1.18rem; letter-spacing: -0.02em; }

.gz-wrap { width: 100%; max-width: var(--gz-max); margin: 0 auto; padding: 0 24px; }

/* Iridescent text — used sparingly, one phrase per screen. */
.gz-slick-text {
  background: var(--gz-slick);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ---------------------------------------------------------------
   HEADER
   --------------------------------------------------------------- */
.gz-header {
  position: sticky; top: 0; z-index: 50;
  backdrop-filter: blur(14px);
  background: rgba(11, 11, 15, .72);
  border-bottom: 1px solid var(--gz-line);
}
.gz-header .gz-wrap {
  display: flex; align-items: center; gap: 32px;
  height: 68px;
}
.gz-logo {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 1.35rem; font-weight: 760; letter-spacing: -0.045em;
  color: var(--gz-text);
}

/* THE MARK. It is a fixed square, never stretched by the flex row — a logo
   that changes proportion between two pages is not a logo. */
.gz-mark,
.gz-droplet { display: block; flex: none; }

/* The droplet is TALLER THAN IT IS WIDE inside its square, so at the same
   nominal size it reads smaller than the tile did. */
.gz-droplet { transform: scale(1.12); }

/* ── OPTICAL CENTRING OF THE WORDMARK ─────────────────────────────────────
   The two boxes are already centred on each other — measured, 0.6px apart —
   and the word still sat low. That is not a bug in the alignment, it is what
   `align-items: center` does with type: it centres the LINE BOX, and a line
   box is mostly空 space above and below the letters.

   "greazy" is the worst case for it. No capitals and no ascenders, so nothing
   reaches the top of the box, while g, y and z push a descender out of the
   bottom — the visible ink ends up roughly 0.2em below the box's middle. At
   21.6px that is about 4px of apparent droop next to a mark whose mass is
   dead centre.

   So the word is lifted, and only the word: the droplet stays where geometry
   puts it, and the letters move to where the eye expects them. The value is
   in em so it holds if the wordmark is ever resized.

   ⚠️ THE NUMBER WAS SET FROM RENDERED PIXELS, NOT FROM FONT METRICS. Canvas
   TextMetrics said the two were aligned when they were 2.6px apart, because
   the droplet is a PNG with transparent padding around the shape: its BOX
   centre and its INK centre are different points, and only the ink is visible.
   Measured off a 4x screenshot, both ink bands now share a centre line. If the
   droplet is ever redrawn (as vector, ideally) with different padding, this
   value has to be re-measured the same way. */
.gz-logo-word,
.gz-footer-word { transform: translateY(-0.144em); }

/* A hair of lift on the header mark, so the round tile sits optically level
   with the wordmark's baseline rather than mathematically level with it. */
.gz-logo-mark { margin-top: -1px; }

.gz-logo-word { display: inline-block; }

/* ── THE FOOTER MARK ──────────────────────────────────────────────────────
   Same mark, one size up, above the columns. */
.gz-footer-brand {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: clamp(26px, 3vw, 40px);
}

.gz-footer-word {
  font-size: 1.3rem;
  font-weight: 760;
  letter-spacing: -0.045em;
  color: var(--gz-text);
}
.gz-nav { display: flex; gap: 26px; margin-left: auto; align-items: center; }
.gz-nav a { color: var(--gz-muted); font-size: .95rem; font-weight: 520; }
.gz-nav a:hover { color: var(--gz-text); }

/* ── ⚠️ THE BUTTON IN THE NAV KEEPS ITS OWN INK ────────────────────────────
   The rule above is `.gz-nav a` — one class and one element, specificity
   0-1-1 — and .gz-btn-primary is one class, 0-1-0. So the nav's muted grey
   BEAT the button's black on every page of the site, and the one control that
   takes a visitor into the product rendered #9B96A8 on a bright magenta-to-gold
   gradient. Measured on the live page: 1.9:1. It was effectively invisible,
   and it is the sign-in button.

   The token file already states the rule this restores — "Its ink is dark in
   BOTH themes: every stop of the ramp is a bright colour, so white on it is
   unreadable and always will be." The fix is specificity, not a new colour.

   :hover and :focus-visible are named explicitly because `.gz-nav a:hover`
   would otherwise take the grey back the moment the pointer arrives. */
.gz-nav a.gz-btn-primary,
.gz-nav a.gz-btn-primary:hover,
.gz-nav a.gz-btn-primary:focus-visible {
  color: #0B0B0F;
}

/* ---------------------------------------------------------------
   BUTTONS
   --------------------------------------------------------------- */
.gz-btn {
  display: inline-block;
  padding: 13px 26px;
  border-radius: 999px;
  font-weight: 640; font-size: .97rem; letter-spacing: -0.01em;
  border: 0; cursor: pointer;
  transition: transform .18s ease, box-shadow .18s ease, opacity .18s ease;
}
.gz-btn-primary {
  background: var(--gz-slick);
  color: #0B0B0F;
  box-shadow: 0 6px 26px -8px rgba(255, 61, 139, .55);
}
.gz-btn-primary:hover {
  color: #0B0B0F;
  transform: translateY(-2px);
  box-shadow: 0 12px 34px -8px rgba(0, 224, 198, .5);
}
.gz-btn-ghost {
  background: transparent; color: var(--gz-text);
  border: 1px solid var(--gz-line);
}
.gz-btn-ghost:hover { background: var(--gz-surface-hi); color: var(--gz-text); border-color: var(--gz-faint); }

/* ---------------------------------------------------------------
   HERO
   --------------------------------------------------------------- */
.gz-hero { position: relative; padding: 108px 0 88px; overflow: hidden; }

/* the slick: THREE blurred blooms drifting behind the copy — one per colour in
   the brand gradient. It ran with two for a long time, which meant the page
   glowed pink and teal and never gold, and the gradient in the logo and the
   buttons had a third colour the background never answered. */
.gz-hero::before,
.gz-hero::after,
.gz-hero > .gz-wrap::before {
  content: ""; position: absolute; border-radius: 50%;
  /* A little brighter than the .30 it sat at. Blur stays at 90px: the glow is
     meant to be felt, not seen as a circle, and raising opacity without the
     blur to match is how these turn into visible discs. */
  filter: blur(90px); opacity: .42; pointer-events: none;
}
.gz-hero::before {
  width: 620px; height: 620px; top: -220px; left: -140px;
  background: radial-gradient(circle, var(--gz-magenta), transparent 68%);
  animation: gz-drift 19s ease-in-out infinite alternate;
}
.gz-hero::after {
  width: 560px; height: 560px; top: -80px; right: -160px;
  background: radial-gradient(circle, var(--gz-teal), transparent 68%);
  animation: gz-drift 23s ease-in-out infinite alternate-reverse;
}
/* THE GOLD ONE HANGS OFF THE WRAP, not off .gz-hero — an element has only two
   pseudo-elements and the other two are spoken for. .gz-wrap is positioned with
   z-index 1, so it is its own stacking context and a negative z-index here puts
   the bloom behind the hero's text without escaping to the back of the page. */
.gz-hero > .gz-wrap::before {
  z-index: -1;
  width: 520px; height: 520px;
  bottom: -240px; left: 34%;
  background: radial-gradient(circle, var(--gz-gold), transparent 68%);
  /* Dimmer than the other two. Gold on a near-black page reads far hotter than
     magenta or teal at the same value, and at .42 it looked like a light left
     on rather than a bloom. */
  opacity: .26;
  animation: gz-drift 27s ease-in-out infinite alternate;
}

@keyframes gz-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(60px, 50px, 0) scale(1.14); }
}
@media (prefers-reduced-motion: reduce) {
  .gz-hero::before,
  .gz-hero::after,
  .gz-hero > .gz-wrap::before { animation: none; }
}

.gz-hero .gz-wrap { position: relative; z-index: 1; }
.gz-hero-inner { max-width: 760px; }

.gz-eyebrow {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 7px 15px 7px 11px;
  border: 1px solid var(--gz-line); border-radius: 999px;
  background: var(--gz-surface);
  font-size: .82rem; font-weight: 560; color: var(--gz-muted);
  letter-spacing: .01em; margin-bottom: 26px;
}
.gz-eyebrow b {
  background: var(--gz-slick); color: #0B0B0F;
  padding: 2px 8px; border-radius: 999px;
  font-size: .72rem; font-weight: 760; letter-spacing: .04em;
}

.gz-hero p.gz-lede {
  font-size: 1.19rem; color: var(--gz-muted);
  max-width: 560px; margin: 22px 0 36px;
}
.gz-cta-row { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; }
/* MUTED, NOT FAINT — this one line sits over the drip. The canvas lifts the
   ground behind it, which COSTS contrast for light text: measured, faint drops
   from 3.31:1 to 2.34:1 there, and raising the faint token cannot rescue it
   (at 4.60:1 on the plain page it is still only ~3.25:1 under the animation).
   --gz-muted clears the floor in both places. The same class is used by
   inc/render-tool-page.php on the four tool pages, which have no canvas — it
   simply reads a little stronger there. */
.gz-cta-note { color: var(--gz-muted); font-size: .87rem; margin-left: 4px; }

/* ---------------------------------------------------------------
   SECTIONS
   --------------------------------------------------------------- */
.gz-section { padding: 90px 0; }
.gz-section-head { max-width: 620px; margin-bottom: 46px; }
.gz-section-head p { color: var(--gz-muted); font-size: 1.06rem; margin: 0; }
/* A SECOND HEADING INSIDE ONE SECTION needs air above it that the section
   padding cannot give: the padding is on the section, and this heading is in
   the middle of one. inc/tool-figures.php carried this as an inline
   style="margin-top:56px" — one number no media query could reach — which is
   why it is here and why it shrinks with everything else on a phone. */
.gz-section-head.is-spaced { margin-top: 56px; }
@media (max-width: 720px) {
  .gz-section-head.is-spaced { margin-top: 40px; }
}

/* The header is sticky and 68px tall, so an anchor jump parks its target
   underneath the bar and the reader lands on a section whose heading is hidden.
   With the nav and the footer between them linking to #chat, #suite and
   #pricing eight times over, this is the normal way people move around the
   page, not an edge case. 68 plus a little air. */
.gz-section, .gz-hero { scroll-margin-top: 84px; }

/* ---------------------------------------------------------------
   SHOWCASE GRID
   --------------------------------------------------------------- */
.gz-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
  gap: 20px;
}
/* ── THE CARD RECIPE, AND IT IS THE ONLY ONE ──────────────────────────────────
   Nineteen things on this site read as a card and they used to be built from
   four different recipes. This is the one they are all built from now: the
   hairline, the --gz-surface ground, --gz-r-lg, and a padding token chosen
   from the three above. .gz-price, .gz-ladder li, .gz-compliance,
   .gz-table-card, .gz-feature, .gz-crop and the three framed pictures all
   spend exactly these values, each at its own place in this file and each
   pointing back here.

   ANYTHING SHARING THIS RECIPE SHARES ITS BEHAVIOUR TOO. assets/css/
   gz-depth.css hangs the tilt, the sheen and the reveal off a `data-gz-tilt`
   attribute rather than off `.gz-card`, so a surface does not have to be a
   .gz-card to be treated as one. That is what fixes the complaint that some of
   this page was enhanced and some of it was left basic: the four plan cards
   sitting directly under the four proposition cards were inert, and the reason
   was that every depth rule on the site named a class only half of them had. */
.gz-card {
  position: relative;
  background: var(--gz-surface);
  border: 1px solid var(--gz-line);
  border-radius: var(--gz-r-lg);
  /* THE CLIP STAYS, AND THE THING IT WAS QUIETLY BREAKING IS GONE INSTEAD.
     `overflow: hidden` is a grouping property, and per CSS Transforms 2 a
     grouping property forces the used value of `transform-style` to `flat` —
     so the `preserve-3d` and the two `translateZ` offsets that gz-depth.css
     carried, under a comment claiming they gave the card a thickness, had
     never once rendered. Deleting them is the honest half of that; keeping
     this is the safe half, because a hundred cards is the wrong place to trade
     a guarantee that nothing escapes the radius for an effect the file was
     only pretending to have. .gz-card-media carries a matching radius below
     regardless, so a picture is right with or without this line. */
  overflow: hidden;
}

/* HOVER IS AMBIENT; THE GRADIENT IS AN AFFORDANCE. The two used to be one rule,
   so a text-only card on /face-swap/ lit an iridescent hairline under the
   pointer and then did nothing when it was clicked — eleven of them on that
   page, sixteen on /image-editing/. The lift and the brighter edge are a
   material property and stay on everything: this is a raised surface and the
   light moves on it. The slick is reserved for the cards that go somewhere,
   which is the same rule .gz-status is written under forty lines below — the
   gradient is how this page says a thing is real, and it cannot be spent on
   anything else.

   THE LIFT, THE EDGE AND THE TRANSITION ARE NOT HERE. They belong to every
   surface built from this recipe rather than to .gz-card alone, so they are
   stated once at the very bottom of this file under THE CARD FAMILY'S SHARED
   BEHAVIOUR. What is left in this section is the part that is genuinely this
   component's own. */

/* iridescent hairline, drawn as a gradient border — LINKED CARDS ONLY */
.gz-card::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px; background: var(--gz-slick);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  opacity: 0; transition: opacity .22s ease; pointer-events: none;
}
.gz-card.is-linked:hover,
.gz-card.is-linked:focus-visible { border-color: transparent; }
.gz-card.is-linked:hover::after,
.gz-card.is-linked:focus-visible::after { opacity: 1; }
/* A card that can be reached with a keyboard gets everything the pointer gets.
   It had none of it: the only focus mark on a linked card was the global 2px
   ring, and the lift lived on :hover alone. Outside the (hover: hover) query
   above on purpose — a keyboard is a keyboard whatever the pointer is — and
   inside :where() for the same reason the hover is: the depth layer's lift is
   larger and has to win. */
:where(.gz-card.is-linked):focus-visible { transform: translateY(-4px); outline: none; }

.gz-card-media {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--gz-surface-hi);
  /* Inside a 1px border, so one pixel short of the card's own radius or a
     hairline of --gz-surface shows in each top corner. */
  border-radius: calc(var(--gz-r-lg) - 1px) calc(var(--gz-r-lg) - 1px) 0 0;
}
.gz-card-media img { width: 100%; height: 100%; object-fit: cover; transition: transform .5s ease; }
/* A PORTRAIT PHOTOGRAPH IN A LANDSCAPE BOX, FRAMED ON THE SUBJECT AND NOT ON
   ITS MIDDLE. The pipeline output in assets/tools/ is 620x1102 — a standing or
   seated person — and a 4/3 box cropping it from the centre keeps the torso and
   cuts the face off at the top, which on a card for a tool that changes a face
   is the one part that must survive. 20% is measured against those five files:
   it puts the head in the upper third and keeps the room and the window that
   make the four cards read as one scene through four operations.
   A modifier rather than a rule on every card, because it is wrong for a
   screenshot — a UI shot is composed for its centre. */
.gz-card-media.is-top img { object-position: 50% 20%; }
.gz-card:hover .gz-card-media img { transform: scale(1.045); }
.gz-card-body { padding: var(--gz-pad-card); }
.gz-card-body h3 { margin-bottom: 6px; }
.gz-card-body p { color: var(--gz-muted); font-size: .94rem; margin: 0; }

.gz-tag {
  display: inline-block; font-size: .7rem; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--gz-void); background: var(--gz-slick);
  padding: 3px 9px; border-radius: var(--gz-r-sm); margin-bottom: 11px;
}

/* Availability pill — "Live now", "In build".
   OUTLINED AND MUTED, NEVER THE GRADIENT, and that is the point of it. The
   slick is how this page says "this exists"; five of the six tools do not yet,
   and they must not be able to borrow the look of the one that does. Keeping
   the gradient for shipped things is the visual half of not selling a roadmap
   as a product. */
.gz-status {
  display: inline-block; font-size: .7rem; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--gz-muted); background: transparent;
  border: 1px solid var(--gz-line);
  padding: 2px 9px; border-radius: var(--gz-r-sm); margin-bottom: 11px;
}
.gz-tag + .gz-status { margin-left: 8px; }

/* A card for a tool that does not exist yet.
   THE PILL ALONE LOST THE ARGUMENT IT WAS MAKING. It sits next to .gz-tag,
   which is the full gradient, so on a grid of six the loudest mark on a roadmap
   tile was the one that looks like every shipped product on the internet and
   the quiet one carried the actual news. Holding the media back is what makes
   five-of-six legible from across the room, which is the whole reason the split
   exists — somebody who skims this grid must not come away believing he can buy
   a face swap today.
   Applied to the media only: the copy stays at full contrast, because the
   description of an unbuilt tool is still the thing being read. */
.gz-card.is-pending .gz-card-media { opacity: .62; filter: saturate(.8); }
.gz-card.is-pending:hover .gz-card-media { opacity: .8; }

/* ---------------------------------------------------------------
   FEATURE BLOCK — the one shipped product
   Media on one side, room for real detail on the other. It exists because the
   flagship cannot look like one tile in six: the grid below it is mostly
   things you cannot buy yet, and the thing you can buy has to outrank them.
   --------------------------------------------------------------- */
.gz-feature {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  background: var(--gz-surface);
  border: 1px solid var(--gz-line);
  border-radius: var(--gz-r-lg);
  overflow: hidden;
}
/* The image is taken out of flow on purpose. Left in flow it would set the row
   height itself — a portrait-shaped placeholder makes the whole block as tall
   as the picture, with the copy stranded in the middle of a white column of
   nothing. Absolute means the TEXT decides how tall this block is and the
   image fills whatever that turns out to be, at any aspect ratio it arrives
   in. The min-height only matters if the copy is ever cut very short. */
.gz-feature-media { position: relative; background: var(--gz-surface-hi); min-height: 260px; }
.gz-feature-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.gz-feature-body { padding: var(--gz-pad-panel); }
/* Held below the page-level h2 scale: this is a panel heading, not a section.
   BOTH LEVELS, BECAUSE THE LEVEL DEPENDS ON WHERE THE PANEL SITS. On the tool
   pages this panel is a section of its own and its heading is an h2. On the
   front page it sits inside #how-it-works, under that section's h2, so its
   heading has to be an h3 — and an h3 falls to the 1.18rem body-card size,
   which would make the one shipped product's panel quieter than the four cards
   for tools nobody can run yet. The size belongs to the panel, not to the tag
   name. */
.gz-feature-body h2,
.gz-feature-body h3 { font-size: clamp(1.5rem, 2.6vw, 2rem); margin-bottom: 16px; }
.gz-feature-body p { color: var(--gz-muted); font-size: .98rem; margin: 0 0 15px; }
.gz-feature-note {
  margin: 22px 0 0; padding-top: 16px;
  border-top: 1px solid var(--gz-line);
  color: var(--gz-faint); font-size: .88rem;
}
/* Stacks earlier than the 720px breakpoint — two columns of prose in ~430px
   each is where the paragraphs start reading as a newspaper. */
@media (max-width: 880px) {
  .gz-feature { grid-template-columns: 1fr; }
  /* min-height off, so the ratio is what sets the height once it is stacked.
     4/3 AND IT WAS 16/9. Once the panel stacks, this box is the full width of
     the wrap and its ratio is the only thing deciding how much of the picture a
     phone gets: at 390px a 16/9 band is 192px tall, and a screenshot of a
     desktop app in 192px is a texture. 4/3 is 256px for the same width, which
     is the difference between a reader seeing that there is a conversation in
     there and a reader seeing a dark rectangle. It costs nothing anywhere else
     — every other .gz-feature in this theme is .is-panel, which sets
     `aspect-ratio: auto` at a higher specificity and never reaches this line. */
  .gz-feature-media { aspect-ratio: 4 / 3; min-height: 0; }
  /* THE TOKEN NARROWS, NOT THIS ONE RULE. A panel at 880px is a panel
     everywhere at 880px — the scope panel on the tool pages and the plan cards
     were each shrinking on their own schedule, which is how three surfaces on
     one screen ended up with three different insets on a tablet. */
  :root { --gz-pad-panel: 26px 24px; }
}

/* ---------------------------------------------------------------
   PRICING
   --------------------------------------------------------------- */
.gz-price-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; }
/* A PLAN CARD IS A CARD. It sits in a track the same width as .gz-card's, one
   section below four of them on the front page, and it used to be built from a
   private recipe — its own padding, no hover, no reveal, and not one depth rule
   reached it because .gz-price-grid is not .gz-grid. Same padding token, same
   radius, same ground, same behaviour. The only thing that stays private to
   this component is .is-featured's gradient border, which is a statement about
   one plan rather than about the shape of a card. */
.gz-price {
  background: var(--gz-surface); border: 1px solid var(--gz-line);
  border-radius: var(--gz-r-lg); padding: var(--gz-pad-card);
  display: flex; flex-direction: column;
}
.gz-price.is-featured {
  border: 1px solid transparent;
  background:
    linear-gradient(var(--gz-surface), var(--gz-surface)) padding-box,
    var(--gz-slick) border-box;
}
.gz-price h3 { color: var(--gz-muted); font-size: .87rem; text-transform: uppercase; letter-spacing: .1em; font-weight: 700; }
.gz-price .gz-amount { font-size: 2.9rem; font-weight: 720; letter-spacing: -0.04em; margin: 6px 0 2px; }
/* Tight under the amount: on the paid cards a .gz-price-line follows it and
   carries the gap down to the bullets, so the billing terms and the one-line
   positioning read as a pair rather than as two stranded captions. */
.gz-price .gz-per { color: var(--gz-faint); font-size: .9rem; margin-bottom: 10px; }
.gz-price-line { color: var(--gz-muted); font-size: .93rem; margin: 8px 0 22px; }
.gz-price ul { list-style: none; padding: 0; margin: 0 0 26px; }
.gz-price li { color: var(--gz-muted); font-size: .94rem; padding: 7px 0 7px 24px; position: relative; }
.gz-price li::before {
  content: ""; position: absolute; left: 0; top: 15px;
  width: 7px; height: 7px; border-radius: 50%; background: var(--gz-slick);
}
/* THE FIGURE IS THE THING BEING COMPARED, so it is the thing at full contrast.
   The rest of the line is the unit and it can stay muted; without this a plan
   card is four sentences of one colour and a reader scanning four cards for
   "how much do I get" has to read all of them. */
.gz-price li strong { color: var(--gz-text); font-weight: 700; }

/* ── THE SECOND CURRENCY IS MARKED, BECAUSE THERE ARE TWO AND THEY ARE NOT
   INTERCHANGEABLE ────────────────────────────────────────────────────────────
   A plan carries an allowance of token credits, which the conversation spends,
   and an allowance of golden credits, which pictures and video spend. They
   refill on the same clock and one of them also lands daily, and until this
   rule existed all of that arrived as four identical bullets with four
   identical dots — the front page saying "one number" while six feature pages
   said two. --gz-gold is already in the ramp at the top of this file; the
   second currency is named golden, and spending the colour it is named after
   is the cheapest way to make one card legible at a glance.
   A FLAT COLOUR AND NOT THE SLICK, and that is the same rule .gz-status is
   written under: the gradient means a thing has shipped. Every tool that
   spends a golden credit is in build. */
.gz-price li.is-golden::before { background: var(--gz-gold); }

.gz-price .gz-btn { margin-top: auto; text-align: center; }

/* Top-up cards. Smaller on purpose: a one-off purchase must not carry the same
   visual weight as a subscription, or somebody buys a pack believing they have
   subscribed and the misunderstanding comes back later as a dispute. */
.gz-price.is-compact { padding: var(--gz-pad-rung); }
.gz-price.is-compact .gz-amount { font-size: 2.1rem; }
.gz-price.is-compact p { color: var(--gz-muted); font-size: .93rem; margin: 4px 0 24px; }

/* ...and the footprint has to agree with that, which for a while it did not.
   Three cards in the plans' own grid is the case auto-fit is worst at: it drops
   the fourth track and hands the width back to the survivors, so three top-ups
   in a 1132px row came out at ~364px each against the four plan cards' ~268px.
   The one-off purchases rendered LARGER than the subscriptions — the argument
   above, backwards, made by the layout while the padding and type were busy
   making it forwards. Capping the track and packing to the start keeps them
   narrower than a plan card and left-aligned under them. */
.gz-price-grid.is-topups {
  grid-template-columns: repeat(auto-fit, minmax(220px, 240px));
  justify-content: start;
}

/* Sub-head inside a section that already has a .gz-section-head. */
.gz-subhead { max-width: 620px; margin: 62px 0 28px; }
/* ONE HAIRLINE, WHERE THE SUBJECT ACTUALLY CHANGES. 62px of air was carrying
   the whole of the difference between "here is the product that runs" and
   "here are four tools that do not", and 62px is also the gap between two
   sub-heads about the same thing — so the front page read as eight tiles in a
   row rather than as two groups, which is exactly what the owner said it read
   as. The rule is the divider; it is not a card, it holds nothing, and it is
   the same hairline every surface on this site is drawn with.
   THE MAX-WIDTH IS RELEASED WITH IT: a 620px rule under a 1180px row stops
   halfway across and reads as an underline on the heading above rather than as
   a division of the page. */
.gz-subhead.is-divided {
  max-width: none;
  border-top: 1px solid var(--gz-line);
  padding-top: 46px;
}
.gz-subhead.is-divided > * { max-width: 620px; }
.gz-subhead h3 { font-size: 1.5rem; margin-bottom: .35em; }
.gz-subhead p { color: var(--gz-muted); font-size: 1rem; margin: 0; }

/* Terms that belong with the prices rather than in the footer. */
.gz-fineprint { color: var(--gz-faint); font-size: .88rem; max-width: 760px; margin: 26px 0 0; }

/* ---------------------------------------------------------------
   FOOTER
   --------------------------------------------------------------- */
.gz-footer {
  border-top: 1px solid var(--gz-line);
  padding: 54px 0 40px;
  color: var(--gz-faint); font-size: .9rem;
}
.gz-footer-top { display: flex; gap: 40px; flex-wrap: wrap; justify-content: space-between; margin-bottom: 34px; }
/* THE COLUMN HEADINGS ARE h2 AND WERE h4, at the same size and weight. The
   element changed because four h4s following an h2 with no h3 between them is a
   hole in the document outline on every page of the site; the appearance did
   not change at all, which is why both selectors are here. `font-weight` and
   `line-height` are now stated rather than inherited from the h4 default —
   without them an h2 would arrive at 680/1.08 from the base heading rule and
   the footer would suddenly shout. */
.gz-footer .gz-footer-head,
.gz-footer h4 {
  color: var(--gz-text);
  font-size: .84rem;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: .09em;
  text-transform: uppercase;
  margin: 0 0 12px;
}
.gz-footer ul { list-style: none; padding: 0; margin: 0; }
.gz-footer li { padding: 4px 0; }
.gz-footer a { color: var(--gz-muted); }
.gz-footer a:hover { color: var(--gz-text); }

/* ---------------------------------------------------------------
   TRUST AND CONSENT STRIP
   Three flat statements and two links, said once, after the prices.
   WAS: a flex row led by a gradient pill, with a comment here calling it
   furniture this brand could not do without. The pill is gone and so is that
   framing. A badge on a landing page raises a subject the rest of the page has
   no reason to raise, and a class name raises it just as loudly as a heading
   does — source is read too. Nothing in this strip is a badge, a seal or a
   certification, nothing in it claims to be one, and nothing should be added
   here later that is.
   Three equal columns, links spanning the full width beneath them.
   --------------------------------------------------------------- */
.gz-compliance {
  border: 1px solid var(--gz-line); border-radius: var(--gz-r-lg);
  background: var(--gz-surface); padding: var(--gz-pad-panel);
  /* ONE COLUMN. The auto-fit track this replaced laid the single paragraph
     into a 230px column and left the remaining two thirds of the panel empty,
     so a full-width box rendered as a narrow strip of text with a lot of
     nothing beside it. There is one paragraph here; it gets the panel. */
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
}
.gz-compliance p { margin: 0; max-width: 78ch; color: var(--gz-muted); font-size: .88rem; line-height: 1.55; }
.gz-compliance strong { display: block; color: var(--gz-text); font-weight: 640; margin-bottom: 3px; }
.gz-compliance-links {
  grid-column: 1 / -1;
  display: flex; gap: 20px; flex-wrap: wrap;
  border-top: 1px solid var(--gz-line); padding-top: 16px;
  font-size: .88rem;
}

/* ---------------------------------------------------------------
   BLOG / GENERIC CONTENT
   --------------------------------------------------------------- */
.gz-entry { padding: 60px 0; }
.gz-entry article { max-width: 720px; margin: 0 auto 46px; }
.gz-entry h2 a { color: var(--gz-text); }
.gz-entry h2 a:hover { color: var(--gz-magenta); }
.gz-meta { color: var(--gz-faint); font-size: .85rem; margin-bottom: 14px; }

@media (max-width: 720px) {
  /* MOBILE NAV. This rule used to be `.gz-nav { display: none }`, which took
     every link on the page with it — including the only sign-up button above
     the fold. A phone visitor had no way to start an account without scrolling
     the whole page. The header now grows to two rows instead: logo on the
     first, the links wrapping across the second. No drawer, no JavaScript,
     nothing to open, and the primary button stays under a thumb.

     IT ALSO STOPS BEING STICKY HERE, and that is the price of the two rows.
     Five items including "Acceptable use" wrap to two or three lines on a 360px
     screen, and a sticky header that tall stands at 110-120px — a fifth of the
     viewport, permanently, for the whole session. A phone reader gives up more
     of the page to the furniture than a desktop reader does, which is backwards.
     Static, so it scrolls away after the first screen; the sign-up button is
     still the first thing under the logo when the page opens, and it is
     repeated four times in the price grid. */
  .gz-header { position: static; }
  .gz-header .gz-wrap {
    height: auto; flex-wrap: wrap; gap: 12px 18px;
    padding-top: 12px; padding-bottom: 12px;
  }
  .gz-nav {
    width: 100%; margin-left: 0;
    flex-wrap: wrap; gap: 12px 18px;
  }
  .gz-nav a { font-size: .9rem; }
  .gz-nav .gz-btn { padding: 9px 18px; font-size: .9rem; }

  /* No sticky bar to clear once the header scrolls away, so the offset that
     rescues anchor targets on desktop is only dead space here. */
  .gz-section, .gz-hero { scroll-margin-top: 0; }

  .gz-hero { padding: 68px 0 60px; }
  .gz-section { padding: 62px 0; }
}

/* ===============================================================
   THE LANDING PAGE REWRITE — everything below this line arrived with
   front-page.php when the suite came off it and the real app screenshots
   went on.

   NOTHING ABOVE THIS LINE WAS DELETED, and that is deliberate rather than
   timid: .gz-card, .gz-tag, .gz-status, .is-pending and .gz-feature are all
   still used by inc/render-tool-page.php on the twenty-four tool URLs. The
   front page stopped calling for some of them; the stylesheet is shared, so
   they stay.
   =============================================================== */

/* ---------------------------------------------------------------
   SCREENSHOT CROPPING, IN CSS, UNDER PROTEST

   THE HONEST VERSION OF THIS IS A CROPPED FILE and it does not exist yet.
   These rectangles are measured in pixels against exact PNGs in
   assets/app-shots/, and they cost real bytes: the hero downloads a full
   screenshot to display a slice of it.

   ── ALL THREE RECTANGLES ARE MEASURED AND CHECKED, 2026-08-19 ─────────────
   This block used to open "EVERY NUMBER BELOW IS STALE AND MUST BE
   RE-MEASURED", on the grounds that the numbers came from an ad-hoc capture
   session and a test account — one of whose friend lists contained a character
   named after a real adult performer — and that the replacement demo captures
   were "not in the theme yet". THAT IS NO LONGER TRUE and had not been for
   some time: demo-memory.png, demo-friend-list.png, demo-conversation.png and
   their -phone variants are all in assets/app-shots/ and all rendered, and the
   warning was contradicted three lines further down by a note recording that
   one of the three HAD been re-measured. A standing warning that is wrong is
   worse than none: it tells the next reader that all three rectangles are
   suspect, so none of them gets checked.

   EACH OF THE THREE HAS NOW BEEN RENDERED AND LOOKED AT against the file it
   names, and what each one frames is recorded on the rule itself. Nothing
   below is inherited from a withdrawn capture.

   ⚠️ THE RULE THAT DOES STAND: a crop rectangle from one screenshot applied to
   another does not break — it silently frames the wrong thing. IF A SHOT IS
   RETAKEN, RE-MEASURE ITS RECTANGLE IN THE SAME COMMIT, and re-read what the
   comment claims the frame contains, because that claim is the thing that goes
   wrong first.

   WHEN PROPERLY CROPPED WEBP ASSETS LAND: delete .gz-crop from the markup,
   delete the blocks of custom properties below, and let the images be
   images.

   HOW IT WORKS, since the arithmetic is not obvious:
     the frame gets the crop's aspect ratio;
     the image is blown up so the crop rectangle is exactly frame-width;
     a percentage translate slides it into place. Translate percentages
     resolve against the IMAGE's own box, which is why the offsets divide by
     the image's dimensions and the width divides by the crop's.
   --------------------------------------------------------------- */
.gz-crop {
  position: relative;
  overflow: hidden;
  border-radius: var(--gz-r-lg);
  border: 1px solid var(--gz-line);
  background: var(--gz-surface);
  aspect-ratio: var(--gz-crop-w) / var(--gz-crop-h);
  /* The <picture> wrapper is an inline box whose only child is taken out of
     flow, so it contributes an empty line box and nothing else. Harmless here
     because aspect-ratio fixes the height, but zeroed so it can never add a
     baseline gap if this frame is ever used without a ratio. */
  line-height: 0;
}
.gz-crop img {
  position: absolute;
  top: 0;
  left: 0;
  /* The global `img { max-width: 100% }` would clamp the blow-up and quietly
     defeat the whole mechanism, leaving a correctly-shaped frame holding a
     squashed full screenshot. */
  max-width: none;
  width: calc(100% * var(--gz-img-w) / var(--gz-crop-w));
  height: auto;
  transform: translate(
    calc(-100% * var(--gz-crop-x) / var(--gz-img-w)),
    calc(-100% * var(--gz-crop-y) / var(--gz-img-h))
  );
}

.gz-shot { margin: 0; }
/* MUTED AND NOT FAINT: the hero caption sits over the drip canvas, which
   lifts the ground behind it and costs contrast for light text — the same
   measurement that governs .gz-cta-note above. */
.gz-shot figcaption {
  color: var(--gz-muted);
  font-size: .86rem;
  margin-top: 14px;
}

/* THE MEMORY PROOF — hero, and the LCP element.
   demo-memory.png, 2880x1800 (demo account, desktop 1440x900 at 2x).
   Crop: the "quote me" exchange and nothing else.

   MEASURED off demo-memory.png as it actually is, 2026-08-15, and RE-CHECKED
   BY RENDERING IT 2026-08-19. An earlier rectangle here was guessed from a
   withdrawn capture and cut two sentences in half — the hero read "you're clo"
   and "all this wis". This one frames the exchange the section claims, whole:
   the user's "morning. did you sleep any better?", and the companion answering
   with "You've been up since five on the same problem — I remember that",
   which it was never told in this conversation. THAT LINE IS THE WHOLE PROOF;
   if a re-shoot moves it, this rectangle moves with it. */
.gz-shot-proof .gz-crop {
  --gz-img-w: 2880; --gz-img-h: 1800;
  --gz-crop-x: 1150; --gz-crop-y: 150;
  --gz-crop-w: 1560; --gz-crop-h: 620;
}
.gz-hero .gz-shot { margin-top: 54px; }

/* THE FRIEND LIST — 3. Who you talk to.
   demo-friend-list.png, 2880x1800 (demo account, desktop 1440x900 at 2x).
   Crop: the icon rail and the sidebar column.
   The rectangle used to be a safety device — it stopped above a row holding a
   character named after a real adult performer in a test account's frame. That
   file is no longer referenced by any template, so this is framing now.

   RENDERED AND CHECKED 2026-08-19: the frame holds the three seeded companions
   of the demo account — Maya, Hugo, Yuki — each with a face, a name and an
   online dot, and nothing else. It contains no count, which is deliberate: the
   estate states no number of ready-made characters anywhere, so a rectangle
   that happened to crop one in would put a figure on the page by accident. */
.gz-shot-roster .gz-crop {
  --gz-img-w: 2880; --gz-img-h: 1800;
  --gz-crop-x: 0; --gz-crop-y: 0;
  --gz-crop-w: 790; --gz-crop-h: 920;
}
.gz-shot-roster { max-width: 340px; margin-inline: auto; }

/* THE CONVERSATION — 4. It is a conversation, not a session.
   demo-conversation.png, 2880x1800 (demo account, desktop 1440x900 at 2x),
   cropped to the conversation column so the sidebar is outside the frame.
   What is left is the friend sending a photograph of its own day, which is the
   warmest and least suggestive thing in the whole screenshot set.

   RENDERED AND CHECKED 2026-08-19: the sidebar is indeed outside the frame,
   and what the rectangle holds is the companion answering about its own
   houseplants, sending a photograph of them, and asking back about the reader's
   basil. That is exactly what the alt text on both pages describes. */
.gz-shot-convo .gz-crop {
  --gz-img-w: 2880; --gz-img-h: 1800;
  --gz-crop-x: 785; --gz-crop-y: 0;
  --gz-crop-w: 2095; --gz-crop-h: 1800;
}

/* ---------------------------------------------------------------
   PROSE BLOCKS
   A measure, not a column of the full 1180px wrap: a 1100px line of body
   copy is unreadable and the sections that carry the argument are text.
   --------------------------------------------------------------- */
.gz-prose { max-width: 720px; }
.gz-prose p { color: var(--gz-muted); font-size: 1.03rem; margin: 0 0 16px; }
.gz-prose p:last-child { margin-bottom: 0; }

/* An honest limit, or an aside that qualifies the claim above it. Quieter
   than body copy and marked with a rule, so it reads as the page volunteering
   something rather than as fineprint hiding it. */
.gz-note {
  color: var(--gz-faint);
  font-size: .92rem;
  border-left: 2px solid var(--gz-line);
  padding-left: 15px;
  margin: 22px 0 0;
}

/* ---------------------------------------------------------------
   SHOWCASE — copy beside a screenshot.

   NOT .gz-feature, which absolutely-positions its media so the text sets the
   block height. That is the right trick for a bordered panel holding a photo
   at any aspect ratio, and the wrong one here: these images are cropped to a
   known rectangle and must keep it, because the rectangle is load-bearing.
   --------------------------------------------------------------- */
.gz-showcase {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 54px;
  align-items: center;
}
.gz-showcase-body h2 { font-size: clamp(1.7rem, 3vw, 2.4rem); margin-bottom: 18px; }
.gz-showcase-body p { color: var(--gz-muted); font-size: 1rem; margin: 0 0 14px; }
.gz-showcase-body p:last-child { margin-bottom: 0; }
.gz-showcase-body .gz-note { margin-top: 20px; }

/* Stacks at the same width .gz-feature does, and for the same reason: two
   columns of prose at ~430px each is where paragraphs start reading as a
   newspaper. */
@media (max-width: 880px) {
  .gz-showcase { grid-template-columns: 1fr; gap: 34px; }
  /* THE HEADING COMES FIRST ON A PHONE, whichever side the picture is on for
     a desktop reader. `is-reversed` puts its figure first in the source so
     the image lands left on a wide screen; stacked, that would open the
     section with a screenshot and no heading to say what it is. */
  .gz-showcase.is-reversed .gz-showcase-body { order: -1; }
  .gz-shot-convo { max-width: 460px; margin-inline: auto; }
}

/* ---------------------------------------------------------------
   THE REMOVAL LADDER
   Five depths, as five things. The same five described in a paragraph read
   as one vague reassurance; listed, they read as a control somebody built on
   purpose, which is what they are.
   --------------------------------------------------------------- */
.gz-ladder {
  list-style: none;
  padding: 0;
  margin: 24px 0 0;
  display: grid;
  gap: 12px;
  max-width: 720px;
}
.gz-ladder li {
  color: var(--gz-muted);
  font-size: .96rem;
  line-height: 1.55;
  background: var(--gz-surface);
  border: 1px solid var(--gz-line);
  border-radius: var(--gz-r-lg);
  padding: var(--gz-pad-rung);
}
.gz-ladder strong { display: block; color: var(--gz-text); font-weight: 640; margin-bottom: 2px; }

/* ---------------------------------------------------------------
   LANGUAGES
   Pills, not links, until there is something behind them — front-page.php
   only emits an <a> when the translated homepage actually exists. The two
   states are styled identically on purpose: a visitor is being told the app
   speaks six languages, and which of the six have a landing page yet is our
   problem rather than news.
   --------------------------------------------------------------- */
.gz-langs { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 10px; }
.gz-langs a,
.gz-langs span {
  display: inline-block;
  padding: 9px 18px;
  border: 1px solid var(--gz-line);
  border-radius: 999px;
  background: var(--gz-surface);
  color: var(--gz-muted);
  font-size: .95rem;
}
.gz-langs a:hover { color: var(--gz-text); border-color: var(--gz-faint); }

/* ---------------------------------------------------------------
   FAQ
   Two columns of question and answer, all of it visible. Half of these
   questions exist so that a nervous visitor — or a reviewer — can read the
   answer without first discovering that the heading is clickable.
   --------------------------------------------------------------- */
.gz-faq {
  display: grid;
  /* TWO COLUMNS, CAPPED — not auto-fit. auto-fit put THREE columns on a 1132px
     wrap, which gave each answer a 345px measure: five- and six-word lines,
     and a question that wrapped onto two lines before it had finished being a
     question. Two columns is ~540px each, which is a readable measure and
     still half the scrolling of one column over thirteen answers. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
  max-width: 1120px;
  gap: 34px 56px;
  /* The trust panel used to butt straight onto the last answer. */
  margin-bottom: clamp(38px, 4vw, 56px);
}

/* Items are top-aligned so two answers of different lengths still start on the
   same line — a grid row stretches to its tallest item, and centred content
   inside that row reads as misaligned rather than as roomy. */
.gz-faq-item { align-self: start; }

/* .gz-faq USED TO MEAN TWO CONTRADICTORY THINGS AT ONCE, and both shipped: the
   rule above made it a two-column grid, and a second block at the bottom of
   this file made it a <details> accordion — so on /image-editing/ and on all
   twenty translated tool URLs a disclosure list was laid out as a two-column
   grid across the full 1180px wrap. A `:has(> details)` selector sat here
   telling the two apart by what was inside them.

   THE PATCH IS GONE BECAUSE THE DISAGREEMENT IS. Every FAQ block in the estate
   — the home page, /ai-chat/, /ai-companion/, /face-swap/, /head-swap/,
   /character-swap/, /image-editing/ and the twenty translated tool URLs through
   inc/render-tool-page.php — is now .gz-faq > .gz-faq-item > h3 + p. One shape,
   one rule. Restoring <details> to any template puts an accordion back inside
   this grid, so restore the selector with it or do not restore either. */
.gz-faq-item h3 { font-size: 1.06rem; margin-bottom: 9px; }
.gz-faq-item p { color: var(--gz-muted); font-size: .95rem; margin: 0; }
.gz-faq-item .gz-faq-link { margin-top: 10px; font-size: .9rem; }

/* ---------------------------------------------------------------
   CLOSING CTA
   One line, one button. A closing section that argues the case again is a
   page that did not trust the nine sections above it.
   --------------------------------------------------------------- */
.gz-closing { text-align: center; padding: 96px 0 108px; }
.gz-closing h2 { max-width: 700px; margin-left: auto; margin-right: auto; }
.gz-closing p { color: var(--gz-muted); margin: 0 0 28px; }

/* ===============================================================
   THE COMPONENTS THAT HAD NO RULES AT ALL

   Everything below this line renders in the theme today and was reaching the
   browser unstyled — a raw <ul> with bullets, a borderless HTML table, a
   <details> arrow — on the twenty translated tool URLs that inc/render-tool-
   page.php serves, and on the English pages that use the same classes.

   THE RULES ARE NOT NEW. Four page templates each echo an inline <style> block
   carrying some subset of them, and every one of those blocks is annotated
   "these belong in style.css" — page-character-swap.php goes as far as saying
   "moving them to style.css fixes the rest for free", which is exactly what
   this does: the pages that inline them keep working (identical declarations,
   so the duplicate simply wins with the same values), the pages that never had
   them are fixed, and whoever owns those templates can now delete their blocks
   without anything changing.

   Tokens only, no raw hex — the same constraint the inline blocks were written
   under, so this is a move rather than a redesign.
   =============================================================== */

/* Long-form prose with headings inside it — /faq/ and the English character
   swap page. The section padding does the spacing everywhere else; inside a
   single .gz-prose column, consecutive h2s need their own air, and the first
   one must not push the block away from the heading above it. */
.gz-prose h2 { margin: 64px 0 .5em; }
.gz-prose > h2:first-child { margin-top: 0; }
/* A sub-heading and a plain list inside that column. These lived only in
   page-image-editing.php's inline <style> — so the same markup on any other
   page rendered an unspaced h3 and a browser-default bulleted list.

   DIRECT CHILDREN, AND A LIST WITH NO CLASS OF ITS OWN. Held down to that
   because the descendant version of these three rules is a trap, and moving
   them out of one page's inline block is exactly what springs it: .gz-ladder
   and .gz-checks are both <ul> elements that sit directly inside a .gz-prose
   column — the removal ladder on /ai-companion/, the limits list on
   /ai-chat/ — and `.gz-prose ul` outranks `.gz-ladder` by one element, so the
   rungs took a 20px browser indent and 4px of list padding in place of their
   own. The same for `.gz-prose h3` against the nine `.gz-qa h3` headings on
   /character-swap/. A component inside a prose column keeps its own rules. */
.gz-prose > h3 { margin: 34px 0 .6em; }
.gz-prose > ul:not([class]) { color: var(--gz-muted); margin: 0 0 16px; padding-left: 20px; }
.gz-prose > ul:not([class]) > li { padding: 4px 0; }

/* Question-and-answer pairs inside a prose column. */
.gz-qa { border-top: 1px solid var(--gz-line); margin-top: 24px; padding-top: 24px; }
.gz-qa h3 { margin-bottom: .55em; }
.gz-qa p:last-child { margin-bottom: 0; }
.gz-qa-links { display: flex; gap: 18px; flex-wrap: wrap; font-size: .9rem; margin-top: 12px; }

/* The eyebrow without its gradient <b> pill. The base rule's left padding is
   cut short to make room for a pill that is not there, so a plain label sits
   4px off-centre in its own capsule until this puts it back. */
.gz-eyebrow.is-plain { padding-left: 15px; }

/* THE SCOPE PANEL — a .gz-feature whose media column holds a list instead of a
   picture, because the tool pages have no photography and an empty slab of
   --gz-surface-hi reads as an image that failed to load. Without padding the
   list touches three edges of the panel; without min-height:0 the 260px floor
   that exists to stop a photo collapsing pads a short list with dead space. */
.gz-feature-media.is-panel {
  display: flex;
  align-items: center;
  padding: var(--gz-pad-panel);
  min-height: 0;
  /* AND THE 880px RULE HAS TO BE ANSWERED TOO. Once .gz-feature stacks, its
     media column is given `aspect-ratio: 16 / 9` so a photograph keeps a shape
     — which on a panel of text means a fixed-height box that either strands the
     list in the middle of it or overflows a long one. A panel is as tall as its
     contents at every width. */
  aspect-ratio: auto;
}
.gz-feature-media.is-panel .gz-checks { margin: 0; }

/* What a tool does and does not touch. Marked with a rule rather than a bullet
   or a tick: a green check beside a sentence reads as a feature that has
   shipped, and half of what these lists carry is a limit. */
.gz-checks {
  list-style: none;
  margin: 28px 0 0;
  padding: 0;
  display: grid;
  gap: 10px;
  max-width: 720px;
}
.gz-checks li {
  color: var(--gz-text);
  font-size: .95rem;
  line-height: 1.5;
  border-left: 2px solid var(--gz-line);
  padding: 2px 0 2px 14px;
}

/* THE SAME LIST INSIDE A CARD BODY, WHICH HAD NO RESET AND NEEDED ONE. There
   is a reset for the panel case directly above, and the card case — the four
   tool tiles on /features/ — got neither. It inherited `margin: 28px 0 0`
   inside a card that has already paid for its own inset, and `--gz-text`,
   which is BRIGHTER than the card's own lead sentence at --gz-muted: three
   supporting facts out-shouting the line they support. */
.gz-card-body .gz-checks { margin: 14px 0 0; max-width: none; }
.gz-card-body .gz-checks li { color: var(--gz-muted); font-size: .92rem; }

/* THE COMPARISON TABLE, AND THE ONE DECLARATION IT CANNOT SHIP WITHOUT.
   A table is wider than a phone. `min-width: 0` is what allows the card to be
   narrower than its content — a grid or flex child defaults to min-width:auto
   and grows to the table's intrinsic width instead — and body{overflow-x:
   hidden} then clips the overflow WITH NO SCROLLBAR, so the last column simply
   does not exist on a 360px screen and nothing indicates that it should.
   The two go together; neither works alone. Check at 360px. */
.gz-table-card {
  min-width: 0;
  overflow-x: auto;
  border: 1px solid var(--gz-line);
  border-radius: var(--gz-r-lg);
  background: var(--gz-surface);
}
.gz-table { width: 100%; border-collapse: collapse; font-size: .95rem; }
.gz-table th,
.gz-table td {
  padding: 14px 18px;
  text-align: left;
  white-space: nowrap;
  border-bottom: 1px solid var(--gz-line);
}
.gz-table tbody tr:last-child th,
.gz-table tbody tr:last-child td { border-bottom: 0; }
.gz-table thead th { color: var(--gz-text); font-weight: 640; }
.gz-table tbody th { color: var(--gz-muted); font-weight: 520; }
.gz-table td { color: var(--gz-muted); }
/* The column the reader is on. Held to a surface change and full-strength
   text: the gradient is reserved for things that have shipped. */
.gz-table .is-current { color: var(--gz-text); background: var(--gz-surface-hi); }

/* The sentence that catches a reader who is on the wrong tool page. */
.gz-cross { color: var(--gz-muted); max-width: 720px; margin: 22px 0 0; }

/* THE ACCORDION RULES USED TO BE HERE — forty lines styling .gz-faq details,
   summary, the WebKit marker and a +/− glyph — and they are deleted with the
   markup that used them. No template in this theme renders <details> any more:
   /image-editing/ and inc/render-tool-page.php's twenty translated tool URLs
   were the last two, and both now render .gz-faq-item like every other page.
   .gz-faq-a went with them; the answer is a plain <p> inside the item.

   THE ARGUMENT IS RECORDED IN THE TEMPLATES rather than repeated here. The
   short version: an accordion hides each answer behind a click, and half of
   these questions exist so a nervous visitor — or a reviewer — can read the
   answer without first discovering the heading was clickable. */

/* THE LANGUAGE SWITCHER shares .gz-langs with the front page's pill strip, and
   the pill rule paints every <span> inside it — which on the switcher is the
   label, not a language. Stripped back to a caption, and the language you are
   already reading is marked (it carries aria-current="page" as well, so the
   information is not colour alone). */
.gz-langs .gz-langs-label {
  padding: 9px 0;
  border: 0;
  background: transparent;
  color: var(--gz-faint);
  font-size: .9rem;
}
.gz-langs a.is-current {
  color: var(--gz-text);
  background: var(--gz-surface-hi);
  border-color: var(--gz-faint);
}

/* A CARD THAT IS A LINK. gz_card() turns its <div> into an <a> when it is given
   a destination, and an <a> is inline and teal: the tile would collapse out of
   the grid and its heading would inherit the link colour, because .gz-card-body
   h3 sets no colour of its own. Nothing passes a destination today — this is
   here so that the first caller that does gets a card rather than a bug. */
.gz-card.is-linked { display: block; color: inherit; }
.gz-card.is-linked:hover { color: inherit; }

/* ---------------------------------------------------------------
   PHONE

   THE BREAKPOINT IS 720px AND IT IS PINNED TO THE MARKUP. front-page.php
   swaps the hero and conversation screenshots with
   <source media="(max-width: 720px)">, and the crop rectangles below belong
   to the phone-width files that swap in. If one moves and the other does
   not, the page crops a desktop screenshot with a phone's coordinates and
   shows a slice of empty background. The two numbers move together or not at
   all.

   THE PHONE SHOTS ARE DIFFERENT PHOTOGRAPHS, not these ones scaled. They
   were taken at phone width, so the bubbles are phone-sized and the text is
   legible at 390px — which is the whole reason for the swap.
   --------------------------------------------------------------- */
@media (max-width: 720px) {
  /* demo-memory-phone.png, 780x1688 (demo account, 390x844 at 2x) — the same
     exchange at phone width, RE-MEASURED against the demo file 2026-08-15.
     A DIFFERENT RECTANGLE FROM THE DESKTOP ONE, and not because of the ratio:
     every message wraps to more lines in a narrow column, so the memory line
     sits much further down the same conversation. It is framed here with the
     user's "ha. i've been up since five with the same problem" underneath it,
     because on a phone the confirmation is what makes the claim land. */
  .gz-shot-proof .gz-crop {
    --gz-img-w: 780; --gz-img-h: 1688;
    --gz-crop-x: 0; --gz-crop-y: 700;
    --gz-crop-w: 780; --gz-crop-h: 610;
  }
  .gz-hero .gz-shot { margin-top: 38px; }

  /* demo-conversation-phone.png, 780x1688 (demo account, 390x844 at 2x) —
     uncropped: the phone frame is already the shot, so this one survives a
     re-shoot unchanged. */
  .gz-shot-convo .gz-crop {
    --gz-img-w: 780; --gz-img-h: 1688;
    --gz-crop-x: 0; --gz-crop-y: 0;
    --gz-crop-w: 780; --gz-crop-h: 1688;
  }
  /* A 0.46 ratio at full column width is a screenshot taller than the phone
     holding it. Held to something a thumb can scroll past. */
  .gz-shot-convo { max-width: 280px; }
  .gz-shot-roster { max-width: 260px; }

  .gz-showcase { gap: 28px; }
  .gz-faq { gap: 26px; }
  .gz-closing { padding: 62px 0 70px; }
}

/* ===============================================================
   THE FEATURES MENU AND THE MOBILE DRAWER
   =============================================================== */

/* ── THE DROPDOWN ───────────────────────────────────────────────
   Opens on hover AND on focus-within, both in plain CSS, so it works
   with no JavaScript and works from the keyboard. The script adds
   click (for touch, which has no hover) and Escape. */
.gz-menu { position: relative; display: flex; align-items: center; }

.gz-menu-btn {
  display: inline-flex; align-items: center; gap: 7px;
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--gz-muted); font-size: .95rem; font-weight: 520;
  font-family: inherit; line-height: inherit;
}
.gz-menu-btn:hover, .gz-menu:focus-within .gz-menu-btn { color: var(--gz-text); }

.gz-menu-caret {
  width: 7px; height: 7px; margin-top: -3px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg); transition: transform .2s ease;
}
.gz-menu:hover .gz-menu-caret,
.gz-menu:focus-within .gz-menu-caret,
.gz-menu-btn[aria-expanded="true"] .gz-menu-caret { transform: rotate(-135deg) translate(-2px,-2px); }

.gz-menu-panel {
  position: absolute; top: calc(100% + 14px); left: 50%;
  transform: translateX(-50%) translateY(-6px);
  min-width: 208px; padding: 8px;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--gz-surface-hi, #14141c);
  /* --gz-r, and it is chrome rather than content, which is the whole of why it
     keeps the smaller radius while every content surface moved up to
     --gz-r-lg. It was a bare `14px` — the one raw literal in a file that spends
     tokens everywhere else, so the day the token moved this would have stayed
     behind. */
  border: 1px solid var(--gz-line); border-radius: var(--gz-r);
  box-shadow: 0 18px 44px -14px rgba(0,0,0,.8), 0 2px 8px -3px rgba(0,0,0,.6);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease, visibility .18s;
  z-index: 60;
}

/* A HOVER BRIDGE. Without it the 14px gap between the button and the panel is
   dead space, the pointer leaves on the way down and the menu shuts under the
   cursor — the single most common way a dropdown feels broken. */
.gz-menu-panel::before {
  content: ''; position: absolute; left: 0; right: 0; top: -16px; height: 16px;
}

.gz-menu:hover .gz-menu-panel,
.gz-menu:focus-within .gz-menu-panel,
.gz-menu-panel.is-open {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}

.gz-menu-panel a {
  display: block; padding: 9px 12px; border-radius: 9px;
  color: var(--gz-muted); font-size: .92rem; white-space: nowrap;
}
.gz-menu-panel a:hover, .gz-menu-panel a:focus-visible {
  background: var(--gz-surface); color: var(--gz-text);
}
/* The hub is the first item and the parent of the rest, so it reads as the
   heading of the group rather than a sibling of the pages under it. */
.gz-menu-panel a:first-child {
  color: var(--gz-text); font-weight: 560;
  border-bottom: 1px solid var(--gz-line);
  border-radius: 9px 9px 0 0; margin-bottom: 4px; padding-bottom: 11px;
}

/* ── THE BURGER ─────────────────────────────────────────────────
   Desktop has a row of links and nothing to open, so it is not rendered
   visually there at all. */
.gz-burger { display: none; }

@media (max-width: 720px) {
  /* THE NAV IS NOW A PANEL, NOT A WRAPPED ROW. The old rule let the links wrap
     onto a second line, which was right for five flat items and impossible for
     eleven with a submenu among them — that is a paragraph of blue text, not a
     navigation. */
  .gz-header { position: sticky; top: 0; }
  .gz-header .gz-wrap {
    height: auto; flex-wrap: wrap; gap: 0;
    padding-top: 10px; padding-bottom: 10px;
    align-items: center;
  }

  .gz-burger {
    display: flex; flex-direction: column; justify-content: center; gap: 5px;
    margin-left: auto; padding: 10px;
    /* 44px is the floor for anything a thumb has to hit. */
    min-width: 44px; min-height: 44px;
    background: none; border: 0; cursor: pointer;
  }
  .gz-burger span {
    display: block; width: 22px; height: 2px; border-radius: 2px;
    background: var(--gz-text); transition: transform .24s ease, opacity .18s ease;
  }
  .gz-burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .gz-burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
  .gz-burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  .gz-nav {
    width: 100%; margin-left: 0; order: 3;
    flex-direction: column; align-items: stretch; gap: 0;
    /* CLOSED IS max-height 0 AND NOT display:none, so opening can animate and
       so the links stay in the accessibility tree in document order. */
    max-height: 0; overflow: hidden;
    transition: max-height .3s ease, opacity .2s ease;
    opacity: 0;
  }
  .gz-nav.is-open {
    max-height: 82vh; overflow-y: auto; opacity: 1;
    padding: 8px 0 14px;
    -webkit-overflow-scrolling: touch;
  }

  .gz-nav > a, .gz-menu-btn {
    display: flex; align-items: center; min-height: 46px;
    padding: 4px 2px; font-size: 1rem;
    border-bottom: 1px solid var(--gz-line);
  }
  .gz-nav > a:last-of-type { border-bottom: 0; }

  /* The dropdown becomes an inline list. No hover on a phone, so it is open
     whenever the drawer is — nesting a tap-to-open inside a tap-to-open is two
     taps to reach a page that is one tap away on every other site. */
  .gz-menu { display: block; width: 100%; }
  .gz-menu-btn { width: 100%; justify-content: space-between; }
  .gz-menu-caret { display: none; }
  .gz-menu-panel {
    position: static; transform: none; opacity: 1; visibility: visible;
    pointer-events: auto; min-width: 0; box-shadow: none;
    background: none; border: 0; border-bottom: 1px solid var(--gz-line);
    padding: 2px 0 8px 12px; gap: 0;
  }
  .gz-menu-panel::before { display: none; }
  .gz-menu-panel a { padding: 11px 8px; font-size: .95rem; min-height: 44px; display: flex; align-items: center; }
  .gz-menu-panel a:first-child { border-bottom: 0; margin-bottom: 0; }

  .gz-nav .gz-btn {
    margin-top: 12px; justify-content: center;
    padding: 12px 18px; font-size: .95rem;
  }

  /* Sticky again, and it can be: the header is one 56px row when shut, and the
     drawer only takes height while it is open. */
  .gz-section, .gz-hero { scroll-margin-top: 64px; }
  .gz-hero { padding: 56px 0 52px; }
  .gz-section { padding: 54px 0; }
}

/* ── NO HORIZONTAL OVERFLOW, ANYWHERE ───────────────────────────
   One long word, one wide table or one fixed-width figure makes the whole page
   scroll sideways, which on a phone reads as the site being broken. These are
   the four sources that actually occur here. */
@media (max-width: 720px) {
  html, body { overflow-x: hidden; }
  .gz-wrap { padding-left: 18px; padding-right: 18px; }
  /* THE THREE PADDINGS NARROW TOGETHER. At 390px the wrap is already down to
     18px each side, and a 34px inset inside that leaves a 300px card holding a
     230px line. They keep their relationship to each other, which is the part
     a reader actually sees — one step down each, in one place, for every
     surface on the site. */
  :root {
    --gz-pad-rung:  14px 16px;
    --gz-pad-card:  18px 20px;
    --gz-pad-panel: 24px 22px;
  }
  img, video, canvas, figure, table, pre { max-width: 100%; }
  h1, h2, h3, p, li, a { overflow-wrap: anywhere; }
  .gz-grid { grid-template-columns: 1fr; }
}

/* Between phone and desktop the four-card grids are two-up rather than four
   squeezed: a 170px card is a title and an ellipsis. */
@media (min-width: 721px) and (max-width: 1024px) {
  .gz-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ===============================================================
   TOOL PAGE IMAGERY
   The four tool pages shipped ~2,400 words each and no pictures.
   These are the rules for the pictures that fixed that.
   =============================================================== */

/* The hero output, directly under the claim it proves. */
.gz-tool-shot { margin: 40px 0 0; max-width: 520px; }
.gz-tool-shot.is-narrow { margin-inline: auto; }
.gz-tool-shot img {
  width: 100%; height: auto; display: block;
  border: 1px solid var(--gz-line); border-radius: var(--gz-r-lg);
  background: var(--gz-surface);
}
.gz-tool-shot figcaption,
.gz-compare figcaption,
.gz-pair figcaption {
  margin-top: 12px; color: var(--gz-muted);
  font-size: .9rem; line-height: 1.5;
}
.gz-tool-shot figcaption b,
.gz-compare figcaption b,
.gz-pair figcaption b { display: block; color: var(--gz-text); font-weight: 600; }

/* THE COMPARISON ROW — four across on a desktop, because the whole argument is
   that you can see all four at once. Anything that breaks the row breaks the
   point, which is why the mobile rule below scrolls rather than stacks. */
/* FIVE ACROSS: source, your footage, and the three results. The whole argument
   is that you can see them all at once, which is why the mobile rule scrolls
   rather than stacks. */
.gz-compare {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 16px;
}
.gz-compare-item { margin: 0; }
/* A FRAMED PICTURE IS A CARD WITH A PICTURE IN IT. These three frames — the
   five-across strip, the before/after pair and the hero output — carry the
   same hairline on the same ground as every other surface, so they take the
   same radius. The strip and the pair were at --gz-r and the hero at
   --gz-r-lg, which put two radii inside one section of /image-editing/. */
.gz-compare-item img {
  width: 100%; height: auto; display: block;
  border: 1px solid var(--gz-line); border-radius: var(--gz-r-lg);
  background: var(--gz-surface);
}

/* Before / after, side by side. */
.gz-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; }
.gz-pair figure { margin: 0; }
.gz-pair img {
  width: 100%; height: auto; display: block;
  border: 1px solid var(--gz-line); border-radius: var(--gz-r-lg);
  /* The ground the other two frames have always had. Without it the before /
     after pair sat on the void while the generated example directly beneath it
     sat on --gz-surface, in the same section, on the same page. */
  background: var(--gz-surface);
}

/* THE PHONE CAPTURE. Lived in page-ai-companion.php's inline <style> and
   nowhere else, so any other template using the class would have rendered an
   uncapped 780px 2x screenshot. The captures are 2x, so a 780px file is a
   390px screen and 390 is the device it was taken on. */
.gz-shot-phone { margin: 34px 0 0; }
.gz-shot-phone img {
  margin: 0 auto;
  max-width: 390px;
  border: 1px solid var(--gz-line);
  border-radius: var(--gz-r-lg);
  background: var(--gz-surface);
}
.gz-shot-phone figcaption { max-width: 390px; margin-inline: auto; }

/* THE SWAP STUDIO — /head-swap/ and /character-swap/, one file.
   demo-swap-studio-tabs.webp, 780x490: the title bar, the app's own Preview
   badge and the three tabs.

   ⚠️ IT IS A CROPPED FILE AND NOT A CROPPED FRAME, WHICH IS THE WHOLE POINT.
   The capture it comes from published three things neither page will say in
   its own voice: "Up to 15 min · max 400 MB", a duration and file-size ceiling
   on a tool that is in build, which both pages otherwise refuse to state as a
   threshold; a "(—cr)" cost chip; and two "Example media is not bundled here"
   placeholders that read as a broken screen. A .gz-crop rectangle would have
   hidden all three and still served every byte to anyone who opened the asset
   URL, so the uncropped file is out of the theme instead — see the note at the
   top of the cropping section, which asked for exactly this.

   RE-CROP, DO NOT RE-FRAME, if the app's header or tab bar changes height. The
   source capture is regenerated by staged/greazy-shots/shoot_apps.py; the
   rectangle that produced this one is (0, 0, 780, 490). */
/* Sized and centred the way .gz-shot-phone is — the figure spans the wrap and
   the image is capped and centred inside it — so the four tool pages present
   their one picture of the app at one width and one alignment. */
.gz-shot-strip { margin: 34px 0 0; }
.gz-shot-strip img {
  width: 100%; height: auto; display: block;
  margin: 0 auto;
  max-width: 390px;
  border: 1px solid var(--gz-line);
  border-radius: var(--gz-r-lg);
  background: var(--gz-surface);
}
.gz-shot-strip figcaption { max-width: 390px; margin-inline: auto; }
/* The shared `figcaption b { display: block }` is scoped to .gz-tool-shot,
   .gz-compare and .gz-pair and does not reach this figure, whose caption is two
   lines: the status, then the note about the animate tab. */
.gz-shot-strip figcaption b { display: block; color: var(--gz-text); font-weight: 600; }

/* .gz-closing centres its children with text-align, which a flex row ignores:
   two buttons would sit hard left under a centred heading. front-page.php's
   closing section has one button and never met this. */
.gz-closing .gz-cta-row { justify-content: center; }

@media (max-width: 1024px) {
  .gz-compare { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 720px) {
  /* IT SCROLLS SIDEWAYS INSTEAD OF STACKING, and this is the one place on the
     site where that is right. Stacked, the four frames are four screens apart
     and the comparison — the entire reason the row exists — cannot be made.
     Side by side in a swipe, they stay comparable. The scroll is contained to
     this element, so the PAGE still never scrolls horizontally. */
  .gz-compare {
    display: flex; gap: 14px;
    overflow-x: auto; scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 10px;
    /* Bleed to the screen edges so a half-visible next frame says "swipe". */
    margin-inline: -18px; padding-inline: 18px;
  }
  .gz-compare-item {
    flex: 0 0 68%; scroll-snap-align: center;
  }
  .gz-pair { grid-template-columns: 1fr; gap: 18px; }
  .gz-tool-shot { margin-top: 30px; max-width: none; }
}

/* ===============================================================
   THE CARD FAMILY'S SHARED BEHAVIOUR

   LAST IN THE FILE ON PURPOSE, and that placement is load-bearing rather than
   tidy. Every rule below names a surface that the rest of this stylesheet has
   already defined — .gz-price sets its own border at line ~470, .gz-ladder li
   at ~760, .gz-compliance at ~560 — and a hover that has to change one of those
   borders must come after the rule that set it or it ties on specificity and
   loses to source order. Written up beside .gz-card it did exactly that: the
   plan cards and the rungs lifted on hover and their edges stayed dark, so
   four of the nine surfaces got half the treatment. This section is the one
   place a card's shared behaviour is stated, and new members are added here.

   WHY THESE NINE. They are the nine things style.css builds from the card
   recipe — the hairline, the --gz-surface ground, --gz-r-lg and one of the
   three padding tokens. A reader cannot tell them apart at rest, so they must
   not be tellable apart under a pointer either. assets/css/gz-depth.css gives
   the same nine a tilt, a leaning shadow, a pointer sheen and a staggered
   arrival, through one `data-gz-tilt` attribute; that file only exists once
   JavaScript has run, and this page is meant to be finished without it, so the
   plain four-pixel lift lives here.

   BEHIND (hover: hover), WHICH IS A FIX AND NOT A FLOURISH. On a phone :hover
   latches on tap and does not let go, so a tapped card stayed lifted with a
   brightened edge until something else was touched. gz-depth.css has always
   suppressed its half of this under (hover: none) and this half never was.
   =============================================================== */

.gz-card,
.gz-price,
.gz-ladder li,
.gz-compliance,
.gz-table-card,
.gz-compare-item,
.gz-pair > figure,
.gz-tool-shot,
.gz-shot-phone {
  transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease;
}
.gz-compare-item img,
.gz-pair > figure img,
.gz-tool-shot img,
.gz-shot-phone img { transition: border-color .22s ease; }

@media (hover: hover) {
  /* THE LIFT SITS IN :where() SO THAT IT WEIGHS NOTHING. gz-depth.css raises
     the same surfaces by six pixels and tilts them through a rule of its own,
     and two files both declaring `transform` on one element is a specificity
     race — one this nearly lost, because `.gz-ladder li:hover` carries an
     element more than the attribute selector the depth layer uses, so a rung
     would have kept the flat 4px and quietly refused to tilt. :where()
     contributes zero, which states the relationship instead of patching it:
     this is the floor, and anything with more to say about a surface than "it
     is a card" outranks it by default. */
  :where(.gz-card,
         .gz-price,
         .gz-ladder li,
         .gz-compliance,
         .gz-table-card,
         .gz-compare-item,
         .gz-pair > figure,
         .gz-tool-shot,
         .gz-shot-phone):hover {
    transform: translateY(-4px);
  }

  /* The edge, at full weight, because it has to beat each component's own
     border declaration and there is nothing downstream that should beat it. */
  .gz-card:hover,
  .gz-price:not(.is-featured):hover,
  .gz-ladder li:hover,
  .gz-compliance:hover,
  .gz-table-card:hover {
    border-color: var(--gz-faint);
  }

  /* THE FEATURED PLAN IS THE ONE EXCLUSION AND IT IS NOT AN INCONSISTENCY.
     .gz-price.is-featured has no border colour to brighten: its edge is a
     gradient painted through a transparent border with border-box clipping, so
     giving it a solid colour on hover would paint over the slick and the
     highlighted plan would look like the three beside it at exactly the moment
     somebody is pointing at it. It lifts, tilts and catches the sheen with the
     rest of them. */

  /* The four framed pictures wear their border on the <img> rather than on the
     figure, so the edge has to be brightened where it actually is. */
  .gz-compare-item:hover img,
  .gz-pair > figure:hover img,
  .gz-tool-shot:hover img,
  .gz-shot-phone:hover img {
    border-color: var(--gz-faint);
  }
}

/* ══ LANDING MEDIA ══════════════════════════════════════════════════════════
   All of it is 9:16 — clips and panels alike — which is an awkward shape to
   lay out beside text and the reason every rule here exists.

   CAPPED BY HEIGHT, NOT WIDTH. A vertical asset constrained only by width
   becomes enormous: at 540x960, a column 500px wide renders it 890px tall and
   pushes everything after it off the first screen. Height caps keep it the
   same physical size at every breakpoint and let the row decide the rest. */

.gz-hero-clip,
.gz-panel {
  margin: 0;
  flex: 0 0 auto;
  line-height: 0;
}

.gz-hero-clip video,
.gz-panel img {
  display: block;
  /* Bigger than it was. These were rendering ~236px wide on a 1440 screen,
     which is too small to SEE a face swap — and a demo you cannot read is
     decoration. The clamp keeps it generous on desktop and still sane on a
     short laptop window. */
  height: clamp(380px, 56vh, 620px);
  width: auto;
  max-width: 100%;
  border-radius: 20px;
  box-shadow: 0 26px 70px rgba(0, 0, 0, .5);
}

/* A feature hero: copy on one side, the 9:16 asset on the other. */
.gz-media-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(28px, 5vw, 72px);
  flex-wrap: wrap;
}

.gz-media-row > .gz-media-copy {
  flex: 1 1 340px;
  min-width: 0;
}

/* ── THE SOURCE → RESULT STRIP ───────────────────────────────────────────
   Three 9:16 tiles side by side, so this one is capped by WIDTH and centred.
   It must never be the thing that scrolls the page sideways. */
.gz-strip {
  margin: 32px auto 0;
  max-width: 900px;
  line-height: 0;
  text-align: center;
}

.gz-strip img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 16px;
  margin-inline: auto;
}

.gz-strip figcaption {
  margin-top: 12px;
  font-size: .84rem;
  line-height: 1.55;
  opacity: .7;
  text-align: center;
}

/* ── NARROW ───────────────────────────────────────────────────────────────
   One column, and everything CENTRED. Left-aligned media under centred-ish
   copy is the thing that makes a phone layout look unfinished. */
@media (max-width: 900px) {
  .gz-media-row {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }

  .gz-media-row > .gz-media-copy {
    flex: 1 1 auto;
    width: 100%;
    text-align: center;
  }

  /* Anything the copy side lays out in a row — buttons, chips — re-centres
     with it rather than staying pinned left. */
  .gz-media-row > .gz-media-copy > * { margin-inline: auto; }

  .gz-hero-clip video,
  .gz-panel img {
    height: auto;
    width: min(100%, 340px);
    margin-inline: auto;
  }
}

@media (max-width: 640px) {
  .gz-strip { margin-top: 24px; }
}

/* Someone who has asked for less motion should not be handed four looping
   videos. Autoplay is an attribute we cannot conditionally drop server-side,
   so the clips are held on their poster frame instead. */
@media (prefers-reduced-motion: reduce) {
  .gz-hero-clip video { animation: none; }
}

/* ── A HERO THAT CONTAINS MEDIA NEEDS MORE ROOM THAN ONE THAT DOES NOT ──────
   .gz-hero-inner is capped at 760px, which is right for a column of text: it
   keeps the measure readable. Put a 9:16 clip inside that same 760px and the
   copy is left with 389px — and a 73px headline in 389px wraps to SIX lines,
   which is what "Face swap for video, and nothing else moves." was doing.

   Widened only when a media row is actually present, so the text-only heroes
   keep the measure they were designed with. Browsers without :has() simply
   keep today's behaviour, which is the current (bad but not broken) wrap —
   never a layout that falls apart. */
.gz-hero-inner:has(.gz-media-row) {
  max-width: 1120px;
}

.gz-hero-inner:has(.gz-media-row) .gz-media-copy {
  /* Enough basis that the headline gets the long side of the row rather than
     splitting it evenly with a narrow vertical clip. */
  flex: 1 1 480px;
}

/* The hero clip is the supporting act here, not the centrepiece — the rail
   further down the page is where the clips are the point. */
.gz-hero-inner .gz-hero-clip video,
.gz-hero-inner .gz-panel img {
  height: clamp(340px, 46vh, 500px);
}

/* ── RHYTHM ────────────────────────────────────────────────────────────────
   90px top AND bottom put 180px of nothing between two short sections, which
   on a page that was just cut to ~800 words reads as a series of empty rooms.
   Tightened about a quarter. Clamped rather than fixed so a phone does not
   inherit desktop spacing. */
.gz-section {
  padding-block: clamp(48px, 5.4vw, 72px);
}

/* The hero keeps more air above it — it sits under the fixed bar. */
.gz-hero {
  padding-block: clamp(64px, 7vw, 96px) clamp(48px, 5vw, 72px);
}

/* ── A BODY PANEL IS NOT A HERO PANEL ──────────────────────────────────────
   In the body sections the copy beside the media is short — a heading and a
   sentence, 145-277px tall — while a 9:16 asset at hero size is 532px. The row
   centres them, so the text ends up stranded in the middle of a tall column
   with ~130-190px of nothing above and below it. That is the "empty rooms"
   feeling on a page that was just cut down to 800 words.

   Heroes keep their size (the rule above is more specific); everything below
   the fold comes down to something the copy can stand beside. */
.gz-section:not(.gz-hero) .gz-panel img,
.gz-section:not(.gz-hero) .gz-hero-clip video {
  /* Tuned by looking, not by arithmetic: 34vh made the panel 182px wide,
     which is too narrow to read a screenshot of a conversation. This lands
     around 235px — big enough to see, small enough that the copy beside it
     does not float in a tall empty column. */
  height: clamp(340px, 44vh, 470px);
}

/* With a shorter panel the pair reads as a unit, so the copy can hold its own
   measure rather than being stretched to match a column of pixels. */
.gz-section:not(.gz-hero) .gz-media-row > .gz-media-copy {
  flex: 1 1 420px;
  max-width: 620px;
}

/* ── THE HERO CAROUSEL ─────────────────────────────────────────────────────
   Six demos in the place four used to sit side by side. Four at once meant
   four clips ~260px wide, which is not enough to see a face change; one at a
   time spends the same room on one asset.

   THE SPLIT. Media LEFT, copy RIGHT on a wide screen. The DOM order is
   media-then-copy, so the reading order and the visual order agree and no
   `order` juggling is needed on desktop — only on the phone, below. */
.gz-hero-split {
  display: grid;
  grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr);
  gap: clamp(28px, 4vw, 64px);
  align-items: center;
  max-width: 1180px;
}

/* The text-only heroes elsewhere keep their 760px measure; this one overrides
   .gz-hero-inner's cap only because it is a two-column layout now. */
.gz-hero-split .gz-hero-copy { min-width: 0; }

/* ── THE SPLIT HERO ACCEPTS A PLAIN 9:16 PANEL, NOT ONLY THE CAROUSEL ──────
   The grid was written for front-page.php, whose media column is .gz-car. Every
   other page with a hero clip has a <figure class="gz-panel"> instead, so none
   of them could use the house pattern and all of them fell back to stacking
   the clip under the copy — which is how /ai-chat/ ended up 2168px tall.
   The panel now sizes to its column the same way the carousel does. */
.gz-hero-split > .gz-panel {
  margin: 0;
  min-width: 0;
}

.gz-hero-split > .gz-panel video,
.gz-hero-split > .gz-panel img {
  display: block;
  width: 100%;
  height: auto;
  /* Capped by HEIGHT, not width: these are 9:16, and a portrait clip given a
     whole grid column takes 1.7 screens of vertical space if nothing stops it.
     The cap is what keeps the hero to about one viewport. */
  max-height: min(72vh, 620px);
  object-fit: contain;
  margin-inline: auto;
  border-radius: var(--gz-radius, 18px);
}

/* The heading is a size smaller here than on the text-only heroes. At the
   full size "AI companions and" does not fit the column beside the carousel,
   so the manual <br> lands on a line that has already wrapped and the first
   line reads "AI companions" with "and" alone under it. */
.gz-hero-split h1 {
  margin-top: 0;
  font-size: clamp(2.3rem, 4.1vw, 3.5rem);
}

.gz-car {
  min-width: 0;
  margin: 0;
}

/* THE STAGE IS A FIXED 9:16 BOX and the slides are absolutely positioned
   inside it. This is what stops the page jumping: two of the six slides are
   stills with a different natural ratio to the four clips, and letting each
   slide size the container would reflow everything below on every tick. */
.gz-car-stage {
  position: relative;
  aspect-ratio: 9 / 16;
  /* A 9:16 box left to fill a half-width column is enormous, so the HEIGHT is
     the master here and the width follows from the ratio. */
  height: clamp(360px, 56vh, 560px);
  margin-inline: auto;
  border-radius: 18px;
  overflow: hidden;
  background: #000;
  box-shadow: 0 22px 60px -28px rgba(0, 0, 0, .85);
}

.gz-car-slide {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  /* visibility, not display:none — a hidden video that was never laid out
     cannot be seeked or played, and the crossfade needs both frames present. */
  visibility: hidden;
  transition: opacity .5s ease;
}

.gz-car-slide.is-on {
  opacity: 1;
  visibility: visible;
}

/* ── THE CROSS-FADING STACK ───────────────────────────────────────────────
   Three panels in one slide, each visible for a third of the cycle with a
   fade between. Driven entirely by CSS: one keyframe track, and each layer
   offset by its own share of it via --k / --n.

   The 12% / 33% shape is what makes it a CROSS-fade rather than a blink —
   each layer is fully up while its neighbours are down, and the two edges
   overlap. A plain 0 -> 100 -> 0 would leave the stage momentarily empty. */
.gz-car-layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  animation: gz-stack calc(var(--n) * 2.2s) ease-in-out infinite;
  animation-delay: calc(var(--k) * 2.2s);
}

@keyframes gz-stack {
  0%   { opacity: 0; }
  4%   { opacity: 1; }
  30%  { opacity: 1; }
  36%  { opacity: 0; }
  100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  /* No fading at all: hold the first panel, which is a complete argument on
     its own — six pictures. */
  .gz-car-layer { animation: none; }
  .gz-car-layer:first-of-type { opacity: 1; }
}

.gz-car-slide video,
.gz-car-slide img {
  width: 100%;
  height: 100%;
  /* contain, NEVER cover: the BEFORE/AFTER pills and the "AI generated by
     Greazy AI" bar are baked into the top and bottom of these files, and cover
     is exactly what crops both off. */
  object-fit: contain;
  display: block;
}

.gz-car-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  font-size: 1.5rem;
  line-height: 1;
  color: #fff;
  background: rgba(0, 0, 0, .42);
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: 50%;
  cursor: pointer;
  opacity: 0;
  transition: opacity .2s ease, background .2s ease;
}

/* The arrows are a fallback for the dots, so they stay out of the picture
   until someone reaches for them. Focus counts as reaching: a keyboard user
   must be able to see the control they have landed on. */
.gz-car:hover .gz-car-arrow,
.gz-car-arrow:focus-visible { opacity: 1; }
.gz-car-arrow:hover { background: rgba(0, 0, 0, .68); }
.gz-car-prev { left: 10px; }
.gz-car-next { right: 10px; }

/* ONE caption, rewritten per slide. A fixed min-height keeps the dots from
   hopping when a two-line description follows a one-line one. */
.gz-car-cap {
  margin: 14px auto 0;
  max-width: 34ch;
  min-height: 3.6em;
  text-align: center;
}

.gz-car-cap b {
  display: block;
  font-size: .98rem;
  letter-spacing: .01em;
}

.gz-car-cap span {
  display: block;
  margin-top: 3px;
  font-size: .84rem;
  line-height: 1.5;
  opacity: .72;
}

.gz-car-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 12px;
}

.gz-car-dots button {
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .28);
  cursor: pointer;
  transition: background .2s ease, transform .2s ease;
}

.gz-car-dots button.on {
  /* The active dot is the only place the three-colour gradient appears in the
     carousel; everything else is neutral so the media is what has colour. */
  background: var(--gz-slick);
  transform: scale(1.5);
}

@media (max-width: 900px) {
  /* THE PHONE ORDER, which is the thing that was wrong before: heading, then
     the demo, then the button. Someone should read what this is, watch it
     work, and only then be asked to sign in — asking first is asking about
     something they have not seen. `display: contents` dissolves the copy box
     so its three children can be ordered against the carousel directly. */
  .gz-hero-split {
    grid-template-columns: minmax(0, 1fr);
    gap: 22px;
  }
  .gz-hero-split .gz-hero-copy { display: contents; }
  /* ⚠️ THIS ORDER IS THE MOBILE GUIDELINE, WRITTEN DOWN IN front-page.php:
     "someone on a phone should read what this is, see it, then be asked to
     sign in — not be asked before they have seen anything." Heading, media,
     lede, button. It lived only in these five lines, it only ever named
     `.gz-car`, and every hero using a `.gz-panel` therefore missed it: the
     copy stacked above the clip with the sign-in button in the middle, which
     is the opposite of the rule. `.gz-panel` now takes the same slot. */
  .gz-hero-split h1            { order: 1; text-align: center; }
  .gz-car,
  .gz-hero-split > .gz-panel   { order: 2; }
  .gz-hero-split .gz-eyebrow   { order: 0; margin-inline: auto; }
  .gz-hero-split .gz-lede      { order: 3; text-align: center; margin-inline: auto; }
  .gz-hero-split .gz-cta-row   { order: 4; justify-content: center; }

  .gz-hero-split > .gz-panel video,
  .gz-hero-split > .gz-panel img { max-height: min(58vh, 460px); }

  .gz-car-stage { height: min(64vh, 460px); }

  /* THE ARROWS STAY, AND THEY STAY VISIBLE.
     They were hidden here on the reasoning that a thumb has the dots — but
     the dots are 7px targets, and hiding the arrows is done by revealing them
     on HOVER, which a touch screen does not have. So on touch the slide could
     only be changed by hitting a 7px dot, or not at all. */
  .gz-car-arrow {
    opacity: 1;
    /* Bigger than the desktop pair: 44px is the smallest target a thumb hits
       reliably, and these sit over a picture rather than beside it. */
    width: 44px;
    height: 44px;
    background: rgba(0, 0, 0, .55);
  }
  .gz-car-prev { left: 8px; }
  .gz-car-next { right: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  /* The script already stops advancing; this removes the crossfade for the
     manual moves that are still allowed. */
  .gz-car-slide { transition: none; }
}


/* ── THE FEATURE ROWS ──────────────────────────────────────────────────────
   One row per tool: copy on one side, its clip on the other, alternating.

   THE DOM IS ALWAYS COPY-THEN-MEDIA and the flip is done by moving the media
   with `order` on wide screens only. That way the phone — where every row must
   read title, clip, then text — needs no per-row exceptions, and a screen
   reader gets the same sequence on both.

   WHAT THIS REPLACED, AND THE BUG IT CARRIED. Two .gz-media-row blocks whose
   panel height was clamped by a rule declared AFTER the narrow-screen
   override, so on a phone the picture kept a 44vh height it could not fill and
   left a screen of empty space between the copy and the media. Heights here
   are set once, inside the query that owns them. */
.gz-feats .gz-feat {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(28px, 4.5vw, 68px);
  align-items: center;
  margin-top: clamp(44px, 5vw, 78px);
}

/* Even rows put the media on the left. */
.gz-feats .gz-feat.is-flipped .gz-feat-media { order: -1; }

.gz-feat-copy { min-width: 0; }

.gz-feat-copy h2 {
  margin: 0 0 12px;
  font-size: clamp(1.7rem, 2.6vw, 2.3rem);
}

.gz-feat-lede {
  margin: 0 0 18px;
  font-size: 1.06rem;
  color: var(--gz-muted);
  max-width: 46ch;
}

.gz-feat-link {
  display: inline-block;
  margin-top: 18px;
  font-weight: 600;
}
.gz-feat-link span { transition: transform .18s ease; display: inline-block; }
.gz-feat-link:hover span { transform: translateX(3px); }

/* THE MEDIA BOX IS A FIXED 9:16 FRAME, height-driven.
   Height first, ratio second: a 9:16 box told to fill half a 1200px row would
   be 1000px tall. The clips are letterboxed inside it, never cropped — the
   BEFORE/AFTER pills and the "AI generated by Greazy AI" bar are baked into
   the top and bottom of those files. */
.gz-feat-media {
  position: relative;
  margin: 0;
  justify-self: center;
  aspect-ratio: 9 / 16;
  /* Taller than the carousel's frame on purpose. This is the pass where
     someone stops on ONE tool, and at the carousel's height the clip came out
     263px wide — too small to read the text inside the chat recording, which
     is the whole content of that row. */
  height: clamp(420px, 64vh, 620px);
  border-radius: 18px;
  overflow: hidden;
  background: #000;
  box-shadow: 0 22px 60px -30px rgba(0, 0, 0, .85);
}

.gz-feat-media video,
.gz-feat-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* The stack's layers are absolutely positioned; they share the carousel's
   cross-fade animation, declared with .gz-car-layer above. */
.gz-feat-media .gz-car-layer { position: absolute; inset: 0; }

@media (max-width: 900px) {
  /* ONE COLUMN, AND THE ORDER IS TITLE -> CLIP -> TEXT.
     `display: contents` dissolves the copy box so the heading can sit above
     the clip while the rest of its copy sits below, without splitting the
     copy into two elements in the markup. */
  .gz-feats .gz-feat {
    grid-template-columns: minmax(0, 1fr);
    gap: 18px;
    margin-top: clamp(52px, 12vw, 72px);
    text-align: center;
  }

  .gz-feats .gz-feat-copy { display: contents; }
  .gz-feats .gz-feat-copy h2 { order: 1; margin-bottom: 0; }
  .gz-feats .gz-feat-media,
  .gz-feats .gz-feat.is-flipped .gz-feat-media { order: 2; }
  .gz-feats .gz-feat-lede { order: 3; margin: 0 auto; }
  .gz-feats .gz-feat-copy .gz-checks { order: 4; margin-inline: auto; }
  .gz-feats .gz-feat-copy .gz-feature-note { order: 5; margin-inline: auto; }
  .gz-feats .gz-feat-link { order: 6; margin: 0 auto; }

  .gz-feat-media {
    /* Width-driven on a phone: the column is narrow, so the width is the
       binding constraint and a height clamp would only crop or float it. */
    height: auto;
    width: min(100%, 300px);
  }
}


/* The mood badge that sits beside the AI-friend row's mood line.
   It is a lifted screenshot with a transparent ground, so it needs no panel
   around it — a card would make the product's own UI look like a stock image
   dropped into the page. */
.gz-feat-aside {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 18px 0 0;
}

.gz-feat-aside img {
  width: 132px;
  height: auto;
  flex: none;
}

.gz-feat-aside figcaption {
  font-size: .84rem;
  color: var(--gz-muted);
}

@media (max-width: 900px) {
  .gz-feats .gz-feat-aside {
    /* The copy box is `display: contents` on a phone, so this needs its own
       slot in the row's order — between the checklist and the note. */
    order: 5;
    justify-content: center;
    margin-inline: auto;
  }
  .gz-feats .gz-feat-copy .gz-feature-note { order: 6; }
  .gz-feats .gz-feat-link { order: 7; }
}


/* ── MONTHLY / YEARLY ──────────────────────────────────────────────────────
   Two options in one pill, the active one lifted out of the track. It is a
   segmented control rather than a switch because a switch cannot say what it
   is switching to, and "save up to 33%" is the reason anybody presses it. */
.gz-billing {
  display: flex;
  justify-content: center;
  margin: 0 0 clamp(28px, 3.4vw, 44px);
  grid-column: 1 / -1;   /* it sits inside the card grid, so it spans it */
}

.gz-billing-switch {
  display: inline-flex;
  align-items: stretch;
  gap: 4px;
  padding: 5px;
  border: 1px solid var(--gz-line);
  border-radius: 999px;
  background: var(--gz-surface);
}

.gz-billing-opt {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 10px 20px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--gz-muted);
  font-size: .95rem;
  font-weight: 620;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: color .18s ease, background .18s ease;
}

.gz-billing-opt:hover { color: var(--gz-text); }

.gz-billing-opt.is-on {
  /* The active option is the only near-white surface in the control, so it
     reads as raised without a shadow that would fight the cards below. */
  background: var(--gz-text);
  color: #0B0B0F;
}

/* The saving, riding along inside the yearly option. It keeps its colour when
   the option is active — on the light pill the gradient would be unreadable,
   so it becomes a solid dark chip instead. */
.gz-billing-save {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .01em;
  background: var(--gz-slick);
  color: #0B0B0F;
  white-space: nowrap;
}

.gz-billing-opt.is-on .gz-billing-save {
  background: rgba(11, 11, 15, .12);
  color: #0B0B0F;
}

/* THE YEARLY FIGURE AND ITS UNIT ARE ONE LINE. The amount is set at a display
   size, so "$66.67" plus "/mo" overflows the card's measure and the unit wraps
   underneath — which reads as a price of "$66.67" and a separate word, not as
   a rate. A baseline-aligned flex row keeps them together and keeps the unit
   sitting on the number's baseline rather than its box. */
/* ⚠️ `[hidden]` FIRST, AND IT IS NOT A NICETY. The rule below sets
   `display: flex` on this element, and a `display` declaration BEATS the
   `hidden` attribute — the attribute is only honoured through the browser's own
   `[hidden] { display: none }`, which any later `display` overrides. So every
   plan card on this site rendered BOTH prices at once until somebody pressed
   the toggle: "$20" with "$16.67 /mo" stacked under it, on the front page, on
   the tool pages and on /pricing/. Two prices on one card is the single worst
   thing a pricing page can say, and it shipped because the attribute and the
   stylesheet were written months apart.

   Scoped to the price cards rather than global, because a global
   `[hidden] { display: none !important }` would also override the theme's
   deliberate uses of display on hidden elements elsewhere. */
.gz-price [data-period][hidden] {
  display: none;
}

.gz-amount[data-period="yearly"] {
  display: flex;
  align-items: baseline;
  gap: 3px;
  flex-wrap: nowrap;
}

/* The /mo after a yearly figure: present, but not competing with the number. */
.gz-amount-unit {
  font-size: .42em;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--gz-muted);
  margin-left: 2px;
}

/* THE SAVING LINE, under the yearly price. Gradient text rather than a chip:
   a second filled pill inside a card that already has a featured state was
   two loud things arguing, and the figure is the thing worth reading. */
.gz-save {
  margin-top: 8px;
  font-size: .88rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  background: var(--gz-slick);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

@media (max-width: 560px) {
  .gz-billing-switch { width: 100%; }
  .gz-billing-opt {
    flex: 1 1 0;
    justify-content: center;
    padding: 11px 12px;
    font-size: .9rem;
  }
  /* The chip wraps under the word on a narrow phone rather than squeezing it. */
  .gz-billing-opt { flex-wrap: wrap; gap: 5px; }
  .gz-billing-save { font-size: .72rem; }
}

/* ---------------------------------------------------------------
   LEGAL PAGES — /tos/, /privacy/, and the three still owed.
   One block, because a legal page laid out differently from the one
   beside it reads as having come from somewhere else.
   --------------------------------------------------------------- */

/* A READING MEASURE, AND THAT IS THE WHOLE DESIGN. These are the longest
   documents on the site and the only ones somebody reads to the end under
   duress. 68ch is the measure; the site's 1180px wrap would set section 5 at
   forty words a line and nobody would finish it. */
.gz-legal { max-width: 68ch; }

.gz-hero--legal { padding-bottom: 8px; }

.gz-legal-updated {
  color: var(--gz-faint);
  font-size: .88rem;
  margin: 18px 0 0;
}

/* Each clause is its own block with a hairline above it, so a reader scanning
   for one thing can find where it starts. The id on the block means every
   clause is linkable — which is what support and a reviewer both do: send
   somebody to the paragraph, not to the page. */
.gz-legal-sec {
  padding-top: 34px;
  margin-top: 34px;
  border-top: 1px solid var(--gz-line);
  /* An anchored clause lands under the sticky header rather than behind it. */
  scroll-margin-top: 96px;
}

.gz-legal-sec:first-child {
  padding-top: 0;
  margin-top: 0;
  border-top: 0;
}

/* SIZED DOWN FROM THE THEME'S HEADING RAMP ON PURPOSE. The site's h2 is a
   marketing heading — 44px, display weight, built to stop a scroller — and
   twelve of them down a Terms page turns a document into a series of
   billboards with paragraphs between them. A clause heading has one job: let
   somebody scanning find where clause 5 starts. */
.gz-legal-sec h2 {
  margin: 0 0 14px;
  font-size: clamp(1.15rem, 1rem + .55vw, 1.42rem);
  line-height: 1.3;
  letter-spacing: -.01em;
}

.gz-legal-sec p {
  color: var(--gz-muted);
  margin: 0 0 14px;
  line-height: 1.7;
}

.gz-legal-sec p:last-child { margin-bottom: 0; }

.gz-legal-list {
  margin: 0 0 14px;
  padding-left: 22px;
  color: var(--gz-muted);
  line-height: 1.7;
}

.gz-legal-list li { margin-bottom: 10px; }

/* The "what we collect" table. A definition list and not a real table: it is
   two columns on a desk and one on a phone, and a <table> would need a
   horizontal scroller to say the same thing. */
.gz-legal-dl { margin: 0; }

.gz-legal-dl dt {
  color: var(--gz-text);
  font-weight: 600;
  margin-top: 18px;
}

.gz-legal-dl dt:first-child { margin-top: 0; }

.gz-legal-dl dd {
  margin: 6px 0 0;
  color: var(--gz-muted);
  line-height: 1.7;
}
