/* Ragnity — LANDING SURFACE — docs/design-system.md */

/* min-height, never height: a screen is the floor, not the ceiling.  [docs/design-system.md] */

.hero {
  /* Inset and radius belong together: the radius has to be clearly larger than
     the inset, or the rounding reads as imprecision instead of as a shape. */
  --hero-inset: clamp(var(--space-2), 0.7vw, var(--space-4));
  --hero-radius: clamp(var(--space-5), 1.7vw, var(--space-7));
  /* The buffer that replaces the retired meta strip. — docs/design-system.md */
  /* Desktop must never scroll, and it did: at 1440 wide the page overflowed at EVERY window under 800px tall.  [docs/design-system.md] */
  --hero-endgap: clamp(var(--space-5), 5.5vh, var(--space-8));

  padding: var(--hero-inset);
}

.hero__box {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* flex-start, not space-between: the slack must NOT be spread between the
     rows. Section 4 decides where it goes. */
  justify-content: flex-start;
  min-height: calc(100svh - 2 * var(--hero-inset));
  border-radius: var(--hero-radius);
  background-color: var(--color-ink);
  color: var(--color-text-on-ink);

  /* Vertical spacing keys off the height of the screen, not its width. — docs/design-system.md */
  gap: clamp(var(--space-5), 4vh, var(--space-7));
  padding-block: clamp(var(--space-5), 4vh, var(--space-9));

  /* Horizontally one step narrower than a section box, and that is not… — docs/design-system.md */
  padding-inline: clamp(var(--space-5), 4vw, var(--space-9));
}

.hero__box > * {
  position: relative;
  z-index: 1;
}

/* The page column — docs/design-system.md */
.hero__box > *:not(.hero__media) {
  width: 100%;
  max-width: calc(var(--container-max) - 2 * var(--gutter));
  margin-inline: auto;
}

/* 2. THE PICTURE — docs/design-system.md */

/* Under the content. .hero__box > * puts z-index 1 on every child, so this
   needs the same specificity and a later position in the stylesheet. */
.hero__box > .hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Its own stacking context, so the texture blends with the photo, the ink
     and the scrim, and with nothing that happens to lie further down. */
  isolation: isolate;
}

.hero__media > * {
  position: absolute;
  inset: 0;
}

/* A training room: speaker in front of a seated audience. — docs/design-system.md */
.hero__media-img {
  background-image: url("/assets/hero-1400.webp");
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  /* Desaturated plus a touch of contrast: a photo that was using colour for — docs/design-system.md */
  filter: grayscale(1) contrast(1.08);
}

@media (min-width: 64rem) {
  .hero__media-img {
    background-image: url("/assets/hero-2400.webp");
  }
}

.hero__media-ink {
  background-color: var(--color-ink);
  /* 0.9 until 2026-08-10, which was the value chosen in… — docs/design-system.md */
  opacity: 0.92;
}

/* A scrim, not a decorative gradient: it lies where type lies and makes… — docs/design-system.md */
.hero__media-scrim {
  background-image: linear-gradient(
    to top,
    var(--color-ink) 0%,
    color-mix(in srgb, var(--color-ink) 55%, transparent) 26%,
    transparent 52%
  );
}

/* Fine lines rather than noise — something printed rather than something — docs/design-system.md */
.hero__grain {
  background-image: repeating-linear-gradient(
    to bottom,
    color-mix(in srgb, var(--color-paper) 55%, transparent) 0 1px,
    transparent 1px 3px
  );
  opacity: 0.16;
  mix-blend-mode: overlay;
}

/* 3. THE BAR — brand left, language right, inside the box — docs/design-system.md */

.hero__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
}

/* The mark is drawn with currentColor, so on ink it only needs the colour. */
.hero__brand {
  color: var(--color-text-on-ink);
}

/* .lang-switch is built for paper in core.css and needs three moves onto… — docs/design-system.md */
.hero__bar .lang-switch {
  display: flex;
}

.hero__bar .lang-switch__item {
  color: var(--color-text-on-ink-muted);
}

.hero__bar .lang-switch__item:hover,
.hero__bar .lang-switch__item[aria-current="true"] {
  color: var(--color-text-on-ink);
}

