/* HunTracker marketing site — huntracker.app
   Tokens are the app's own (design/huntracker.tokens.json, BRANDBOOK.md §referenceUi),
   so the site and the product read as one thing. Fonts are self-hosted on purpose:
   the site makes zero third-party requests, which is what keeps it consent-free
   under GDPR/ePrivacy — no visitor IP ever reaches Google Fonts or an analytics host. */

@font-face {
  font-family: 'Outfit';
  src: url('fonts/Outfit.woff2') format('woff2-variations');
  font-weight: 100 900; font-display: swap; font-style: normal;
}
@font-face {
  font-family: 'Fraunces';
  src: url('fonts/Fraunces.woff2') format('woff2-variations');
  font-weight: 100 900; font-display: swap; font-style: normal;
}

:root {
  --base:        #0c1208;
  --base-deep:   #070d05;
  --surface:     rgba(255,255,255,0.055);
  --surface-2:   rgba(255,255,255,0.085);
  --nav:         rgba(8,13,4,0.82);
  --text:        #f0e6c8;
  --dim:         rgba(220,210,185,0.72);
  --muted:       rgba(220,210,185,0.66);
  --gold:        #c8a84b;
  --gold-bright: #e8c96a;
  --gold-bg:     rgba(200,168,75,0.16);
  --gold-br:     rgba(200,168,75,0.34);
  --border:      rgba(255,255,255,0.11);
  --border-str:  rgba(255,255,255,0.2);
  --moss:        #8FA75F;

  /* Flat, opaque fills: the pricing table (whose sticky label column has to
     match the panel it scrolls over exactly) and anything else that must
     not let the map through. The exact colours the old translucent
     --surface / --surface-2 composited to over --base. */
  --card:        #191f16;
  --card-2:      #21261d;

  /* Panels laid on the map. Nine-tenths opaque: the contours behind text
     drop to a couple of percent -- a texture you notice only at the edges,
     never a line through a letter -- so a panel reads as laid on the map
     rather than cut out of it. The edge is a hairline that is gold where the
     light comes from (top left) and fades to a neutral rim, drawn as a
     second background clipped to the border box. */
  --panel:       linear-gradient(180deg, rgba(36,42,31,.9) 0%, rgba(22,28,19,.9) 100%);
  --panel-edge:  linear-gradient(150deg, rgba(232,201,106,.36) 0%, rgba(232,201,106,.08) 32%, rgba(255,255,255,.075) 62%, rgba(255,255,255,.035) 100%);
  --panel-hot:   linear-gradient(150deg, rgba(232,201,106,.7) 0%, rgba(232,201,106,.2) 38%, rgba(232,201,106,.1) 70%, rgba(232,201,106,.06) 100%);

  /* The contour lines. A muted moss green at low alpha, not the brand gold:
     gold lines, however faint, warm the ground toward olive, and the page has
     to keep reading as the same very dark green. Index lines are drawn at this
     full strength inside topo.svg, minor lines at .55 of it. */
  --topo-ink:    rgba(143,167,95,.17);

  /* Motion. Arrivals use an expo-out: most of the distance in the first third,
     then a long, soft landing, which reads as something settling onto the
     page rather than stopping on it. State changes (hover, press, the nav)
     use a gentler curve so they never feel snappy. Everything that moves on
     this page moves by transform or opacity only, which the compositor runs
     off the main thread -- that, not the curves, is what keeps it smooth. */
  --ease-out:  cubic-bezier(.16, 1, .3, 1);
  --ease-soft: cubic-bezier(.22, 1, .36, 1);
  --ease-pop:  cubic-bezier(.34, 1.56, .64, 1);

  --ui:   'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --ed:   'Fraunces', Georgia, 'Times New Roman', serif;

  --wrap: 1140px;
  --pad:  clamp(20px, 5vw, 40px);
  --r:    18px;
  --nav-h: 70px;
  --feat-gap: clamp(80px, 10vw, 140px);
}
@media (max-width: 420px) { :root { --nav-h: 64px; } }

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: calc(var(--nav-h) + 12px); }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

body {
  margin: 0;
  background: var(--base);
  color: var(--text);
  font-family: var(--ui);
  font-size: 17px;
  line-height: 1.62;
  font-weight: 350;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  /* Anchors .topo, which spans the whole document from here. */
  position: relative;
}

/* ---------- topographic map ----------
   One generated contour map (web-site/make_topo.py) laid under the entire
   page, from the top of the hero to the bottom of the footer. build.py puts
   it right after <body> as a <use> of assets/img/topo.svg.

   One map rather than a repeating tile, so no hill comes round again further
   down; and live vector through <use> rather than a CSS background, because a
   background image is rasterised whole, and a page-tall map at 3x is far past
   the size iOS will decode. The container is body-sized with overflow hidden,
   so the 16000px map never adds scroll height to a shorter page.

   z-index -1 in the root stacking context: above the page colour, below all
   content. Nothing else paints a ground over it -- the hero photograph
   dissolves into it and the footer sits directly on it. */
.topo {
  position: absolute; inset: 0; z-index: -1;
  overflow: hidden; pointer-events: none;
  color: var(--topo-ink);
}
/* max-width:none and no transform: the global svg rule would shrink it to the
   container, and a transform on a 16000px-tall element risks promoting it to
   one enormous compositor layer. Centred with an inline left:calc() instead.
   That is also why nothing below ever moves the map itself: the parallax on
   this page is all on small elements above it. */
