/* Home page (/). Sections: sticky header, sticky sidebar,
   main column, full-width footer.

   One source now: both the shape and the colour are the reference design's. The
   page used to split the two — its layout, but the chat page's warm maroon
   canvas and shared --accent over it — and the palette below ended that: the
   canvas, the chrome, the divider and the pink are the reference's own values,
   read off its rendered pixels rather than guessed from screenshots.

   That makes this page the one place on the site that does NOT follow the
   injected --accent (see styles.css and gallery.go). It is deliberate and it is
   why --h3-primary is a literal below: a page painted in someone else's
   neutrals cannot borrow the accent tuned for ours.

   Breakpoints: 560 / 640 / 900 / 1280. The sidebar leaves at 1280 (that is where
   280px of it stops being affordable next to the grid), the bottom tab bar
   arrives at 900, and between the two the burger in the header is the way into
   the menu. */
:root {
  /* Surfaces: a neutral near-black canvas with the chrome one step above it, and
     panels as translucent white so a card sits on the same tint wherever it
     lands. The divider is the one surface that is not neutral — it carries a
     violet undertone, which is what keeps a flat black page from reading as a
     wireframe. It is opaque rather than a white alpha because it is the value
     the reference uses, and on this canvas the two are indistinguishable. */
  --h3-bg: #070708;
  --h3-bg-header: #0e0e10;
  --h3-panel: rgba(255, 255, 255, .04);
  --h3-panel-2: rgba(255, 255, 255, .08);
  --h3-line: #26202e;
  /* A brighter hairline for controls that sit on top of a photograph, where
     --h3-line disappears: the hero arrows, the icon buttons, the drawer's close.
     Unlabelled controls have nothing but their outline, so it has to clear 3:1
     against artwork we do not control. */
  --h3-line-media: rgba(255, 255, 255, .24);
  /* The canvas as an rgb triple, for the scrims and pills that need it at a
     partial alpha. Keep it in step with --h3-bg. */
  --h3-dark-rgb: 7, 7, 8;

  /* Two pinks, and the split is a contrast one rather than a taste one. On this
     canvas --h3-primary reads 4.3:1 — enough for a border, a focus ring and a
     heading (3:1), not enough for accent-coloured body text (4.5:1). The lighter
     step reaches 5.8:1 and carries every text role. The reference splits them
     the same way: its pink headings are visibly lighter than its pink buttons.
     Tints are mixed from the deep step rather than written out. */
  --h3-primary: #de0d79;
  --h3-primary-text: #f43e9c;
  --h3-primary-deep: #910850;
  --h3-primary-soft: color-mix(in srgb, var(--h3-primary-deep) 20%, transparent);
  --h3-primary-weak: color-mix(in srgb, var(--h3-primary-deep) 10%, transparent);
  /* The accent as a moving surface, the reference's own three stops. The blue in
     the middle is the point — blurred to 40px behind a card it is what stops the
     glow reading as a flat pink smudge, and it is the same blue the Premium
     border ends on. Two consumers, and they use it at opposite scales: the grid
     card blurs it into an aura outside its box, the rail card masks it down to a
     2px ring on the box itself. Anything else that wants the accent in motion
     takes it from here rather than writing the three stops out again — that is
     exactly how the old palette ended up with a copy that stayed pink. */
  --h3-grad-accent: linear-gradient(45deg, #f21589, #0f0cff, #ff7ebc);
  /* The section strip's wash: the accent at one end, the reference's electric
     blue at the other, both at a fifth of their strength over the chrome. This
     is the only place the blue appears outside the Premium glow. */
  --h3-grad-strip: linear-gradient(90deg, rgba(145, 8, 79, .2), rgba(16, 19, 159, .2));
  /* The primary button's light, the reference's own: two insets that bank the
     accent up against the button's edge, and a halo that only appears on hover.
     Both insets, not one — the tight 4px pass is what gives the rim its hard
     line, and without it the wide pass alone just muddies the fill. */
  --h3-glow-accent: inset 0 0 16px 1px rgba(222, 13, 121, .5), inset 0 0 4px rgba(222, 13, 121, .65);
  --h3-glow-accent-hover:
    inset 0 0 16px 1px rgba(222, 13, 121, .5), inset 0 0 4px rgba(222, 13, 121, .65),
    0 0 16px 1px rgba(222, 13, 121, .55);
  /* White measures 4.7:1 on this accent, so the label is white and matches the
     reference. The near-black that used to sit here existed because the old,
     lighter pink gave white only 2.9:1. */
  --h3-on-accent: #fff;

  /* The Premium treatment, kept whole because it is a recognisable object rather
     than a colour: a dark two-stop fill under a six-stop gradient hairline. It
     is declared once here because two components wear it — the header button and
     the bottom bar's raised icon — and the copies had already been written out
     twice, character for character. */
  --h3-glow-fill: linear-gradient(90deg, #172841, #41122c);
  --h3-glow-border: linear-gradient(195deg,
    rgba(255, 44, 247, 1) -10%,
    rgba(251, 4, 123, .8) 12%,
    rgba(255, 126, 188, .81) 31%,
    rgba(255, 255, 255, .15) 54%,
    rgba(73, 255, 233, .65) 70%,
    rgba(19, 14, 255, 1) 100%);
  --h3-glow-shadow: -28px 12px 50px rgba(0, 56, 255, .15), 10px -6px 30px rgba(248, 0, 59, .2);
  --h3-glow-shadow-hover: -28px 12px 50px rgba(0, 56, 255, .25), 10px -6px 30px rgba(248, 0, 59, .3);

  /* Neutral white now that the canvas is neutral: the warm off-white this page
     used to carry was tuned for a maroon background and reads dirty on black. */
  --h3-text: #fff;
  --h3-text-dim: rgba(255, 255, 255, .72);
  --h3-text-dim2: rgba(255, 255, 255, .62);

  --h3-font: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --h3-r-card: 24px;
  --h3-r-pill: 999px;
  /* 16px wider than the design it follows: our menu carries a "soon" badge on
     five of its rows, and at 264px "Generate Image" and "Roleplay Games"
     truncated to make room for it. */
  --h3-side-w: 280px;
  --h3-gap: 24px;
  /* The header row's own gap. It is a token rather than a literal because the
     brand's width is derived from it: brand + this gap + the page padding is
     what puts the section tab on the sidebar's edge (see .h3-brand). */
  --h3-header-gap: 12px;
  /* The content column stops growing here and centres in whatever is left, so a
     wide monitor gets margins instead of a line of text running its full width.
     Both numbers are measured off the design this page follows: its content
     wrapper is max-w-screen-xl with 24px of padding, and its sections sit 48px
     apart from the tablet breakpoint up. */
  --h3-main-max: 1280px;
  --h3-section-gap: 48px;

  /* Header row plus the section strip beneath it. The strip only exists below
     1280px, so above it the header is the row alone — everything that sticks
     under the header measures from this one variable rather than from a pixel
     value repeated in five places. */
  --h3-row-h: 64px;
  --h3-strip-h: 45px;
  --h3-header-h: calc(var(--h3-row-h) + var(--h3-strip-h));
}
@media (min-width: 1280px) {
  :root { --h3-header-h: var(--h3-row-h); }
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; padding: 0; }
/* The canvas lives on <html> alone. <body> stays transparent so the grain layer
   below, which sits at z-index -1, has something to show through.

   The wash gives the page a top. It is lifted and faintly violet at the document
   origin and has settled into the flat canvas by 1100px, so scrolling down is a
   descent into black rather than a slide across an unchanging field. Anchored to
   the document, not the viewport: a fixed one would follow the reader and stop
   being a place.

   The explicit background-size is what makes it safe. Left to itself the
   gradient box is <html>'s own height, which is the viewport — the 1100px falloff
   would then be clipped mid-ramp and meet the flat colour below in a visible
   seam. Pinned to 1100px the last stop is exactly --h3-bg and the join is
   invisible. Keep the two in step. */
html {
  background:
    linear-gradient(180deg, #130b18 0%, #0b0810 38%, var(--h3-bg) 100%)
      top center / 100% 1100px no-repeat,
    var(--h3-bg);
}
body.h3 {
  font-family: var(--h3-font);
  color: var(--h3-text);
  min-height: 100vh;
  /* Everything on the page inherits this. Without it body copy runs at the UA's
     `normal` (~1.2), which is too tight for anything that wraps — the slide
     paragraphs, card meta and footer notes all do. Headings set their own. */
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
a { color: inherit; text-decoration: none; }
button { font-family: inherit; }
img, video { max-width: 100%; display: block; }

/* One focus ring for the whole page. Keyed to :focus-visible so a mouse press
   stays quiet, and declared once rather than per component so no future control
   can ship without one. --h3-primary reads 4.3:1 against the page background,
   clear of the 3:1 a non-text control needs. */
body.h3 :where(a, button, input, summary):focus-visible {
  outline: 2px solid var(--h3-primary);
  outline-offset: 2px;
}
/* The card's hit area and the rail's thumbnail clip their overflow, so an outset
   ring would be cut off. */
.h3-card-hit:focus-visible, .h3-rail-media:focus-visible { outline-offset: -3px; }
.h3-rail-card:focus-visible { outline-offset: 3px; border-radius: var(--h3-r-card); }

/* ---------- Grain ---------- */

/* A single layer of film grain over the wash on <html>. It replaced four blurred
   colour blobs, and the reason is worth keeping: almost none of this page's
   background is ever visible. The header, sidebar, footer, tab bar and the cards
   themselves are opaque, so what is left of the backdrop is a set of narrow
   channels — the strip above the hero, the gutters, the gaps in the grid. A soft
   shape wide enough to read as a shape only ever appears in those channels as an
   arbitrary slice of itself, which is why the blobs read as smudges rather than
   as light. Grain has no shape to slice: it looks the same in a 24px gap as it
   does across the page.

   Fixed rather than scrolling. A texture that travels with the content swims
   against it, and fixed also keeps the layer off the scrolling raster — it is
   rasterised once and composited after that.

   It sits behind everything at z-index -1 and only shows where the page is
   transparent: nothing scrolls under translucent chrome, by design.

   The noise is an inline SVG rather than an asset because it has to survive
   //go:embed and cache alike, and feTurbulence gives a stable pattern at any
   viewport without a file to ship. It is drawn once into a 180px tile and
   repeats; stitchTiles keeps the seams from showing.

   Opacity is the whole contrast story of this layer, and it is measured rather
   than chosen. At .055 the brightest composited pixel anywhere on the page is
   rgb(27, 21, 32) at 1440 wide and rgb(28, 21, 33) at 390 — both at the very
   top, where the wash is lightest and a white grain speck lands on it. Against
   the worse of the two, the page's own text reads:

     body           #fff      17.8:1
     --h3-text-dim  (.72)      9.6:1
     --h3-text-dim2 (.62)      7.4:1
     accent word in a heading  5.1:1   <- the binding one

   The accent word is pink on a violet-lifted field, so it runs out of contrast
   first and sets the ceiling for this layer. It sits in headings only, where WCAG
   asks 3:1 rather than 4.5:1, but the field is tuned to clear the stricter bar
   anyway. --h3-primary itself, which borders and the focus ring are drawn in,
   holds 3.8:1 at that same lightest point — below the 4.3:1 it gets on the flat
   canvas, still clear of the 3:1 a non-text control needs.

   Raising this opacity, or lifting the top of the wash above, is a contrast
   change rather than a taste one: re-measure by sampling the composited pixels
   before shipping it. Grain is peaks, not averages — the arithmetic on the mean
   says one thing and the brightest speck says another. */
.h3-grain {
  position: fixed; inset: 0; z-index: -1;
  pointer-events: none;
  opacity: .055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.h3-skip {
  position: fixed; top: 8px; left: 8px; z-index: 100;
  padding: 10px 16px; border-radius: var(--h3-r-pill);
  /* Opaque, unlike the other panels: this one is painted over page content. */
  background: var(--h3-bg-header); color: var(--h3-text);
  border: 1px solid var(--h3-line); font-weight: 600;
  transform: translateY(-200%);
}
.h3-skip:focus { transform: none; }
#h3Main:focus { outline: none; }

/* ---------- Header ---------- */

.h3-header {
  position: sticky; top: 0; z-index: 40;
  height: var(--h3-row-h);
  display: flex; align-items: center; gap: var(--h3-header-gap);
  padding: 0 var(--h3-gap);
  background: var(--h3-bg-header);
  border-bottom: 1px solid var(--h3-line);
}

.h3-burger {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 50%; cursor: pointer;
  border: 0; background: none; color: var(--h3-text);
}
.h3-burger:hover { background: var(--h3-panel); }
@media (min-width: 1280px) { .h3-burger { display: none; } }

/* The brand must not absorb the header's overflow. It is the only flex item that
   can shrink, so without flex:0 0 auto it collapses to 0px before anything else
   gives. The compact mark takes over where the wordmark cannot fit. */
.h3-brand { flex: 0 0 auto; display: flex; align-items: center; }
/* The size for every width without a sidebar. Above 1280px the wordmark is
   widened to the sidebar's menu column instead — see the 1280px block below. */
.h3-brand img { height: 26px; width: auto; }
.h3-brand-mark { display: none; }

/* ---------- Catalog sections ---------- */

.h3-sections { display: flex; align-items: stretch; height: 100%; }
.h3-section {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 0 20px; border-bottom: 3px solid transparent;
  /* The border sits at the bottom, so the label is nudged down by the same
     amount to stay optically centred in the row. */
  padding-top: 3px;
  font-size: 1rem; font-weight: 500; color: var(--h3-text-dim);
  white-space: nowrap;
}
.h3-section:hover { color: var(--h3-primary-text); }
.h3-section.is-on { color: var(--h3-primary-text); border-bottom-color: var(--h3-primary); }

.h3-soon {
  font-style: normal; font-size: .55rem; letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 5px; border-radius: var(--h3-r-pill);
  border: 1px solid var(--h3-line); color: var(--h3-text-dim2);
  flex: 0 0 auto;
}

/* One of the two copies is always display:none, so the duplicated markup never
   produces two visible navigations. Above 1280 the sections sit in the header
   row; below it they take a strip of their own, sticky right under the header. */
.h3-header .h3-sections { display: none; }
.h3-sections-strip {
  position: sticky; top: var(--h3-row-h); z-index: 39;
  height: var(--h3-strip-h);
  background: var(--h3-grad-strip), var(--h3-bg-header);
  border-bottom: 1px solid var(--h3-line);
}
.h3-sections-strip .h3-sections {
  height: 100%;
  overflow-x: auto; scrollbar-width: none;
}
.h3-sections-strip .h3-sections::-webkit-scrollbar { display: none; }
.h3-sections-strip .h3-section { flex: 1 0 auto; justify-content: center; }
@media (min-width: 1280px) {
  .h3-header .h3-sections { display: flex; }
  .h3-sections-strip { display: none; }
  /* Where the sidebar exists, the tab heads the catalog column rather than
     floating over the menu: the brand is padded out to a column of its own so
     that page padding + brand + header gap lands the tab's left edge — and the
     accent underline with it — exactly on the sidebar's right border. The brand
     itself stays left-aligned inside it; the wordmark below fills all but the
     last 12px of it, which are the header gap. */
  .h3-brand { flex: 0 0 calc(var(--h3-side-w) - var(--h3-gap) - var(--h3-header-gap)); }
  /* The wordmark heads the menu instead of floating over it: its width is the
     sidebar's content column — the sidebar minus its two paddings and its right
     border, which box-sizing pulls inside the 280px — and the header carries the
     same left padding, so the logo's right edge lands on the right edge of the
     menu rows. Drop the 1px and the logo overhangs them by exactly that. The
     height follows from the 594:120 file (231px → ~47px inside the 64px row).

     max-height is the guard, not the size: .h3-header has a fixed height rather
     than a min-height, so a grown --h3-side-w would push the logo out of the row
     instead of opening it. It never binds at the current 280px. The aspect ratio
     survives it — a max-height violation on a replaced element recomputes the
     width (CSS2.1 §10.4), it does not squash the image.

     The selector needs both classes: .h3-brand img above is (0,1,1) and would
     otherwise keep its 26px height whatever the source order. */
  .h3-brand .h3-brand-word {
    width: calc(var(--h3-side-w) - 2 * var(--h3-gap) - 1px);
    height: auto;
    max-height: 48px;
  }
}

/* ---------- Header actions ---------- */

.h3-header-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }

/* A greeting, not a control: dimmer than the buttons it sits beside, and it must
   never win the fight for the row. A display name may be 64 characters long, so
   it is capped and truncated rather than allowed to push the actions off the
   edge; the buttons keep their width because this is the only shrinkable item. */
.h3-welcome {
  min-width: 0; max-width: 22ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: .92rem; color: var(--h3-text-dim);
}

.h3-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 40px; padding: 0 18px; border-radius: var(--h3-r-pill);
  font-size: .92rem; font-weight: 600; white-space: nowrap;
  border: 1px solid transparent; cursor: pointer;
}
/* The reference's CTA: one flat magenta lit from inside rather than a gradient
   sitting under a drop shadow. Flat is what makes it read as a lamp — a gradient
   fill and an inset glow fight each other, and the button ends up looking like
   neither. The label is --h3-on-accent (white, 4.7:1 on this fill). */
.h3-btn-primary {
  background: var(--h3-primary); color: var(--h3-on-accent);
  box-shadow: var(--h3-glow-accent);
}
.h3-btn-primary:hover { box-shadow: var(--h3-glow-accent-hover); }
.h3-btn-ghost { border-color: var(--h3-line); color: var(--h3-text); background: none; }
.h3-btn-ghost:hover { background: var(--h3-panel); }

/* The reference design's "glow" treatment: a dark two-stop fill with a
   multi-hue gradient hairline. A gradient cannot be a border-color, so it is
   painted as two backgrounds — the fill clipped to the padding box, the gradient
   to the border box, with the border itself transparent. Keep the fill opaque:
   a translucent one would let the gradient show through the middle. */
.h3-btn-glow, .h3-premium.is-offer {
  color: #fff; border-color: transparent;
  background: var(--h3-glow-fill) padding-box, var(--h3-glow-border) border-box;
  box-shadow: var(--h3-glow-shadow);
}
.h3-btn-glow:hover, .h3-premium.is-offer:hover { box-shadow: var(--h3-glow-shadow-hover); }

.h3-icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 50%;
  /* Brighter than --h3-line: this control has no label, so its outline is the
     only thing that identifies it and has to clear 3:1 on its own. */
  border: 1px solid var(--h3-line-media);
  color: var(--h3-text-dim);
}
.h3-icon-btn:hover { color: var(--h3-text); background: var(--h3-panel); }

/* 560px is where the wordmark stops fitting beside the actions. Below it the
   square mark carries the brand. */
@media (max-width: 559px) {
  .h3-brand-word { display: none; }
  .h3-brand-mark { display: block; }
}
@media (max-width: 899px) {
  /* Premium has its own slot in the bottom tab bar from here down, and the
     header has no room for three buttons on a phone. */
  .h3-header-actions .h3-btn-glow { display: none; }
}
@media (max-width: 639px) {
  .h3-btn { height: 36px; padding: 0 12px; font-size: .85rem; }
  .h3-header-actions .h3-btn-ghost { display: none; }
  .h3-section { padding: 0 14px; font-size: .9rem; }
  /* The greeting is the first thing to go on a phone: it is pleasantry, while
     everything left in the row is a way out of it. */
  .h3-welcome { display: none; }
}

/* ---------- Shell, sidebar, footer ---------- */

.h3-shell { display: flex; align-items: flex-start; }

.h3-side {
  position: sticky; top: var(--h3-header-h);
  flex: 0 0 var(--h3-side-w); width: var(--h3-side-w);
  height: calc(100vh - var(--h3-header-h));
  overflow-y: auto;
  padding: var(--h3-gap);
  background: var(--h3-bg-header);
  border-right: 1px solid var(--h3-line);
  display: flex; flex-direction: column; gap: 14px;
}

/* max-width plus auto inline margins: as a flex item the column is capped at
   --h3-main-max and the leftover row space goes to the margins, which centres it
   beside the sidebar. Below that width nothing changes — the cap simply never
   binds. The sections are spaced by the container's gap rather than by margins
   on each one, so the rhythm holds however many of them render (the personal
   rail only exists for a signed-in visitor). */
.h3-main {
  flex: 1 1 auto; min-width: 0;
  max-width: var(--h3-main-max);
  margin-inline: auto;
  padding: var(--h3-gap);
  display: flex; flex-direction: column; gap: 24px;
}
@media (min-width: 768px) { .h3-main { gap: var(--h3-section-gap); } }

.h3-footer {
  background: var(--h3-bg-header);
  border-top: 1px solid var(--h3-line);
  padding: 28px var(--h3-gap);
  color: var(--h3-text-dim2);
}

@media (max-width: 1279px) {
  .h3-side { display: none; }
}
@media (min-width: 1280px) {
  .h3-drawer-wrap { display: none; }
}

/* ---------- Navigation ---------- */

.h3-nav { display: flex; flex-direction: column; gap: 4px; }
.h3-nav-item {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-radius: var(--h3-r-pill);
  border: 1px solid transparent; letter-spacing: .025em;
  font-size: 15px; color: var(--h3-text-dim);
  transition: background-color .15s ease, color .15s ease;
}
.h3-nav-item:hover { background: var(--h3-primary-weak); color: var(--h3-text); }
/* The current page has to be obvious at a glance, not merely different: the
   accent fill and hairline, a bloom under the pill, a full-strength label and
   the icon in the accent itself. Four signals, none of them colour alone — the
   template also marks it aria-current. Measured on this fill: label 18:1,
   icon 5.2:1. */
.h3-nav-item.is-on {
  border-color: var(--h3-primary);
  background: var(--h3-primary-soft);
  color: #fff; font-weight: 600;
  box-shadow: 0 6px 20px -8px var(--h3-primary);
}
.h3-nav-item.is-on .h3-nav-ico { color: var(--h3-primary-text); }
/* Not a link and not focusable: the label plus the badge say why, and no hover
   promises an action that is not there. */
.h3-nav-item.is-soon { cursor: default; opacity: .55; }
.h3-nav-item.is-soon:hover { background: none; color: var(--h3-text-dim); }
.h3-nav-ico { flex: 0 0 auto; width: 22px; height: 22px; }
/* The label is the only part that may grow, so a long translation truncates
   instead of pushing the "soon" badge out of the row. */
.h3-nav-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.h3-premium-box { margin-top: 4px; }
.h3-premium {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 16px 8px 8px; border-radius: 16px;
  border: 1.5px solid var(--h3-line); background: var(--h3-panel);
  color: var(--h3-text);
}
.h3-premium.is-offer { border-radius: var(--h3-r-pill); }
.h3-premium-icon {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1.5px solid rgba(255, 255, 255, .45); color: #fff;
}
.h3-premium-label { font-weight: 700; font-size: .98rem; line-height: 1.25; min-width: 0; }
.h3-premium-sub { display: block; margin: 8px 2px 0; font-size: .8rem; color: var(--h3-text-dim2); }

.h3-side-bottom { margin-top: auto; display: flex; flex-direction: column; gap: 4px; }
/* Metrics deliberately match .h3-nav-item so the row sits in the same rhythm as
   the links around it. The border is what separates it from them: those
   navigate, this one opens a picker, and a control that behaves differently
   should not be styled identically to the links beside it. */
.h3-lang {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; border-radius: var(--h3-r-pill); cursor: pointer;
  border: 1px solid var(--h3-line); background: none;
  font-family: inherit; font-size: 15px; text-align: left; letter-spacing: .025em;
  color: var(--h3-text-dim);
}
.h3-lang:hover { background: var(--h3-primary-weak); color: var(--h3-text); }
.h3-lang-caret { flex: 0 0 auto; }

/* ---------- Promo carousel ---------- */

.h3-hero { position: relative; border-radius: var(--h3-r-card); overflow: hidden; background: var(--h3-panel); }
.h3-hero-track { position: relative; aspect-ratio: 4 / 1; }
.h3-slide {
  position: absolute; inset: 0; opacity: 0; transition: opacity .5s ease;
  pointer-events: none;
}
.h3-slide.is-on { opacity: 1; pointer-events: auto; }
.h3-slide img { width: 100%; height: 100%; object-fit: cover; }
/* The scrim spans the whole slide, and the copy is held back by its own
   max-widths below. It used to be a 60%-wide box carrying the gradient, which
   put the far end of a 42ch paragraph at ~74% of that box — where the scrim had
   already faded out. Measured against a banner forced to pure white, the text
   sat on bare white there and vanished. A banner is an asset someone can swap,
   so the scrim has to carry the copy over a light photo rather than trust the
   artwork. Stops chosen against that same white worst case: the text ends at
   ~44% of the slide, where this holds .73 and the copy reads 7.3:1. */
.h3-slide-body {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: flex-start; justify-content: center; gap: 10px;
  padding-left: 6%; padding-right: 6%;
  background: linear-gradient(90deg,
    rgba(var(--h3-dark-rgb), .92) 0%,
    rgba(var(--h3-dark-rgb), .8) 40%,
    rgba(var(--h3-dark-rgb), .35) 65%,
    rgba(var(--h3-dark-rgb), 0) 88%);
}
.h3-slide-title {
  margin: 0; font-weight: 900;
  font-size: clamp(1.5rem, 2.5vw, 2.5rem); line-height: 1.1; max-width: 16ch;
  text-wrap: balance;
}
.h3-slide-text {
  /* Not --h3-text-dim: at .72 this line lands at 5.5:1 on the scrim above when
     the banner behind it is light. .88 keeps it recessive and reads 7.3:1. */
  margin: 0; max-width: 42ch; color: rgba(255, 255, 255, .88);
  font-weight: 300; font-size: clamp(.8rem, 1.4vw, 1rem); line-height: 1.35; text-wrap: pretty;
}

.h3-hero-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px; border-radius: 50%; cursor: pointer;
  display: grid; place-items: center; padding: 0;
  border: 1px solid var(--h3-line-media); background: rgba(var(--h3-dark-rgb), .6); color: var(--h3-text);
}
.h3-hero-arrow.is-prev { left: 12px; }
.h3-hero-arrow.is-next { right: 12px; }

/* The 18px gap is not decoration: it puts 26px between the centres of two dots,
   so the 24px hit areas below meet without overlapping. */
.h3-hero-dots { position: absolute; left: 0; right: 0; bottom: 14px; display: flex; justify-content: center; gap: 18px; }
.h3-hero-dot {
  position: relative;
  width: 8px; height: 8px; border-radius: 50%; border: 0; padding: 0; cursor: pointer;
  background: rgba(255, 255, 255, .55);
  transition: width .2s ease, background-color .2s ease;
}
/* The dot stays 8px tall; the pseudo-element carries the 24×24 target so touch
   has something to hit without the control growing into a blob. */
.h3-hero-dot::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 24px; height: 24px; transform: translate(-50%, -50%);
}
.h3-hero-dot.is-on { background: var(--h3-primary); width: 22px; border-radius: var(--h3-r-pill); }