.hero__bar .lang-switch__sep {
  color: var(--color-hairline-ink);
}

/* 4. THE CONTENT — docs/design-system.md */

.hero__eyebrow {
  margin-top: auto;
}

/* The location column, which becomes the footer — docs/design-system.md */

.hero__top {
  display: contents;
}

.hero__aside {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  order: 9;
  /* No text-align on a phone: the column keeps the left edge that the… — docs/design-system.md */
}

/* The price of display: contents, and it is easy to miss: it dissolves the BOX of .hero__top but not its place in the DOM, so `.hero__box > *` above still matches .hero__top and never reaches its children.  [docs/design-system.md] */
.hero__eyebrow,
.hero__aside {
  position: relative;
  z-index: 1;
}

/* The affordance is the trailing arrow instead, which is a NON-COLOUR cue and therefore still satisfies "don't signal with colour alone" — the thing an underline was there to do.  [docs/design-system.md] */
.hero__meta {
  display: flex;
  flex-direction: column;
  /* Stretch, so the target is the width of the column and not the width of… — docs/design-system.md */
  align-items: stretch;
  gap: var(--space-1);
  /* Balances the block against the copyright below it. — docs/design-system.md */
  margin-top: var(--space-4);
  list-style: none;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-normal);
  color: var(--color-text-on-ink-muted);
}

.hero__meta a {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  /* The tap target. — docs/design-system.md */
  padding-block: var(--space-3);
  transition: color var(--duration-base) var(--ease);
}

.hero__meta a:hover {
  color: var(--color-text-on-ink);
}

/* The same nudge as the CTA's arrow, on the same two tokens on purpose… — docs/design-system.md */
.hero__meta-arrow {
  display: inline-block;
  transition: transform var(--duration-base) var(--ease);
}

.hero__meta a:hover .hero__meta-arrow {
  transform: translate(0.15em, -0.15em);
}

/* Same size and colour as the row above, and told apart by not being a… — docs/design-system.md */
.hero__copyright {
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  line-height: var(--leading-snug);
  /* Sets it off from the last link rather than letting it read as a fifth… — docs/design-system.md */
  margin-top: var(--space-4);
  color: var(--color-text-on-ink-muted);
}

/* Why the side-by-side arrangement needs a LANDSCAPE frame, not a wide one — docs/design-system.md */
@media (min-width: 48rem) and (min-aspect-ratio: 1/1) {
  .hero__meta,
  .hero__copyright {
    line-height: var(--leading-relaxed);
  }

  .hero__meta {
    align-items: flex-end;
    gap: var(--space-2);
  }

  /* The tap padding comes back off where a mouse is doing the aiming: it — docs/design-system.md */
  .hero__meta a {
    display: inline-flex;
    padding-block: 0;
  }
}

/* Why the column leaves the flow at 64rem — docs/design-system.md */
@media (min-width: 64rem) and (min-aspect-ratio: 1/1) {
  .hero__top {
    position: relative;
    /* Raising the ASIDE would do nothing, and that is the whole trap: — docs/design-system.md */
    z-index: 2;
  }

  .hero__aside {
    position: absolute;
    right: 0;
    /* Level with the eyebrow, which is where the location line sat before the
       footer existed. */
    top: 0;
  }

  /* The gap between the location line and the links, not a shift of the… — docs/design-system.md */
  .hero__meta {
    margin-top: var(--space-6);   /* 2rem */
  }
}

/* This is the one that dissolves display: contents and puts the two back on a row, so a portrait frame never reaches it.  [docs/design-system.md] */
@media (min-width: 48rem) and (min-aspect-ratio: 1/1) {
  .hero__top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-4);
    /* The auto margin moves with the flex child. Narrow OR portrait that is the
       eyebrow itself, wide and landscape it is .hero__top again. */
    margin-top: auto;
  }

  .hero__eyebrow {
    margin-top: 0;
  }

  .hero__aside {
    order: 0;
    align-items: flex-end;
    text-align: right;
  }
}

/* -- Headline block -------------------------------------------------------- */