.topo svg { position: absolute; top: 0; max-width: none; height: auto; }

/* Depth. A fixed vignette over the map (and under every piece of content):
   the contours fall off into shadow toward the edges of the window, so the
   middle of the screen -- where the reading happens -- reads as lit. Fixed
   and static, so it is one composited layer that scrolling never repaints.
   body::after rather than ::before so it comes after .topo in tree order and
   paints above it at the same z-index. */
body::after {
  content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(130% 95% at 50% 38%, rgba(4,7,2,0) 50%, rgba(4,7,2,.55) 100%);
}

/* Anyone who has asked their system for more contrast gets a plain ground --
   the map is decoration and must not cost legibility. */
@media (prefers-contrast: more) {
  .topo, body::after { display: none; }
}

img, svg, video { max-width: 100%; height: auto; display: block; }

a { color: var(--gold-bright); text-decoration-color: var(--gold-br); text-underline-offset: 3px; transition: color .3s var(--ease-soft); }
a:hover { color: var(--text); }

/* Visible focus for keyboard users — EN 301 549 / WCAG 2.4.7, which the
   European Accessibility Act makes a legal requirement for this kind of
   consumer service, not just good practice. */
:focus-visible {
  outline: 2px solid var(--gold-bright);
  outline-offset: 3px;
  border-radius: 6px;
}

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--pad); }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--gold); color: #12180c; padding: 12px 20px;
  border-radius: 0 0 10px 0; font-weight: 600;
}
.skip:focus { left: 0; }

/* ---------- type ---------- */
h1, h2, h3 { font-family: var(--ed); font-weight: 600; letter-spacing: -0.015em; margin: 0; line-height: 1.1; text-wrap: balance; }
h1 { font-size: clamp(2.6rem, 7vw, 4.7rem); font-variation-settings: 'opsz' 96, 'SOFT' 18, 'WONK' 1; }
h2 { font-size: clamp(2rem, 4.4vw, 3rem); font-variation-settings: 'opsz' 72, 'SOFT' 14, 'WONK' 1; }
h3 { font-size: clamp(1.25rem, 2.2vw, 1.55rem); font-variation-settings: 'opsz' 36, 'SOFT' 10; letter-spacing: -0.01em; }
p  { margin: 0 0 1.1em; text-wrap: pretty; }
.lead { font-size: clamp(1.06rem, 1.7vw, 1.28rem); color: var(--dim); line-height: 1.66; }

/* A short gold rule leads every eyebrow -- and closes it on a centred head --
   the way a map legend sets off its captions. */
.eyebrow {
  display: inline-flex; align-items: center; gap: 12px;
  font-family: var(--ui); font-size: .78rem; font-weight: 600;
  letter-spacing: .17em; text-transform: uppercase; color: var(--gold);
  margin: 0 0 16px;
}
.eyebrow::before,
.center .eyebrow::after, .mission .eyebrow::after {
  content: ''; width: 28px; height: 1px; flex: none;
  background: linear-gradient(90deg, rgba(200,168,75,0), currentColor);
}
.center .eyebrow::after, .mission .eyebrow::after {
  background: linear-gradient(90deg, currentColor, rgba(200,168,75,0));
}

/* ---------- nav ----------
   Dark like the rest of the site. The only light element is the pill behind
   the wordmark, which is how the app itself presents the lockup on a dark
   ground (see the login screen in dark mode): the mark's "Hun" is forest
   green and "Tracker" is warm brown, and neither reads on near-black, so the
   brand gives it its own ivory plate rather than switching to the white
   variant. Fill, radius and padding are measured off the app -- #F0E6C8 is
   the same ivory as --text, and the radius is 0.24x the pill's height.

   At the top of the page the bar is glass over the hero photograph (the hero
   is pulled up underneath it); once the page scrolls, site.js marks it
   .scrolled and it settles into the solid bar. Without JS it simply stays
   solid. */
.nav {
  position: sticky; top: 0; z-index: 100;
  background: var(--nav);
  backdrop-filter: saturate(160%) blur(16px);
  -webkit-backdrop-filter: saturate(160%) blur(16px);
  border-bottom: 1px solid var(--border);
  transition: background-color .6s var(--ease-soft), border-color .6s var(--ease-soft), box-shadow .6s var(--ease-soft);
}
.js .nav:not(.scrolled) { background-color: rgba(8,13,4,.22); border-bottom-color: rgba(255,255,255,0); }
.js .nav.scrolled { box-shadow: 0 18px 40px -28px rgba(0,0,0,.9); }
.nav-in { display: flex; align-items: center; gap: 18px; height: var(--nav-h); }
.nav-logo { display: flex; align-items: center; gap: 11px; text-decoration: none; margin-right: auto; }
.nav-logo .mark-emblem { height: 40px; width: auto; transition: transform .6s var(--ease-pop); }
.nav-logo:hover .mark-emblem { transform: rotate(-6deg) scale(1.05); }

.mark-pill {
  display: inline-flex; align-items: center;
  background: var(--text);
  padding: 6px 11px;
  border-radius: 8px;
}
.mark-pill img { height: 21px; width: auto; display: block; }

@media (max-width: 420px) {
  .nav-in { gap: 12px; }
  .nav-logo { gap: 9px; }
  .nav-logo .mark-emblem { height: 34px; }
  .mark-pill { padding: 5px 9px; border-radius: 7px; }
  .mark-pill img { height: 18px; }
}

