@charset "UTF-8";
/* =============================================================================
   PARTANUM — custom.css
   -----------------------------------------------------------------------------
   Holds only rules for parts that have no counterpart in _source, plus
   wanted deviations from it, one repair of its print rules and one of its
   mobile menu:

     1  Default page         the text column, the block headings and the
                             two block types that have no styled
                             counterpart in the source
     2  Home page slider     tile width and format that keep the uncropped
                             image and its caption the same width
     3  Contact card         two photos side by side
     4  Briefing print       repairs the broken PDF of the briefing sheet
     5  Project gallery      the image collage of the project pages stacked
                             below 62em
     6  Projects overview    two landscape tiles side by side, one per row
                             below 48em
     7  Mobile menu          locks the page without moving it
     8  SEO structure        headings instead of paragraphs on the service
                             page, its spotlight section with customer
                             quotes, the intro of the projects overview
                             and the service links of the project pages
     9  Buttons              one hover for light and dark sections

   Pages that exist in _source get no rule here, except the wanted
   deviations of the project slider (2), the contact card (3), the
   project gallery (5), the projects overview (6) and the SEO structure
   (8), the print repair of the briefing sheet (4) and the scroll lock of
   the mobile menu (7).
   The source file
   style.css is not touched; on the default page this file overrides the
   width of .prose and the size of its h2/h3.

   Every value comes from the tokens in docs/design-tokens.md — no new
   colours, fonts, spacings, radii or breakpoints. The column width of
   56.25rem (900px) is the width of .container--narrow from the source. The
   tile height in 6 is derived from the content width and the 16:10 of
   .hero__band-image, see there. The
   rules hang off classes from the source; the only classes of our own are
   .contact__images (3) and .statement--subtitle, .spotlight-section,
   .testimonials, .testimonial*, .testimonials__nav, .projects__intro and .case-services (8).

   layout.php loads this file as the LAST stylesheet.
   ============================================================================= */


/* =============================================================================
   1) DEFAULT PAGE
   The default page (Impressum, Datenschutz) uses Kirby's standard blocks.
   Paragraphs, lists, images and rules are styled by A3 (base) and A4 (.prose)
   of the source.

   Column and headings: with --measure (62ch) and the section sizes --h2/--h3
   in the wide Monument Wide, a heading held only 15–18 characters per line
   and broke after every other word. The default page therefore gets a wider
   column (56.25rem = 900px, the width of .container--narrow, instead of
   --measure), centred under the centred page title,
   and headings one step down the scale. .prose only occurs on the default
   page, so no other page is affected.
   ============================================================================= */

.prose {
  max-inline-size: 56.25rem;                             /* 900px */
  margin-inline: auto;
}

.prose h2 { font-size: var(--h4); }                     /* 20 → 26 */
.prose h3 { font-size: var(--h5); }                     /* 18 → 22 */

/* Without animation (the default) the title is not split into word spans. A
   single long word ("Datenschutz") in --h1 is wider than a phone
   screen and would break at any letter; hyphens lets it break at a syllable
   (lang="de"). Hooked on main:has(.prose) because the default page has no
   class of its own. */
main:has(.prose) .case-hero__project {
  hyphens: auto;
}


/* -----------------------------------------------------------------------------
   Blocks without a counterpart in the source

   Two block types have no counterpart in the source and would otherwise stay
   unstyled: the <iframe> of the video block would have no height, and the
   citation of the quote block would sit directly under the quote with nothing
   to set it apart. Both get the bare minimum here — values from the tokens
   only.
   ----------------------------------------------------------------------------- */

.prose figure iframe {
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: 16 / 9;
  border: 0;
}

.prose blockquote footer {
  margin-block-start: var(--space-xs);
  font-size: var(--text-s);
  color: var(--color-text-muted);
}


/* =============================================================================
   2) HOME PAGE PROJECT SLIDER
   Wanted deviation from _source2 (B14): every tile takes the format of its
   first image (--slide-ratio, set on the <li> by project-teaser.php), so the
   image shows uncropped.

   B14 caps the image box at --slide-max-height. With an aspect ratio the
   browser carries that cap over to the width: in a low window the box
   becomes narrower than the tile, while client and project type below it
   keep the full tile width and run past the image. The tile is therefore
   never wider than its image at full height; box, image and caption share
   one width. In calc() the ratio "1800 / 1200" reads as a division.
   ============================================================================= */