.h3-hero-play {
  position: absolute; right: 12px; bottom: 12px;
  width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
  display: grid; place-items: center; padding: 0;
  border: 1px solid var(--h3-line-media); background: rgba(var(--h3-dark-rgb), .6); color: var(--h3-text);
}
/* Both icons stay in the DOM and cross-fade, so the control animates between
   states without a motion library and without swapping assets per state. */
.h3-hero-play .h3-ico {
  grid-area: 1 / 1; width: 14px; height: 14px;
  transition: opacity .2s cubic-bezier(.2, 0, 0, 1), scale .2s cubic-bezier(.2, 0, 0, 1);
}
.h3-hero-play .h3-ico-play { opacity: 0; scale: .25; }
.h3-hero-play.is-paused .h3-ico-pause { opacity: 0; scale: .25; }
.h3-hero-play.is-paused .h3-ico-play { opacity: 1; scale: 1; }

@media (max-width: 767px) {
  .h3-hero { border-radius: 0; margin: calc(var(--h3-gap) * -1) calc(var(--h3-gap) * -1) 0; }
  .h3-hero-track { aspect-ratio: 5 / 2; }
  .h3-slide-body {
    width: 100%; padding-left: 5%; padding-right: 5%; padding-bottom: 38px;
    justify-content: flex-end;
    background: linear-gradient(0deg, rgba(var(--h3-dark-rgb), .93) 25%, rgba(var(--h3-dark-rgb), .25) 75%);
  }
  .h3-hero-arrow { display: none; }
  .h3-hero-dots { bottom: 10px; }
  .h3-hero-play { right: 8px; bottom: 8px; }
}
/* The description is the first thing to go: at phone widths it competes with the
   slide's own button for the same few lines. */