.nav-links { display: flex; align-items: center; gap: 26px; }
.nav-links a {
  position: relative;
  color: var(--dim); text-decoration: none; font-size: .93rem; font-weight: 450;
}
/* An underline that draws out from the middle, rather than appearing. */
.nav-links a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -5px; height: 1px;
  background: var(--gold-bright); transform: scaleX(0);
  transition: transform .45s var(--ease-soft);
}
.nav-links a:hover { color: var(--text); }
.nav-links a:hover::after { transform: scaleX(1); }
.lang {
  display: flex; gap: 1px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 9px; padding: 2px; font-size: .78rem;
}
.lang a {
  padding: 3px 9px; border-radius: 7px; color: var(--muted);
  text-decoration: none; font-weight: 600; letter-spacing: .04em;
  transition: background-color .3s var(--ease-soft), color .3s var(--ease-soft);
}
.lang a:hover { color: var(--text); }
.lang a[aria-current="true"] { background: var(--gold-bg); color: var(--gold-bright); }
@media (max-width: 860px) { .nav-links .hide-sm { display: none; } }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 14px 26px; border-radius: 13px;
  font-family: var(--ui); font-size: 1rem; font-weight: 600;
  text-decoration: none; border: 1px solid transparent; cursor: pointer;
  transition: transform .45s var(--ease-soft), background-color .3s var(--ease-soft),
              border-color .3s var(--ease-soft), box-shadow .45s var(--ease-soft);
}
.btn:hover { transform: translate3d(0,-2px,0); }
.btn:active { transform: translate3d(0,0,0) scale(.98); transition-duration: .12s; }
.btn-primary { background: var(--gold); color: #12180c; }
.btn-primary:hover { background: var(--gold-bright); color: #12180c; box-shadow: 0 16px 34px -16px rgba(232,201,106,.65); }
.btn-ghost { background: var(--surface); color: var(--text); border-color: var(--border-str); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); border-color: var(--gold-br); }

/* Store badges, drawn rather than fetched — Apple's and Google's official
   badge images are hosted assets, and pulling them would put a third-party
   request back on a page whose whole compliance story is that it has none. */
.stores { display: flex; flex-wrap: wrap; gap: 13px; }
.store-badge {
  display: inline-flex; align-items: center; gap: 11px;
  padding: 11px 20px 11px 17px; border-radius: 13px;
  background: var(--text); color: #10160b; text-decoration: none;
  border: 1px solid rgba(255,255,255,.2);
  box-shadow: 0 10px 24px -16px rgba(0,0,0,.8);
  transition: transform .45s var(--ease-soft), background-color .3s var(--ease-soft), box-shadow .45s var(--ease-soft);
}
.store-badge:hover {
  transform: translate3d(0,-3px,0); background: #fff; color: #10160b;
  box-shadow: 0 20px 40px -18px rgba(232,201,106,.6), 0 0 0 1px rgba(232,201,106,.45);
}
.store-badge:active { transform: translate3d(0,-1px,0) scale(.98); transition-duration: .12s; }
.store-badge svg { width: 25px; height: 25px; flex: none; }
.store-badge span { display: block; line-height: 1.16; }
.store-badge .sb-sm { font-size: .63rem; letter-spacing: .08em; text-transform: uppercase; opacity: .72; font-weight: 600; }
.store-badge .sb-lg { font-size: 1.03rem; font-weight: 650; letter-spacing: -.01em; }

/* ---------- hero ----------
   Pulled up under the nav by the nav's own height, so the photograph runs
   to the top edge of the window and the glass bar floats over it. */
.hero { position: relative; overflow: hidden; isolation: isolate; margin-top: calc(-1 * var(--nav-h)); }
.hero-bg {
  position: absolute; inset: 0; z-index: -2;
  /* The photograph dissolves into the map underneath rather than into a flat
     band of page colour -- a solid fade here drew a hard line exactly where
     the contours began. The mask also covers the scrim in ::after, so the
     darkening fades out with the photo. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 58%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 58%, transparent 100%);
}
.hero-bg img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 42%;
  /* The frame settles: it opens a touch close and eases back to its crop. */
  animation: settle 3.4s var(--ease-out) both;
}
@keyframes settle { from { transform: scale(1.1); } to { transform: none; } }
/* Two scrims with different jobs, kept apart on purpose: the horizontal one
   buys contrast for the headline on the left, the vertical one only closes the
   photograph into the page colour at the very bottom. Darkening the whole
   frame to solve the first problem is what flattens the picture.

   Tuned for a blue-hour frame, which is already dark: the horizontal scrim
   only has to deepen the left rather than rescue it. (An amber, light-sky
   photograph needs a far heavier hand -- if this hero is ever swapped for one,
   the gradient has to be rebuilt, not reused.) */
.hero-bg::after {
  content: ''; position: absolute; inset: 0;
  background:
    linear-gradient(to right, rgba(7,12,4,.86) 0%, rgba(7,12,4,.60) 34%, rgba(7,12,4,.12) 68%, rgba(7,12,4,0) 100%),
    linear-gradient(to bottom, rgba(7,12,4,.44) 0%, rgba(7,12,4,0) 30%, rgba(7,12,4,0) 62%, rgba(12,18,8,.6) 100%);
}
.hero-in {
  display: grid; grid-template-columns: minmax(0,1fr) minmax(0,.82fr);
  gap: clamp(28px, 5vw, 64px); align-items: center;
  min-height: calc(min(88vh, 820px) + var(--nav-h));
  padding-top: calc(var(--nav-h) + clamp(56px, 8vw, 96px)); padding-bottom: clamp(48px, 7vw, 88px);
}
.hero h1 { text-shadow: 0 2px 34px rgba(0,0,0,.5); }
.hero .lead { max-width: 33ch; color: rgba(235,228,205,.86); margin-top: 22px; }
.hero .stores { margin-top: 32px; }
.hero-note { margin-top: 18px; font-size: .85rem; color: var(--muted); }
@media (max-width: 900px) {
  .hero-in { grid-template-columns: 1fr; }
  .hero .lead { max-width: none; }
  .hero-phone { margin-inline: auto; max-width: 290px; }
}

/* The headline rises line by line from behind its own baseline. Each line is
   a clipping box; the clip is padded well past the glyphs (and pulled back by
   the same negative margin, so layout is untouched) so neither the text
   shadow nor a descender is cut off at rest. */
/* A flex column, because the lines' negative margins must add up rather
   than collapse into one another, as stacked block margins would -- that
   pushed the lines a fifth of an em further apart than the type sets them. */
.hero h1, .mission blockquote { display: flex; flex-direction: column; }
.ln { display: block; overflow: hidden; padding: .2em .5em .26em; margin: -.2em -.5em -.26em; }
.ln > * { display: block; }
.hero .ln > * { animation: line-rise 1.35s var(--ease-out) both; }
.hero .ln:nth-child(2) > * { animation-delay: .13s; }
@keyframes line-rise { from { transform: translate3d(0, calc(100% + .35em), 0); } to { transform: none; } }

.hero .lead  { animation: fade-rise 1.3s var(--ease-out) .34s both; }
.hero .stores { animation: fade-rise 1.3s var(--ease-out) .46s both; }
.hero-note   { animation: fade-rise 1.3s var(--ease-out) .58s both; }
.hero-phone  { animation: phone-in 1.7s var(--ease-out) .18s both; }
@keyframes fade-rise { from { opacity: 0; transform: translate3d(0, 18px, 0); } to { opacity: 1; transform: none; } }
@keyframes phone-in  { from { opacity: 0; transform: translate3d(0, 64px, 0) scale(.94); } to { opacity: 1; transform: none; } }

/* Photographer's credit, kept on the image it belongs to. Every seasonal
   photograph in the app and on this site is © Csótai László, and the app
   credits them on its own Home screen -- the site does the same rather than
   burying it. */
.hero-credit {
  position: absolute; left: 0; right: 0; bottom: 14px;
  margin: 0; padding-inline: var(--pad);
  color: rgba(220,210,185,.58); font-size: .72rem; letter-spacing: .02em;
}
.hero-credit span { max-width: var(--wrap); margin-inline: auto; display: block; }

/* ---------- iPhone ----------
   The phone is a single pre-composited image: screenshot already dropped
   into the hardware render by web-site/make_shots.py. An earlier version
   drew the bezel in CSS and layered the screenshot underneath, which meant
   hand-tuned percentages for the screen opening -- fine until the frame is
   re-exported at a different size, at which point every one of them is wrong
   and nothing says so. Compositing in pixels, against the frame's own
   geometry, cannot drift.

   drop-shadow rather than box-shadow on purpose: the image is transparent
   around the phone's silhouette, and drop-shadow follows that silhouette
   (including the rounded corners) where box-shadow would draw a rectangle. */
.phone { position: relative; width: 100%; max-width: 340px; margin-inline: auto; }
.phone img {
  width: 100%; height: auto; display: block;
  filter:
    drop-shadow(0 34px 54px rgba(0,0,0,.58))
    drop-shadow(0 8px 18px rgba(0,0,0,.42));
}
.phone-float { animation: float 7.5s ease-in-out infinite; will-change: transform; }
@keyframes float { 0%,100% { transform: translate3d(0,0,0); } 50% { transform: translate3d(0,-14px,0); } }

/* ---------- sections ---------- */
section { position: relative; }
.sec { padding-block: clamp(72px, 10vw, 130px); }
.sec-head { max-width: 640px; margin-bottom: clamp(48px, 7vw, 80px); }
.sec-head.center { margin-inline: auto; text-align: center; }

/* mission */
.mission { text-align: center; padding-block: clamp(80px, 12vw, 150px); isolation: isolate; }
.mission::before {
  content: ''; position: absolute; z-index: -1; left: 50%; top: 50%;
  width: min(1000px, 140vw); aspect-ratio: 2 / 1; transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, rgba(232,201,106,.08), rgba(143,167,95,.04) 55%, rgba(0,0,0,0) 78%);
  pointer-events: none;
}
.mission blockquote {
  margin: 0 auto; max-width: 22ch;
  font-family: var(--ed); font-size: clamp(2rem, 5.2vw, 3.5rem);
  font-variation-settings: 'opsz' 88, 'SOFT' 22, 'WONK' 1;
  font-weight: 500; line-height: 1.14; letter-spacing: -.02em;
}
.mission blockquote em { font-style: italic; color: var(--gold-bright); }
.mission .lead { max-width: 58ch; margin: 30px auto 0; }