.project-slider .project {
  max-inline-size: calc(var(--slide-max-height) * var(--slide-ratio));
}

/* Below 48em the grid rule of B5 gives every 1st, 4th, 7th … tile 776/533
   (.project:nth-child(3n + 1) .project__media), which outranks the slider
   rule of B14. In the slider every tile keeps the format of its image. */
.project-slider .project .project__media {
  aspect-ratio: var(--slide-ratio);
}


/* =============================================================================
   3) CONTACT CARD: TWO PHOTOS
   The source has one photo (.contact__image, B7). The card takes up to two;
   two stand side by side, each at most 224px like the single one, and both
   shrink alike when the column is narrower. One photo stays the bare <img>
   of the source and gets no rule here.
   ============================================================================= */

.contact__images {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 14rem));   /* 224px each */
  gap: var(--space-s);
}


/* =============================================================================
   4) BRIEFING SHEET: PRINT
   The print rules of the source (style.css, "Druck / PDF") produce a broken
   PDF, in _source/briefing.html just the same:

   - The page title stays invisible. Its words wait in the start position of
     the load animation (transform from GSAP), which never runs for print.
   - Every block is a <fieldset> with a floated <legend>. Chrome measures such
     a fieldset wrongly once it runs across a page break: the next block
     heading overlaps the last options of the one before.
   - .field { break-inside: avoid } on the nested fieldsets leaves half a page
     empty after "Funktionen"; block headings end up alone at a page bottom.
   - A textarea keeps its screen height, so a longer answer is cut off and
     prints with a scrollbar.
   - The surface colour is printed, although the source wants no areas that
     cost toner; and the title touches the first block rule.

   For print the blocks give up their box (display: contents), so headings
   and questions run in one plain flow: the form becomes a block, since its
   grid gap would otherwise fall between every two questions. The block rule
   moves to the heading, which stays with its first question. A question
   stays in one piece, except the long list of functions. Textareas grow
   with their answer (field-sizing; browsers without it keep the screen
   height). Animated elements show their end state, the surface goes. The
   spacing is in points like the print block of the source, but roomier.
   Hooked on body:has(.briefing__form), so the print of every other page
   stays as in the source.
   ============================================================================= */

@media print {
  body:has(.briefing__form) [data-anim],
  body:has(.briefing__form) .word__inner {
    opacity: 1 !important;                              /* beats GSAP inline styles */
    transform: none !important;
    translate: none !important;
  }

  body:has(.briefing__form) .section-light {
    background: none;
  }

  body:has(.briefing__form) .case-hero {
    padding-block-end: 6pt;
  }

  /* --h1 breaks the title over two lines on the sheet; one step down keeps
     it on one line and still above the block headings (--h3). */
  body:has(.briefing__form) .case-hero__project {
    font-size: var(--h2);
  }

  body:has(.briefing__form) .briefing__form {
    display: block;
  }

  body:has(.briefing__form) .briefing-block {
    display: contents;
  }

  body:has(.briefing__form) .briefing-block__head {
    float: none;
    margin-block: 36pt 14pt;
    padding-block-start: 14pt;
    border-block-start: 1.5pt solid #000;
    break-after: avoid;
  }

  body:has(.briefing__form) .briefing-block__head + .field {
    break-before: avoid;
  }

  body:has(.briefing__form) .field {
    padding-block-start: 16pt;
    break-inside: avoid;
  }

  body:has(.briefing__form) .field + .field {
    margin-block-start: 16pt;
  }

  body:has(.briefing__form) .field__label {
    margin-block-end: 8pt;
  }

  body:has(.briefing__form) .field:has(.choice-list--stacked) {
    break-inside: auto;
  }

  body:has(.briefing__form) .choice,
  body:has(.briefing__form) .input {
    break-inside: avoid;
  }

  body:has(.briefing__form) .input--area {
    field-sizing: content;
    overflow: visible;
    resize: none;
  }
}


/* =============================================================================
   5) PROJECT PAGES: IMAGE COLLAGE STACKED
   The staggered grid of the source (B11, .case-gallery) holds in every window
   size and only shrinks; on a phone the images end up 86–217px wide. Below
   62em – the breakpoint where the source already moves the text below the
   images – all of them stand in one column at the full content width, in
   DOM order A, B, C, text. Formats and cropping stay as in the source.
   Desktop and tablet landscape keep the grid. ".case-gallery …" has the
   same specificity as the mirrored rules and wins as the later file.
   ============================================================================= */