@media (max-width: 639px) { .h3-slide-text { display: none; } }

/* ---------- Section headings ---------- */

/* The reference design highlights the first word of every heading in the accent
   colour. Our headings arrive from the dictionary as single strings, so the
   accent goes to a marker in front of the text instead of into it. */
/* No top margin: the space above a heading is the main column's gap, and a
   margin here would add to it rather than replace it. Normal inline flow, not
   flex: the accented opening and the rest are one sentence that has to wrap
   between them like any other text. */
.h3-sec-title, .h3-title {
  font-weight: 700; margin: 0 0 14px;
}
.h3-sec-title { font-size: clamp(1.25rem, 2vw, 1.5rem); }
.h3-title { font-size: clamp(1.5rem, 2.6vw, 2rem); }
/* The opening fragment of a heading. Colour is the only thing it changes — the
   heading is still one element and one string to a screen reader. The lighter
   accent step, not the fill one: 5.8:1 on the page background, and this is the
   binding case for the aurora field below it. */
.h3-hl { color: var(--h3-primary-text); }

/* ---------- Personal rail ---------- */

.h3-rail { display: flex; gap: 24px; overflow-x: auto; padding-bottom: 6px; scrollbar-width: none; }
.h3-rail::-webkit-scrollbar { display: none; }
/* Deliberately smaller than a catalog card, but not by much: at the 132px it
   started from it looked like a different page's component once the grid's
   cards grew to 260. */