/* feature rows */
.feats { position: relative; }
.feat { position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: clamp(40px, 7vw, 100px); align-items: center; }
.feat + .feat { margin-top: var(--feat-gap); }
.feats > .feat:nth-child(odd) .feat-media { order: -1; }
.feat-media { position: relative; display: flex; justify-content: center; isolation: isolate; }
.feat-media .phone { max-width: 302px; }
/* A pool of warm light behind each phone, as if the screen lit the map it
   lies on. A plain radial gradient on a pseudo-element: static, so it costs
   nothing once painted. */
.feat-media::before, .feat-devices::before {
  content: ''; position: absolute; z-index: -1; left: 50%; top: 50%;
  width: min(620px, 140%); aspect-ratio: 1; transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, rgba(232,201,106,.13), rgba(143,167,95,.055) 48%, rgba(0,0,0,0) 74%);
  pointer-events: none;
}
.feat h2 { margin-bottom: 18px; }
/* The copy beside each phone sits on a panel laid on the map. */
.feat-text {
  position: relative;
  border: 1px solid transparent;
  border-radius: calc(var(--r) + 6px);
  background: var(--panel) padding-box, var(--panel-edge) border-box;
  padding: clamp(28px, 3.6vw, 48px);
  box-shadow: 0 50px 90px -54px rgba(0,0,0,.95), inset 0 1px 0 rgba(255,255,255,.035);
}
.feat-text > :last-child { margin-bottom: 0; }
@media (max-width: 880px) {
  .feat { grid-template-columns: 1fr; }
  .feats > .feat:nth-child(odd) .feat-media { order: 0; }
  .feat-media .phone { max-width: 258px; }
}