@media (width < 62em) {
  .case-gallery,
  .case-gallery--mirrored {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--space-m);
  }

  .case-gallery .case-gallery__item,
  .case-gallery .case-note {
    grid-column: 1;
    grid-row: auto;
    margin-inline-start: 0;
  }

  /* 100%, not auto: a grid item with an aspect ratio does not stretch. */
  .case-gallery .case-gallery__item {
    inline-size: 100%;
    margin-block-start: 0;
  }
}


/* =============================================================================
   6) PROJECTS OVERVIEW: LANDSCAPE TILES
   The source grid (B5) splits the eight columns 3/5 and gives every tile the
   height --project-height. Wanted instead: two tiles of equal width side by
   side, each in landscape format, and one tile per row below 48em.

   Height: a half tile is (content - gap) / 2, at most (1440 - 60) / 2 =
   690px; 16:10 makes that 431px = 26.9375rem. 25.4vw keeps the box between
   1.58 and 1.65 from 768 to 1680px. 16:10 is the format of .hero__band-image
   and lies in the middle of the uploaded screenshots (1.5-1.65).
   Below 48em B5 sets the shape by position (431/533 and 776/533); here every
   tile is full width at 16:10. ".project-list .project .project__media" has
   the specificity of ".project:nth-child(3n + 1) .project__media" and wins as
   the later file. The slider is no .project-list and stays untouched.
   ============================================================================= */

.project-list {
  --project-height: clamp(12rem, 25.4vw, 26.9375rem);   /* ≈ 16:10 at half width */
}

@media (width >= 48em) {
  .project--narrow,
  .project--wide {
    grid-column: span 4;
  }
}

@media (width < 48em) {
  .project-list {
    grid-template-columns: minmax(0, 1fr);
  }

  .project-list .project .project__media {
    aspect-ratio: 16 / 10;
  }
}


/* =============================================================================
   7) MOBILE MENU: SCROLL LOCK
   Below 62em B2 locks the page behind the open menu by fixing the body at
   -scrollY and switching the header from sticky to fixed. Both happen in the
   frame the menu starts to slide down: the header leaves the flow and the
   page below moves up by its height, the document shrinks to the window,
   scrollY drops to 0 and the scroll event makes ScrollTrigger recalculate
   during the animation (and again when navigation.js scrolls back on close).
   overflow: hidden on html, which B2 sets as well, is enough on its own: it
   locks the page and keeps the scroll position. So the body stays in place
   and the header stays sticky. navigation.js is unchanged; its body.style.top
   has no effect on a static body and its scrollTo lands where the page
   already is. Same specificity as B2, this file wins as the later one.
   ============================================================================= */

@media (width < 62em) {
  .is-menu-open body {
    position: static;
    overflow: visible;      /* else body becomes the scroller and the sticky
                               header sticks to it instead of the window */
  }

  .is-menu-open .site-header {
    position: sticky;
  }

  /* The old lock fired a scroll event, and navigation.js then showed a
     hidden header. Without it a header that had slid away (toggle reached
     by keyboard) would stay away, close button included. */
  .is-menu-open .site-header.is-hidden {
    translate: none;
  }

  /* Narrow desktop windows: no width jump when the scrollbar disappears. */
  .is-menu-open {
    scrollbar-gutter: stable;
  }
}


/* =============================================================================
   8) SEO STRUCTURE
   Wanted deviation from the SEO audit: a clear heading structure and more
   text on the service pages, the projects overview and the project pages.

   Headings instead of paragraphs: the statement and the label over the
   skills list become <h2>, the step label <h3>. The base rule for h1–h6 (A3,
   in @layer) would switch them to the heading font, its tracking and the
   primary colour; here they take those from their parent again, as the <p>
   of the source did. Their own rules (B) are unlayered and stay untouched,
   so they look exactly as before.
   ============================================================================= */

.statement-section h2.statement,
.statement--subtitle {
  font-family: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  color: inherit;
}

/* The second sentence of the statement, formerly in the same paragraph:
   directly below the first, without the gap .statement keeps above a
   second element. */
.statement--subtitle {
  margin-block-start: 0;
}

.approach__skills > h2.eyebrow {
  font-weight: inherit;
  letter-spacing: inherit;
  text-wrap: pretty;
}

h3.step__marker {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  text-wrap: pretty;
}