.h3-rail-card { flex: 0 0 170px; }
.h3-rail-media {
  display: block; position: relative; aspect-ratio: 3 / 4;
  border-radius: var(--h3-r-card); overflow: hidden;
  background: var(--h3-panel);
}
.h3-rail-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.h3-rail-media canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
@media (prefers-reduced-motion: reduce) { .h3-rail-media canvas { display: none; } }
.h3-rail-name { display: block; margin-top: 8px; font-size: .88rem; font-weight: 600; }

/* The guest's rail carries catalog cards, not the rail card above it: a .h3-card
   takes its width from a grid track in .h3-grid and has none inside a flex row.
   Narrower than the grid's 240-290px on purpose — this is a shelf standing next
   to the catalog, and matching its cards would read as a second one. */
.h3-rail-cards .h3-card { flex: 0 0 200px; }
@media (max-width: 639px) { .h3-rail-cards .h3-card { flex-basis: 150px; } }

/* ---------- Search and filters ---------- */

.h3-catalog-head { margin: 0 0 16px; }
.h3-filters { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.h3-search {
  height: 40px; padding: 0 16px; border-radius: var(--h3-r-pill); min-width: 200px;
  border: 1px solid var(--h3-line); background: var(--h3-panel); color: var(--h3-text);
  font-family: inherit;
  /* Under 16px on iOS Safari the page zooms when the field takes focus, and the
     visitor has to pinch back out by hand. The smaller desktop size only applies
     where that behaviour does not exist. */
  font-size: 1rem;
}
.h3-search::placeholder { color: var(--h3-text-dim2); }
@media (min-width: 640px) { .h3-search { font-size: .92rem; } }
.h3-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.h3-chip {
  height: 36px; padding: 0 14px; border-radius: var(--h3-r-pill); cursor: pointer;
  border: 1px solid var(--h3-line); background: var(--h3-panel);
  color: var(--h3-text-dim); font-size: .86rem;
}
.h3-chip:hover { color: var(--h3-text); background: var(--h3-panel-2); }
/* White label on the accent, 4.7:1 — the same fill and the same reasoning as
   .h3-btn-primary. */
.h3-chip.is-on { color: var(--h3-on-accent); background: var(--h3-primary); border-color: transparent; font-weight: 600; }
/* The disclosure is not a category. Dashed border and the leading sign keep it
   from reading as one more trait sitting in the same row. */
.h3-chip.is-more { border-style: dashed; background: none; color: var(--h3-text); }
.h3-chip.is-more::before { content: "+ "; }
.h3-chip.is-more[aria-expanded="true"]::before { content: "− "; }

/* ---------- Catalog grid ---------- */

/* The card size sets the column count, not a list of viewport presets: the
   sidebar takes 280px out of the row from 1280px up, so a viewport-keyed rule
   would be measuring the wrong box. 240px is what reproduces the reference
   design's column counts through the whole range — two up to 640, three to
   1280, four above it — and lands on the same card widths it does: 260px inside
   a 1440 viewport, 290px once the column hits its cap. */
.h3-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 24px; }
/* Below 640px auto-fill has room for one 240px track and stretches it across the
   whole column — a card larger than the two it replaced. Two is the floor. */