/* The statistics row, when it carries the multi-device scene: text on top,
   devices below at full width. A 1.65-aspect composition dropped into half a
   feature row would render smaller than the phone it replaced, which is the
   opposite of the point. */
.feat-wide {
  grid-template-columns: 1fr;
  gap: clamp(30px, 4vw, 48px);
  text-align: center;
}
.feat-wide .feat-text { max-width: 62ch; margin-inline: auto; }
.feat-wide .ticks {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 10px 26px;
}
.feat-wide .feat-note { text-align: left; max-width: 60ch; margin-inline: auto; }
.feat-devices { position: relative; width: 100%; isolation: isolate; }
.feat-devices::before { width: min(1100px, 120%); aspect-ratio: 2 / 1; }
.feat-devices img {
  width: 100%; max-width: 980px; margin-inline: auto; height: auto;
  filter: drop-shadow(0 30px 56px rgba(0,0,0,.55));
}
/* The alternation counts .feat siblings, so a full-width row must not take a
   turn in it -- otherwise every row after this one flips side. */
.feat-wide .feat-media, .feat-wide .feat-devices { order: 0; }

/* ---------- the route ----------
   A GPS route, drawn the way the app draws one -- a gold line on a dark
   casing -- walks down the middle of the feature rows and stops at a
   waypoint beside each one. site.js lays it out from the rows' real
   geometry as ONE continuous path and draws it as far down as the reader
   has scrolled, so the route gets walked as the page is read.

   The drawing is stroke-dashoffset against pathLength=1: the length of the
   line never has to be measured to animate it. Below the one-column
   breakpoint there is no middle to walk down, so the route stays off. */
.route { position: absolute; left: 50%; z-index: -1; overflow: visible; pointer-events: none; }
.route path {
  fill: none; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 1; stroke-dashoffset: 1;
  transition: stroke-dashoffset 1s var(--ease-soft);
}
.route .t-glow { stroke: rgba(232,201,106,.13); stroke-width: 13; }
.route .t-case { stroke: rgba(9,14,6,.92); stroke-width: 6.5; }
.route .t-line { stroke: var(--gold-bright); stroke-width: 2.6; }
/* Markers along the route that report how far down it has come into view.
   They only need a box for the observer to see. */
.route-stop {
  position: absolute; left: 50%; width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}

.waypoint {
  position: absolute; left: 50%; top: 50%; z-index: 1;
  width: 14px; height: 14px; margin: -7px 0 0 -7px; border-radius: 50%;
  background: var(--gold-bright);
  box-shadow: 0 0 0 3.5px rgba(9,14,6,.95), 0 0 0 5px rgba(232,201,106,.4), 0 0 22px 4px rgba(232,201,106,.28);
  transform: scale(0);
  transition: transform .8s var(--ease-pop) .45s;
  pointer-events: none;
}
.feat-wide .waypoint { top: 0; }
.waypoint.in { transform: scale(1); }
/* A slow sonar ping, like the app's live position -- the waypoint you have
   just reached is the one that is alive. */
.waypoint::after {
  content: ''; position: absolute; inset: -5px; border-radius: 50%;
  border: 1.5px solid rgba(232,201,106,.55); opacity: 0;
}
.waypoint.in::after { animation: ping 3.4s var(--ease-out) 1.3s infinite; }
@keyframes ping {
  0%   { transform: scale(.7); opacity: .85; }
  70%, 100% { transform: scale(2.8); opacity: 0; }
}
@media (max-width: 880px) { .route, .waypoint { display: none; } }

.ticks { list-style: none; margin: 22px 0 0; padding: 0; display: grid; gap: 11px; }
.ticks li { position: relative; padding-left: 30px; color: var(--dim); font-size: 1rem; }
.ticks li::before {
  content: ''; position: absolute; left: 3px; top: .56em;
  width: 11px; height: 6px; border-left: 2px solid var(--gold); border-bottom: 2px solid var(--gold);
  transform: rotate(-45deg);
}