/* Spotlight section of the service page ("Im Fokus"): section head and
   text column of the approach section above. Not .region – that class
   belongs to "Über uns" (B12) and would bring its padding along. The edge
   rule of the head runs out to the window border; clipped like in
   .projects (B5). */
.spotlight-section {
  overflow: clip;
}

/* Intro of the projects overview, between title and grid: .case-prose of
   the project pages, across the full content width instead of --measure,
   with the gap of a section head below it. */
.projects__intro {
  max-inline-size: none;
  margin-block-end: var(--space-l);
}

/* Links from a project to its service pages, under the result text, flush
   with it (the base rule for lists, A3, indents by 1.5rem). */
.case-services {
  display: grid;
  justify-items: start;
  gap: var(--space-xs);
  margin-block-start: var(--space-m);
  padding-inline-start: 0;
}

/* Customer quotes in the right column of the spotlight section, set like the
   statement (body font, primary colour), one step smaller. Without
   testimonials.js – or with reduced motion – they stand one below the
   other. With it (.is-rotating) all of them share one grid cell, so the box
   keeps the height of the longest quote, and only the active one is shown.
   The change is staggered: the old quote fades out and drifts up a little,
   the new one follows half a beat later from slightly below, so the two
   texts never sit on top of each other at half strength. */
@media (width >= 62em) {
  .testimonials {
    grid-column: 2;
  }
}

.testimonials__list {
  display: grid;
  row-gap: var(--space-l);
}

.testimonial {
  margin: 0;
}

.testimonial__quote {
  padding: 0;
  border: 0;
  font-size: var(--text-xl);
  line-height: 1.4;
  color: var(--color-primary);
}

.testimonial__quote p::before { content: "„"; }
.testimonial__quote p::after  { content: "“"; }

.testimonial__source {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin-block-start: var(--space-s);
  font-size: var(--text-l);
}

/* Optional small portrait or logo left of the name, round like the arrows. */
.testimonial__image {
  flex: 0 0 auto;
  inline-size: 3rem;                                    /* 48px */
  block-size: 3rem;
  object-fit: cover;
  border-radius: 50%;
}

.testimonial__person {
  display: grid;
}

.testimonial__role {
  font-size: var(--text-s);
  color: var(--color-text-muted);
}

.testimonials__list.is-rotating > .testimonial {
  grid-area: 1 / 1;
  opacity: 0;
  translate: 0 0.5rem;
  visibility: hidden;
  transition:
    opacity 900ms cubic-bezier(0.4, 0, 0.2, 1),
    translate 900ms cubic-bezier(0.4, 0, 0.2, 1),
    visibility 0s linear 900ms;
}

.testimonials__list.is-rotating > .testimonial.is-active {
  opacity: 1;
  translate: 0 0;
  visibility: visible;
  transition:
    opacity 1100ms cubic-bezier(0.4, 0, 0.2, 1) 400ms,
    translate 1100ms cubic-bezier(0.4, 0, 0.2, 1) 400ms,
    visibility 0s;
}

/* Waiting quotes sit slightly below, so the next one rises into place; the
   one that just left (.is-leaving, set by testimonials.js for the length of
   its fade) drifts up instead. */
.testimonials__list.is-rotating > .testimonial.is-leaving {
  translate: 0 -0.5rem;
}

/* Back and forward under the quotes: the round arrows of the project
   slider (.slider-arrow, B14), left-aligned in the column. Hidden until
   testimonials.js runs; [hidden] needs its own rule because the reset that
   hides it lies in @layer and loses against display: flex (as in B14). */
.testimonials__nav {
  display: flex;
  gap: var(--space-xs);
  margin-block-start: var(--space-m);
}

.testimonials__nav[hidden] {
  display: none;
}


/* =============================================================================
   9) BUTTONS: ONE HOVER FOR ALL SECTIONS
   Wanted deviation: the source turns a button on a light section dark red
   with white text on hover (--button-hover-* in A2), but light orange with
   dark text on a dark one (.section--dark, B1) – the contact card on every
   page. Wanted is the latter everywhere. The tokens are set again outside
   @layer, so they win over A2; .section--dark already sets the same values.
   The hairline of .section-light .button (--accent-dark) stays and keeps the
   edge visible against the light ground in both states (WCAG 1.4.11).
   .button--quiet has its own hover and is not affected.
   ============================================================================= */

:root {
  --button-hover-bg: var(--accent-light);
  --button-hover-text: var(--color-on-accent);
}