.hero__main {
  display: flex;
  flex-direction: column;
  /* Height-fluid, and capped at --space-6 so it is IDENTICAL on any window — docs/design-system.md */
  gap: clamp(var(--space-4), 3.6vh, var(--space-6));
  /* The buffer that used to be the meta strip. Without something down here the
     block would sit on the bottom edge rather than above it. */
  margin-bottom: var(--hero-endgap);
}

/* The same buffer above the bar, so the brand sits as far from the top… — docs/design-system.md */
@media (min-width: 48rem) and (min-aspect-ratio: 1/1) {
  .hero__bar {
    margin-top: var(--hero-endgap);
  }
}

/* 16ch, the measure the headline has always had. — docs/design-system.md */
.hero__title {
  max-width: 16ch;
  /* A second ceiling, out of the height of the screen. — docs/design-system.md */
  /* 11svh -> 10 -> 9 -> 10 over 2026-08-10. — docs/design-system.md */
  font-size: min(var(--text-display), 10svh);
  /* Line height changes the height, never the break points, so the measured wrapping above is unaffected.  [docs/design-system.md] */
  line-height: var(--leading-snug);
}

@media (min-width: 48rem) {
  .hero__title {
    line-height: var(--leading-tight);
  }
}

/* The hero lead keeps the larger size the section leads stepped down from… — docs/design-system.md */
.hero .lead {
  max-width: var(--container-text);
  font-size: var(--text-lead);
  line-height: var(--leading-relaxed);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

/* The primary button and its microcopy, stacked so the note stays attached to
   the button rather than to the row. */
.hero__cta {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* Spacing lives on the note, not as a gap on the column, so these two values
   are the whole offset from the button. */
.hero__note {
  padding-left: var(--space-4);   /* 1rem   */
  padding-top: var(--space-5);    /* 1.5rem */
  /* Inherits --leading-normal from body otherwise. This only shows on a phone,
     where the line wraps — on a desktop it is one line and the leading has
     nothing to open up. */
  line-height: var(--leading-relaxed);
  /* One line, not two, on a phone. — docs/design-system.md */
  /* The line broke as "30 MINUTEN · KOSTENLOS ·" / "UNVERBINDLICH", which — docs/design-system.md */
  text-wrap: balance;
}

/* Progressive enhancement: with no JS this renders as the first phrase, in flow, and never moves.  [docs/design-system.md] */

.rotator {
  display: block;
  color: var(--color-lavender);
}

.rotator__item + .rotator__item {
  display: none;
}

/* Once live, phrases stack. JS pins min-height to the tallest phrase first, so
   swapping never reflows anything below the headline. */
.rotator.is-live {
  position: relative;
}

.rotator.is-live .rotator__item {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  opacity: 0;
}

/* The active phrase returns to flow, so the block still has height even if the
   measured min-height is ever wrong. */
.rotator.is-live .rotator__item.is-active,
.rotator.is-live .rotator__item.is-leaving {
  opacity: 1;
}

.rotator.is-live .rotator__item.is-active {
  position: relative;
}

/* Word stagger. Each word carries its own delay via --word, set by the… — docs/design-system.md */
.rotator.is-live .rotator__word {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.5em);
  transition-delay: calc(var(--word, 0) * 45ms);
}

.rotator.is-live .is-active .rotator__word {
  opacity: 1;
  transform: translateY(0);
  transition-property: opacity, transform;
  transition-duration: 300ms, 380ms;
  transition-timing-function: var(--ease);
}

/* Outgoing words leave upward and faster than they arrived, so the incoming
   phrase is never waiting on them. */
.rotator.is-live .is-leaving .rotator__word {
  opacity: 0;
  transform: translateY(-0.35em);
  transition-property: opacity, transform;
  transition-duration: 200ms;
  transition-timing-function: var(--ease);
}

/* It is generated content rather than markup because hero-rotator.js rebuilds textContent when it splits a phrase into word spans — a <span> would not survive that, and generated content is not in textContent, so the script never touches it.  [docs/design-system.md] */
.rotator.is-live .rotator__word:last-child::after {
  content: ".";
  color: var(--color-text-on-ink);
}

/* With no script there are no word spans — and only ever one visible sentence
   to hang it on. */
.rotator:not(.is-live) .rotator__item::after {
  content: ".";
  color: var(--color-text-on-ink);
}