/* A caveat that sits with the claim it qualifies. Muted, but --muted now
   clears 4.5:1, so it is quiet rather than unreadable -- which matters more
   here than anywhere else on the page, since this is the legal one. */
.feat-note {
  margin: 22px 0 0; padding-left: 14px;
  border-left: 2px solid var(--gold-br);
  color: var(--muted); font-size: .86rem; line-height: 1.6;
}

.tag {
  display: inline-flex; align-items: center; gap: 6px; margin-left: 10px;
  padding: 3px 10px; border-radius: 999px; vertical-align: middle;
  background: var(--gold-bg); border: 1px solid var(--gold-br);
  color: var(--gold-bright); font-family: var(--ui);
  font-size: .62rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
}

/* card grid */
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(266px, 1fr)); gap: 20px; }
.card {
  position: relative;
  border: 1px solid transparent; border-radius: var(--r);
  background: var(--panel) padding-box, var(--panel-edge) border-box;
  padding: 30px 28px;
  box-shadow: 0 34px 64px -46px rgba(0,0,0,.95), inset 0 1px 0 rgba(255,255,255,.03);
  transition: transform .6s var(--ease-soft), box-shadow .6s var(--ease-soft);
}
/* Hover lights the card from wherever the pointer is: a soft pool of gold
   inside, and the rim catching it. site.js feeds the pointer position in as
   --mx/--my (fine pointers only); without it the light sits top centre.
   Both layers only fade, so a hover never repaints more than the one card. */