@media (max-width: 639px) { .h3-grid { grid-template-columns: repeat(2, 1fr); gap: 16px; } }

.h3-card {
  position: relative; z-index: 0; margin: 0;
  aspect-ratio: 3 / 4; border-radius: var(--h3-r-card);
}
/* Only the media clips: the hover glow below is painted outside the card's box
   and overflow:hidden here would cut it away. */
.h3-card-media {
  position: absolute; inset: 0; overflow: hidden;
  border-radius: var(--h3-r-card); background: var(--h3-panel);
}
.h3-card-media img, .h3-card-media video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.h3-card-media video { opacity: 0; transition: opacity .25s ease; }
.h3-card:hover .h3-card-media video { opacity: 1; }

/* The card's two top pills — the occupation on the left, "already in your chats"
   on the right — share one row instead of being pinned to opposite corners. A
   rail card is 150px wide on a phone, and two absolutely positioned pills there
   would overlap; in a flex row they shrink and ellipsise past each other.
   The submit button under this row covers the whole tile and is what makes the
   card clickable, so the row must not swallow the pointer and punch a dead hole
   in it. */
.h3-card-pins {
  position: absolute; top: 12px; left: 12px; right: 12px; z-index: 2;
  display: flex; align-items: flex-start; gap: 8px;
  pointer-events: none;
}
.h3-card-tag, .h3-card-started {
  /* min-width:0 is what lets a flex item shrink below its text and ellipsise. */
  min-width: 0;
  display: inline-flex; align-items: center;
  padding: 4px 8px; border-radius: var(--h3-r-pill);
  font-size: .75rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.h3-card-tag {
  /* .72, not the .6 the arrows use: this pill carries text, and text over a
     light photo needs the extra alpha — 8.5:1 against a white image behind it
     instead of 5.4:1. */
  border: 1px solid var(--h3-line); background: rgba(var(--h3-dark-rgb), .72);
  color: var(--h3-text);
}
/* Filled with the accent rather than dressed like the neutral pill beside it:
   this one reports the visitor's own state, not a fact about the assistant, and
   at a glance across a grid the fill is what separates the cards he already
   writes to from the rest. White on the accent is 4.7:1, same as the buttons.
   margin-left:auto pins it right even when it is the only pill in the row. */
.h3-card-started {
  margin-left: auto;
  /* The occupation is what gives way when the row runs out of room: it is a
     detail about her, while this pill is the answer to "have I met her already"
     and half of it says nothing. */
  flex: none;
  background: var(--h3-primary); color: var(--h3-on-accent); font-weight: 600;
}
/* Two columns of grid on a phone leave a card about 165px wide, and there the
   pill above eats the row whole: what is left of the occupation is a syllable
   and an ellipsis. Below the breakpoint it steps out instead of being clipped
   into nonsense. The modifier is set by the template rather than read with
   :has(), which nothing else in this file relies on. */
@media (max-width: 639px) {
  .h3-card-pins-both .h3-card-tag { display: none; }
}

.h3-card-hit {
  position: absolute; inset: 0; z-index: 1; width: 100%; cursor: pointer; text-align: left;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 2px;
  padding: 14px; border: 0; border-radius: var(--h3-r-card); color: var(--h3-text);
  background: linear-gradient(0deg, rgba(var(--h3-dark-rgb), .88) 0%, rgba(var(--h3-dark-rgb), .82) 30%, rgba(var(--h3-dark-rgb), 0) 74%);
}
/* Pulled out to the card's edge and rounded on one side only, the way the
   reference design hangs it off the artwork. */
.h3-card-badge {
  align-self: flex-start; margin: 0 0 4px -14px;
  padding: 4px 8px 4px 14px; border-radius: 0 var(--h3-r-pill) var(--h3-r-pill) 0;
  background: linear-gradient(90deg, rgba(var(--h3-dark-rgb), .25), rgba(var(--h3-dark-rgb), .7));
  backdrop-filter: blur(4px);
  font-size: .75rem; line-height: 1.35; text-transform: capitalize;
  max-width: calc(100% + 14px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Clamping is CSS only: the button's accessible name still carries the full
   name and age, so nothing is lost to assistive tech or to the tests. */
.h3-card-name {
  font-size: 1.05rem; font-weight: 700; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden;
}
.h3-card-desc {
  font-size: .75rem; color: var(--h3-text-dim2); line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 1; line-clamp: 1;
  -webkit-box-orient: vertical; overflow: hidden;
}

/* The hover glow: a blurred, slowly drifting gradient painted behind the card.
   z-index:-1 keeps it under the artwork, and the parent's z-index:0 keeps it
   from sliding behind the page background. The gradient comes from the token —
   it used to be written out here by hand, which is how it stayed pink through an
   accent change. */
.h3-card:not(.h3-create-card)::before {
  content: ""; position: absolute; top: 2%; left: 2%; width: 96%; height: 96%; z-index: -1;
  border-radius: var(--h3-r-card);
  background: var(--h3-grad-accent);
  background-size: 400% 400%;
  filter: blur(40px);
  opacity: 0; transition: opacity .5s ease-in-out;
}
.h3-card:not(.h3-create-card):hover { z-index: 10; }
.h3-card:not(.h3-create-card):hover::before { opacity: .7; }
/* The drift only runs under the pointer. A grid holds twenty of these, and an
   always-on animation on twenty blurred gradients is compositor work paid for
   an effect nobody can see at opacity 0. */
@media (prefers-reduced-motion: no-preference) {
  .h3-card:not(.h3-create-card):hover::before { animation: h3-glow 20s linear infinite; }
}
@keyframes h3-glow {
  0% { background-position: 0 0; }
  50% { background-position: 400% 0; }
  100% { background-position: 0 0; }
}

/* The guest's Favorites rail marks the hovered card with a ring on the card
   itself instead of the glow above: its cards stand shoulder to shoulder in one
   row, and a wash of colour spilling out of every box there reads as the page
   lighting up rather than as this card being picked out. The override lives
   here, after the rule it cancels, and not up with the other .h3-rail-cards
   rules: both selectors carry two classes, so on equal specificity only source
   order decides, and from up there the glow would win.
   Nothing is painted outside the box any more, which is what let the rail drop
   the vertical padding that used to keep overflow-x from clipping the glow. */
.h3-rail-cards .h3-card::before { content: none; }
.h3-rail-cards .h3-card::after {
  content: ""; position: absolute; inset: 0;
  /* Above .h3-card-hit (1) and .h3-card-tag (2): the hit area paints a dark
     scrim over the lower third of the tile and would eat the bottom of the
     ring. Pointer-events off for the reason .h3-card-tag has them off — the
     submit button underneath is what makes the whole tile clickable. */
  z-index: 3; pointer-events: none;
  border-radius: var(--h3-r-card);
  /* A gradient fill minus its own content box: what survives is a 2px band
     along the radius. --h3-grad-accent, the same surface the grid card blurs
     into its hover aura, so the two agree without either pinning its own copy
     of the stops. Not the CTA's colour any more — that button went flat when
     the palette moved to the reference's. */
  padding: 2px;
  background: var(--h3-grad-accent);
  background-size: 400% 400%;
  /* Safari still wants the prefix here and knows only the xor keyword. Without
     this pair the ::after stays a solid rectangle over the artwork. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  opacity: 0; transition: opacity .25s ease;
}
.h3-rail-cards .h3-card:hover::after { opacity: 1; }
/* The same drift the glow has, under the pointer only for the same reason; the
   keyframes above are written for the 400% background this shares. */
@media (prefers-reduced-motion: no-preference) {
  .h3-rail-cards .h3-card:hover::after { animation: h3-glow 20s linear infinite; }
}

.h3-create-card {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  border: 1px dashed var(--h3-line); background: var(--h3-panel);
  text-align: center; padding: 16px;
}
.h3-create-card:hover { border-color: var(--h3-primary); }
.h3-create-plus { font-size: 2rem; line-height: 1; color: var(--h3-primary-text); }
.h3-create-title { display: block; font-weight: 700; }
.h3-create-sub { display: block; margin-top: 4px; font-size: .8rem; color: var(--h3-text-dim2); }

.h3-empty { color: var(--h3-text-dim2); padding: 24px 0; }
.h3-empty-text { margin: 0 0 14px; }
.h3-empty-reset { color: var(--h3-text); }

/* ---------- SEO copy ---------- */

/* The page's one block of long-form prose, and the only indexable copy it has.
   It is hidden from the visitor deliberately: it was written for search engines
   rather than for someone who came to pick a companion, and where it now stands
   — above the catalog, ahead of the page's only <h1> — it was a wall of text
   between the character rail and the grid. The markup stays in the document
   instead of being deleted so that crawlers still read it; nothing in this
   project renders differently for a bot, so there is no other way to serve it.
   display:none rather than an sr-only clip because the clip keeps the text in
   the accessibility tree, and a screen reader would then announce all
   thirty-six paragraphs of it.

   Everything below is consequently dead, and kept anyway: it is tuned
   typography that would have to be written from scratch if the section is ever
   shown again, which turns a one-word revert into a rebuild. */
.h3-seo { display: none; border-top: 1px solid var(--h3-line); padding-top: 34px; }
.h3-seo-inner { max-width: 68ch; }
.h3-seo-title { font-weight: 700; font-size: clamp(1.5rem, 2.6vw, 2rem); margin: 0 0 18px; text-wrap: balance; }
.h3-seo h2 { font-weight: 600; font-size: clamp(1.2rem, 1.9vw, 1.45rem); margin: 32px 0 10px; text-wrap: balance; }
.h3-seo p { margin: 0 0 14px; color: var(--h3-text-dim); line-height: 1.72; text-wrap: pretty; }
/* The lead paragraph carries the page's pitch, so it sits a step brighter and a
   step larger than the body that follows it. */
.h3-seo-lead { font-size: 1.05rem; color: var(--h3-text); }
/* The bolded run-ins that open several paragraphs. Accent rather than plain
   white: they are the same device as .h3-hl in the headings. */
.h3-seo p strong { color: var(--h3-primary-text); font-weight: 700; }

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

.h3-faq { max-width: 900px; }
.h3-faq-item {
  background: var(--h3-panel); border: 1px solid var(--h3-line);
  border-radius: 12px; margin-bottom: 8px; padding: 14px 16px;
}
.h3-faq-item summary {
  cursor: pointer; font-weight: 600; list-style: none;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.h3-faq-item summary::-webkit-details-marker { display: none; }
.h3-faq-caret { flex: 0 0 auto; color: var(--h3-text-dim2); transition: transform .2s ease; }
.h3-faq-item[open] .h3-faq-caret { transform: rotate(90deg); }
/* The section is 900px wide for the summaries; the answers need their own cap.
   At 16px, 900px runs to roughly 112 characters a line and the eye loses the
   next line on the way back. */
.h3-faq-item p { margin: 10px 0 0; max-width: 68ch; color: var(--h3-text-dim); line-height: 1.6; text-wrap: pretty; }

/* ---------- Footer ---------- */

.h3-foot-links { display: flex; flex-wrap: wrap; gap: 8px 22px; margin-bottom: 14px; }
.h3-foot-links a { font-size: .88rem; }
.h3-foot-links a:hover { color: var(--h3-primary-text); }
.h3-foot-note { margin: 4px 0; font-size: .8rem; }

/* ---------- Drawer ---------- */

.h3-drawer-wrap { position: fixed; inset: 0; z-index: 60; }
.h3-drawer-scrim { position: absolute; inset: 0; background: rgba(var(--h3-dark-rgb), .78); }
.h3-drawer {
  position: absolute; top: 0; bottom: 0; left: 0; width: min(320px, 86vw);
  background: var(--h3-bg-header); border-right: 1px solid var(--h3-line);
  padding: 20px; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 14px;
}
.h3-drawer-x {
  align-self: flex-end; width: 36px; height: 36px; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--h3-line-media); background: none; color: var(--h3-text);
  font-size: 1.3rem; line-height: 1;
}
body.h3-locked { overflow: hidden; }

/* ---------- Bottom tab bar ---------- */

.h3-tabbar { display: none; }
@media (max-width: 899px) {
  .h3-tabbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    display: grid; grid-template-columns: repeat(5, 1fr);
    background: var(--h3-bg-header);
    border-top: 1px solid var(--h3-line);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .h3-tabbar-item {
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 10px 4px; text-align: center; font-size: .72rem; font-weight: 500;
    color: var(--h3-text-dim); border: 0; background: none; cursor: pointer;
  }
  .h3-tabbar-item.is-on, .h3-tabbar-item.is-premium { color: var(--h3-primary-text); }
  /* The centre tile is lifted out of the bar and sits in a glowing disc, the way
     the reference design promotes its main action. The two pseudo-elements are
     the bar's own background punched up behind the disc so it reads as a notch
     rather than as a button floating over the content. */
  .h3-tabbar-item.is-raised { position: relative; gap: 1px; }
  .h3-tabbar-item.is-raised .h3-tabbar-raised-ico {
    position: relative; z-index: 3;
    display: flex; align-items: center; justify-content: center;
    width: 50px; height: 50px; margin-top: -23px; border-radius: 50%;
    color: #fff; border: 1px solid transparent;
    background: var(--h3-glow-fill) padding-box, var(--h3-glow-border) border-box;
    box-shadow: var(--h3-glow-shadow);
  }
  .h3-tabbar-item.is-raised > span:last-child { position: relative; z-index: 3; }
  .h3-tabbar-item.is-raised::before {
    content: ""; position: absolute; z-index: 1;
    width: 64px; height: 64px; transform: translateY(-30px); border-radius: 50%;
    border: 1px solid var(--h3-line); background: var(--h3-bg-header);
  }
  .h3-tabbar-item.is-raised::after {
    content: ""; position: absolute; inset: 0; z-index: 2;
    transform: translateY(-11px); background: var(--h3-bg-header);
  }
  /* .h3-footer is a sibling of .h3-shell and renders after it, so it — not
     .h3-main — is the page's last element and the one the fixed tab bar
     actually overlays. The clearance belongs here, composed with the safe-area
     inset rather than a guessed pixel value. */
  .h3-main { padding-bottom: 12px; }
  /* 76px, not the bar's 66: the raised centre tile stands 23px proud of it, and
     at 66 the copyright line ran under that disc. */
  .h3-footer { padding-bottom: calc(28px + 76px + env(safe-area-inset-bottom)); }
}