.card::before, .card::after {
  content: ''; position: absolute; pointer-events: none; border-radius: inherit;
  opacity: 0; transition: opacity .5s var(--ease-soft);
}
.card::before {
  inset: 0;
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 0%), rgba(232,201,106,.1), rgba(232,201,106,0) 62%);
}
.card::after {
  inset: -1px; padding: 1px;
  background: radial-gradient(320px circle at var(--mx, 50%) var(--my, 0%), rgba(232,201,106,.85), rgba(232,201,106,.14) 55%, rgba(232,201,106,0) 82%);
  -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 exclude, linear-gradient(#000 0 0);
}
.card:hover { transform: translate3d(0,-4px,0); box-shadow: 0 44px 74px -42px rgba(0,0,0,1), inset 0 1px 0 rgba(255,255,255,.03); }
.card:hover::before, .card:hover::after { opacity: 1; }
/* A Pro feature leading the grid: the full row, in the same gold treatment as
   the Pro plan card, so it reads as part of what Pro buys rather than one more
   tile. On a wide card the icon sits beside the text instead of above it. */
.card-wide { grid-column: 1 / -1; display: grid; grid-template-columns: auto 1fr; column-gap: 22px; align-items: start; }
.card-wide .card-ico { grid-row: span 2; margin-bottom: 0; }
.card-wide p { max-width: 72ch; }
.card-pro {
  background:
    linear-gradient(135deg, rgba(200,168,75,.16), rgba(200,168,75,0) 55%) padding-box,
    var(--panel) padding-box,
    var(--panel-hot) border-box;
}
.card h3 .tag { vertical-align: 3px; }
@media (max-width: 560px) {
  .card-wide { grid-template-columns: 1fr; }
  .card-wide .card-ico { grid-row: auto; margin-bottom: 20px; }
}
.card h3 { margin-bottom: 11px; }
.card p { color: var(--dim); font-size: .97rem; margin: 0; }
.card-ico {
  width: 42px; height: 42px; margin-bottom: 20px; border-radius: 11px;
  display: grid; place-items: center;
  background: var(--gold-bg); border: 1px solid var(--gold-br); color: var(--gold-bright);
  transition: transform .6s var(--ease-pop);
}
.card:hover .card-ico { transform: translate3d(0,-2px,0) rotate(-4deg); }
.card-ico svg { width: 21px; height: 21px; }

/* plans: a Free/Pro comparison table, so a difference (2 sets vs unlimited)
   reads as one row instead of two bullets the visitor has to line up
   themselves -- except on an actual phone, where a 3-column table has no
   room to breathe and the original two cards read better. 640px is a
   phone-only cutoff: it still shows the table on tablets. Both markups are
   always in the DOM; this media query is the only thing choosing between
   them -- see the rules after .plan below.

   The table keeps a flat, opaque fill rather than the gradient panel: its
   sticky label column has to be exactly the colour of what scrolls under it. */

.plan-compare-wrap {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  border: 1px solid transparent; border-radius: var(--r);
  /* The fill is a one-colour gradient, not the bare colour: a <color> is only
     allowed in a background's LAST layer, so `var(--card) padding-box,
     var(--panel-edge) border-box` made the whole declaration invalid at
     computed-value time and the table computed to no background at all --
     the page showed straight through it, and the rim was lost too. */
  background:
    linear-gradient(var(--card), var(--card)) padding-box,
    var(--panel-edge) border-box;
  box-shadow: 0 50px 90px -54px rgba(0,0,0,.95);
}
/* border-spacing: 0 rather than border-collapse -- Chromium mispositions a
   sticky cell's siblings when a collapsed border sits on the same column,
   which showed up as the Free/Pro cells overlapping the sticky label. */
.plan-compare { width: 100%; min-width: 560px; border-collapse: separate; border-spacing: 0; }
.plan-compare th, .plan-compare td {
  padding: 15px 20px; border-bottom: 1px solid var(--border); text-align: center;
  transition: background-color .3s var(--ease-soft);
}
.plan-compare tbody tr:last-child th, .plan-compare tbody tr:last-child td { border-bottom: none; }
.plan-compare thead th { padding-top: 24px; padding-bottom: 22px; vertical-align: bottom; }
/* The feature-label column stays put while Free/Pro scroll under it on
   narrow screens -- both the empty corner cell and the body labels need the
   sticky/background pair, or the header drifts out of step with the rows
   once you scroll right. */
.plan-compare th:first-child {
  position: sticky; left: 0; background: var(--card); z-index: 1;
}
.plan-compare tbody th {
  text-align: left; font-family: var(--ui); font-weight: 500;
  font-size: .93rem; color: var(--dim); line-height: 1.4;
}
/* Pro reads as the lit column: a warm wash down its length, and a gold rule
   across the top of its heading. */
.plan-compare .pro-col { background-color: rgba(232,201,106,.07); }
.plan-compare thead .pro-col {
  background: linear-gradient(180deg, rgba(232,201,106,.2), rgba(232,201,106,.08));
  box-shadow: inset 0 2px 0 var(--gold-bright);
  border-top-right-radius: calc(var(--r) - 1px);
}
.plan-compare tbody tr:hover td { background-color: rgba(255,255,255,.03); }
.plan-compare tbody tr:hover td.pro-col { background-color: rgba(232,201,106,.12); }
.plan-compare tbody tr:hover th { background-color: #1f251c; color: var(--text); }
.plan-col-head { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.plan-col-head .plan-name {
  font-family: var(--ed); font-size: 1.1rem; color: var(--text);
  display: flex; align-items: center; font-variation-settings: 'opsz' 24;
}
.plan-col-head .plan-price { font-family: var(--ui); font-size: .8rem; color: var(--gold-bright); }
.plan-compare .yes { color: var(--gold-bright); font-size: 1.1rem; font-weight: 700; }
.plan-compare .no { color: var(--muted); }
.plan-compare .val { color: var(--dim); font-size: .88rem; }
.plan-compare .pro-col .val { color: var(--text); }

.plans { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 22px; align-items: start; }
.plan {
  border: 1px solid transparent; border-radius: var(--r);
  background: var(--panel) padding-box, var(--panel-edge) border-box;
  padding: 34px 30px;
  box-shadow: 0 40px 80px -50px rgba(0,0,0,.95);
}
.plan.pro {
  background:
    linear-gradient(180deg, rgba(200,168,75,.16), rgba(200,168,75,0) 62%) padding-box,
    var(--panel) padding-box,
    var(--panel-hot) border-box;
}
.plan h3 { display: flex; align-items: center; }
.plan .price { font-family: var(--ed); font-size: 1.05rem; color: var(--gold-bright); margin: 12px 0 0; font-variation-settings: 'opsz' 24; }
.plan .ticks { margin-top: 22px; }
.plan .ticks li { font-size: .95rem; }

/* Which of the two plan markups shows. These have to come AFTER the base
   .plans rule: a media query adds no specificity, so a `display: none` placed
   above `.plans { display: grid }` loses to it on source order and the cards
   show on desktop alongside the table. */
@media (max-width: 640px) { .plan-compare-wrap { display: none; } }
@media (min-width: 641px) { .plans { display: none; } }

/* CTA: the last thing on the page stands in the brightest light on it. */
.cta { text-align: center; padding-block: clamp(90px, 13vw, 160px); isolation: isolate; }
.cta::before {
  content: ''; position: absolute; z-index: -1; left: 50%; top: 50%;
  width: min(1100px, 160vw); aspect-ratio: 2 / 1; transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, rgba(232,201,106,.15), rgba(143,167,95,.06) 50%, rgba(0,0,0,0) 76%);
  pointer-events: none;
}
.cta .stores { justify-content: center; margin-top: 34px; }
.cta h2 { max-width: 17ch; margin-inline: auto; font-size: clamp(2.3rem, 5.4vw, 3.6rem); }

/* ---------- footer ----------
   The map runs on under the footer and settles into shadow toward the
   bottom of the page -- a gradient rather than a fill, because a solid band
   drew a hard edge exactly where the contours stopped. The top rule is a
   hairline that fades out at both ends for the same reason. */
.foot {
  position: relative; padding-block: 70px 34px;
  background: linear-gradient(to bottom, rgba(6,10,4,0) 0%, rgba(6,10,4,.62) 38%, rgba(6,10,4,.82) 100%);
}
.foot::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px;
  background: linear-gradient(90deg, rgba(232,201,106,0), rgba(232,201,106,.3) 30%, rgba(232,201,106,.3) 70%, rgba(232,201,106,0));
}
.foot-grid { display: grid; grid-template-columns: 1.5fr repeat(3, 1fr); gap: 40px 30px; }
@media (max-width: 820px) { .foot-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 460px) { .foot-grid { grid-template-columns: 1fr; } }
/* The footer uses the white wordmark rather than the header's pilled colour
   one: ht-title-white.svg is the variant the brand draws for dark grounds, and
   a second ivory plate this far down the page would read as a button. */
.foot-lockup { display: flex; align-items: center; gap: 11px; margin-bottom: 18px; }
.foot-lockup .mark-emblem { height: 44px; width: auto; }
.foot-lockup .mark-word { height: 25px; width: auto; }
.foot h4 {
  font-family: var(--ui); font-size: .74rem; font-weight: 700; letter-spacing: .15em;
  text-transform: uppercase; color: var(--muted); margin: 0 0 15px;
}
.foot ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.foot a { color: var(--dim); text-decoration: none; font-size: .93rem; }
.foot a:hover { color: var(--gold-bright); text-decoration: underline; }
.foot .about { color: var(--muted); font-size: .9rem; max-width: 34ch; }

/* Impresszum — Ekertv. 2001. évi CVIII. §4 requires the service provider's
   name, seat, contact, registration and tax number to be directly and
   permanently available, so it lives on every page rather than behind a link. */
.imprint {
  margin-top: 46px; padding-top: 26px; border-top: 1px solid var(--border);
  color: var(--muted); font-size: .8rem; line-height: 1.75;
}
.imprint dl { display: grid; grid-template-columns: auto 1fr; gap: 2px 14px; margin: 10px 0 0; max-width: 560px; }
.imprint dt { color: rgba(220,210,185,.60); }
.imprint dd { margin: 0; }
.imprint a { color: var(--dim); }
.foot-base {
  margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--border);
  display: flex; flex-wrap: wrap; gap: 12px 24px; justify-content: space-between;
  color: var(--muted); font-size: .82rem;
}

/* ---------- misc ---------- */
.disclaim {
  margin-top: 30px; padding: 18px 22px; border-radius: 13px;
  border: 1px solid transparent;
  background: var(--panel) padding-box, linear-gradient(150deg, rgba(255,255,255,.09), rgba(255,255,255,.03)) border-box;
  color: var(--muted); font-size: .87rem; line-height: 1.6;
}
.center { text-align: center; }
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- reveal on scroll ----------
   Starts visible and is only hidden once JS confirms it can animate, so the
   page is fully readable with JS off or broken. A container rises in first;
   whatever site.js marks .rise inside it follows a beat behind, one after
   another (--i is its place in line), so a panel's heading, text and list
   arrive as a sequence rather than a block. */
.reveal { opacity: 1; }
.js .reveal {
  opacity: 0; transform: translate3d(0, 38px, 0);
  transition: opacity 1.1s var(--ease-out), transform 1.35s var(--ease-out);
}
.js .reveal.in { opacity: 1; transform: none; }
.js .reveal .rise {
  opacity: 0; transform: translate3d(0, 16px, 0);
  transition: opacity 1s var(--ease-out), transform 1.1s var(--ease-out);
  transition-delay: calc(var(--base, 0ms) + var(--i, 0) * 70ms + 160ms);
}
.js .reveal.in .rise { opacity: 1; transform: none; }

/* The mission quote rises line by line, like the hero headline, but on
   arrival rather than on load. */
.js .mission .reveal .ln > * {
  transform: translate3d(0, calc(100% + .35em), 0);
  transition: transform 1.4s var(--ease-out) .1s;
}
.js .mission .reveal .ln:nth-child(2) > * { transition-delay: .24s; }
.js .mission .reveal.in .ln > * { transform: none; }

/* Table rows fade in top to bottom once the table has arrived. Opacity only:
   transforms on table rows are still unreliable across engines. */
.js .plan-compare-wrap.reveal tbody tr {
  opacity: 0; transition: opacity .7s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 38ms + 250ms);
}
.js .plan-compare-wrap.reveal.in tbody tr { opacity: 1; }

/* ---------- scroll-linked motion ----------
   Only where the browser can tie an animation to scroll position itself
   (CSS scroll-driven animations): it runs on the compositor with no script
   in the loop, which is the difference between parallax that glides and
   parallax that stutters. Browsers without it get the page without these
   touches, not a JavaScript imitation of them. Never for anyone who has
   asked for reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    /* The photograph drifts down at a fraction of the scroll speed and the
       headline lifts away, so the hero falls behind the page as it leaves. */
    .hero-bg {
      animation: hero-drift linear both;
      animation-timeline: scroll(root); animation-range: 0 100vh;
    }
    @keyframes hero-drift { to { transform: translate3d(0, 20vh, 0); } }
    .hero-in {
      animation: hero-lift linear both;
      animation-timeline: scroll(root); animation-range: 0 85vh;
    }
    @keyframes hero-lift { to { opacity: .1; transform: translate3d(0, -7vh, 0); } }

    /* How far down the page you are, as a gold line under the bar. */
    .nav::after {
      content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
      transform-origin: 0 50%; transform: scaleX(0);
      background: linear-gradient(90deg, rgba(200,168,75,0), var(--gold) 40%, var(--gold-bright));
      animation: progress linear both; animation-timeline: scroll(root);
    }
    @keyframes progress { to { transform: scaleX(1); } }
  }
  @supports (animation-timeline: view()) {
    /* Each phone travels a little slower than its row, which is all it
       takes for it to stand off the page. */
    .feat-media .phone {
      animation: phone-drift linear both;
      animation-timeline: view(); animation-range: cover;
    }
    @keyframes phone-drift {
      from { transform: translate3d(0, 46px, 0); }
      to   { transform: translate3d(0, -46px, 0); }
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  .js .reveal, .js .reveal .rise, .js .plan-compare-wrap.reveal tbody tr { opacity: 1; transform: none; }
  .js .mission .reveal .ln > * { transform: none; }
  .phone-float { animation: none; }
}
