/* ---------- tokens ---------- */
:root {
    --bg: #0a0a0f;
    --indigo: #6366f1;
    --violet: #8b5cf6;
    --teal: #14b8a6;

    --text-primary: #f5f5f7;
    --text-secondary: #94a3b8;

    /* score bands + rating gold */
    --good: #14b8a6;
    --ok: #eab308;
    --bad: #f87171;
    --gold: #fbbf24;

    --glass-bg: rgba(255, 255, 255, 0.05);
    --glass-border: rgba(255, 255, 255, 0.10);
    --blur-amt: 16px;

    --radius-lg: 20px;
    --radius-md: 12px;
    /* Card inner padding - the buy banner breaks out of it (negative margins) to run edge to
       edge, so the two must stay one value. */
    --card-pad: 1.25rem;
    --ease: cubic-bezier(.22, 1, .36, 1);
    --dur: 240ms;
}

/* ---------- base ---------- */
* {
    box-sizing: border-box;
}

/* Author `display` rules (e.g. .btn-secondary) otherwise beat the UA's [hidden] rule. */
[hidden] {
    display: none !important;
}

/* Keyboard focus must be visible on every control; the search input keeps its own
   border/glow treatment instead (see .search-input:focus). */
:is(a, button):focus-visible {
    outline: 2px solid color-mix(in srgb, var(--indigo) 80%, #fff);
    outline-offset: 2px;
}

body {
    margin: 0;
    min-height: 100vh;
    background: var(--bg);
    color: var(--text-primary);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, sans-serif;
    position: relative;
}

/* ---------- background glow ---------- */
.glow-bg {
    position: fixed;
    inset: -10%;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(circle at 15% 20%, color-mix(in srgb, var(--indigo) 40%, transparent) 0%, transparent 55%),
        radial-gradient(circle at 85% 15%, color-mix(in srgb, var(--violet) 35%, transparent) 0%, transparent 55%),
        radial-gradient(circle at 50% 100%, color-mix(in srgb, var(--teal) 30%, transparent) 0%, transparent 60%);
    filter: blur(80px);
}

/* While the agent works, the glow slowly breathes and drifts hue - ambient "the machine is
   alive" motion that adds stimulation without demanding attention. */
body.is-searching .glow-bg {
    animation: glow-drift 9s ease-in-out infinite alternate;
}

@keyframes glow-drift {
    from { transform: scale(1); filter: blur(80px) hue-rotate(0deg); }
    to { transform: scale(1.06) translateY(-1.5%); filter: blur(80px) hue-rotate(30deg); }
}

/* ---------- worker fairies (search-time entertainment) ---------- */
/* Tiny glowing helpers that flit between "work stations" around the screen while the agent
   searches. Each flight path plateaus at a few spots where the fairy hovers (the core keeps
   bobbing) and sprinkles fairy dust - so they read as busy little coworkers, not random
   particles. Same z-plane as .glow-bg: behind all content, shining through the glass cards.
   Lifecycle (driven by js/fairies.js, not body.is-searching): `fairies--active` = spawn from
   the screen center (every path starts there) and fly the loops; `fairies--exit` = the layer
   stays visible while the fly-away animation (WAAPI, from each fairy's live position) runs. */
.fairies {
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    opacity: 0;
    transition: opacity 800ms var(--ease);
}

.fairies--active,
.fairies--exit {
    opacity: 1;
}

.fairy {
    position: absolute;
    top: 0;
    left: 0;
    will-change: transform;
}

/* Falling fairy dust - the "I'm working on this" sparkle dropped while hovering. */
.fairy::after {
    content: "";
    position: absolute;
    left: 2px;
    top: 8px;
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--fairy-color) 60%, #fff);
    opacity: 0;
    animation: fairy-dust 1.7s ease-in infinite;
}

/* The fairy itself: a glowing core... */
.fairy__core {
    position: relative;
    display: block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--fairy-color) 35%, #fff);
    animation:
        fairy-bob 2.1s ease-in-out infinite,
        fairy-twinkle 2.8s ease-in-out infinite;
}

/* ...with two fluttering wings. */
.fairy__core::before,
.fairy__core::after {
    content: "";
    position: absolute;
    top: -6px;
    width: 9px;
    height: 14px;
    background: color-mix(in srgb, var(--fairy-color) 45%, rgba(255, 255, 255, 0.5));
    opacity: 0.75;
}

.fairy__core::before {
    left: -8px;
    border-radius: 90% 10% 60% 40%;
    transform-origin: right center;
    animation: flutter-l 200ms ease-in-out infinite alternate;
}

.fairy__core::after {
    right: -8px;
    border-radius: 10% 90% 40% 60%;
    transform-origin: left center;
    animation: flutter-r 200ms ease-in-out infinite alternate;
}

/* Colors live outside --active so a fairy keeps its glow while flying away on exit. */
.fairy--1 { --fairy-color: var(--indigo); }
.fairy--2 { --fairy-color: var(--teal); }
.fairy--3 { --fairy-color: var(--violet); }
.fairy--4 { --fairy-color: var(--gold); }

.fairy--3 .fairy__core { scale: 0.8; }
.fairy--4 .fairy__core { scale: 0.9; }

/* Flight loops only exist while active, so (re)adding the class restarts every path at its
   0% frame - the screen center - which IS the spawn animation. Staggered positive delays +
   `both` fill: latecomers hold at the center, then the swarm bursts out one by one. Different
   durations keep them permanently out of sync. */
.fairies--active .fairy--1 { animation: fairy-path-1 26s ease-in-out infinite both; }
.fairies--active .fairy--2 { animation: fairy-path-2 32s ease-in-out 0.4s infinite both; }
.fairies--active .fairy--3 { animation: fairy-path-3 23s ease-in-out 0.8s infinite both; }
.fairies--active .fairy--4 { animation: fairy-path-4 37s ease-in-out 1.2s infinite both; }

/* All fairy animations idle while hidden - no compositor work on a page that isn't searching.
   Wings/bob/dust keep running during --exit so the fairies visibly flap while leaving.
   Must come AFTER every `animation:` shorthand above: the shorthand resets play-state to
   `running`, and at equal specificity the later declaration wins. */
.fairy,
.fairy::after,
.fairy__core,
.fairy__core::before,
.fairy__core::after {
    animation-play-state: paused;
}

:is(.fairies--active, .fairies--exit) .fairy,
:is(.fairies--active, .fairies--exit) .fairy::after,
:is(.fairies--active, .fairies--exit) .fairy__core,
:is(.fairies--active, .fairies--exit) .fairy__core::before,
:is(.fairies--active, .fairies--exit) .fairy__core::after {
    animation-play-state: running;
}

@keyframes flutter-l {
    from { transform: rotate(-24deg) scaleX(0.6); }
    to { transform: rotate(10deg) scaleX(1); }
}

@keyframes flutter-r {
    from { transform: rotate(24deg) scaleX(0.6); }
    to { transform: rotate(-10deg) scaleX(1); }
}

@keyframes fairy-bob {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-5px); }
}

@keyframes fairy-twinkle {
    0%, 100% {
        box-shadow:
            0 0 8px 2px color-mix(in srgb, var(--fairy-color) 75%, transparent),
            0 0 20px 7px color-mix(in srgb, var(--fairy-color) 35%, transparent);
    }
    50% {
        box-shadow:
            0 0 12px 3px color-mix(in srgb, var(--fairy-color) 95%, transparent),
            0 0 34px 12px color-mix(in srgb, var(--fairy-color) 50%, transparent);
    }
}

@keyframes fairy-dust {
    0%, 55% { opacity: 0; transform: translateY(0) scale(1); }
    62% { opacity: 0.9; }
    100% { opacity: 0; transform: translateY(24px) scale(0.3); }
}

/* Flight routes: spawn at the screen center (scaling up from a spark) -> fly -> plateau
   (hover & work) -> fly on -> dive back into the center. Pairs of nearly identical adjacent
   keyframes are the work stops; the scale(1) frame near 100% keeps the shrink to a short
   "dive into the portal" instead of a slow fade over the whole return leg. Positions in
   vw/vh so every screen size gets full coverage. */
@keyframes fairy-path-1 {
    0% { transform: translate(50vw, 50vh) scale(0.2); }
    10% { transform: translate(14vw, 26vh) scale(1); }
    22% { transform: translate(15vw, 28vh); }
    34% { transform: translate(42vw, 10vh); }
    45% { transform: translate(41vw, 12vh); }
    60% { transform: translate(74vw, 34vh); }
    72% { transform: translate(75vw, 32vh); }
    86% { transform: translate(38vw, 70vh); }
    96% { transform: translate(52vw, 53vh) scale(1); }
    100% { transform: translate(50vw, 50vh) scale(0.2); }
}

@keyframes fairy-path-2 {
    0% { transform: translate(50vw, 50vh) scale(0.2); }
    12% { transform: translate(78vw, 60vh) scale(1); }
    24% { transform: translate(77vw, 58vh); }
    38% { transform: translate(50vw, 82vh); }
    50% { transform: translate(52vw, 80vh); }
    64% { transform: translate(18vw, 62vh); }
    76% { transform: translate(17vw, 64vh); }
    90% { transform: translate(60vw, 30vh); }
    96% { transform: translate(48vw, 47vh) scale(1); }
    100% { transform: translate(50vw, 50vh) scale(0.2); }
}

@keyframes fairy-path-3 {
    0% { transform: translate(50vw, 50vh) scale(0.2); }
    14% { transform: translate(24vw, 40vh) scale(1); }
    26% { transform: translate(25vw, 42vh); }
    42% { transform: translate(8vw, 78vh); }
    54% { transform: translate(9vw, 76vh); }
    70% { transform: translate(58vw, 22vh); }
    82% { transform: translate(57vw, 24vh); }
    96% { transform: translate(52vw, 48vh) scale(1); }
    100% { transform: translate(50vw, 50vh) scale(0.2); }
}

@keyframes fairy-path-4 {
    0% { transform: translate(50vw, 50vh) scale(0.2); }
    12% { transform: translate(86vw, 14vh) scale(1); }
    26% { transform: translate(87vw, 16vh); }
    40% { transform: translate(62vw, 8vh); }
    52% { transform: translate(63vw, 10vh); }
    66% { transform: translate(30vw, 88vh); }
    80% { transform: translate(31vw, 86vh); }
    96% { transform: translate(48vw, 52vh) scale(1); }
    100% { transform: translate(50vw, 50vh) scale(0.2); }
}

/* ---------- warp starfield (search-time speed effect) ---------- */
/* "Traveling through space" alongside the fairies: thin star streaks shoot from the screen
   center outward, accelerating and stretching as they go - the page itself feels like it's at
   warp while the agent races through the internet. js/starfield.js builds the stars (randomized
   bearing/timing per star, which static CSS can't do) and drives the lifecycle classes; CSS owns
   the visuals. Same z-plane as .glow-bg/.fairies: behind all content. */
.starfield {
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    overflow: hidden;
    opacity: 0;
    transition: opacity 800ms var(--ease);
}

.starfield--active {
    opacity: 1;
}

/* Each star is a streak pointing away from the screen center: positioned at the center,
   rotated to its bearing (origin at its inner end), then pushed outward along that ray.
   scaleX stretches the streak as it accelerates - the classic warp-line look.
   The layer's --speed (duration multiplier, smaller = faster) and --boost (brightness) come
   from starfield.js: the warp starts soft and shifts up a gear on every pipeline stage. */
.star {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 34px;
    height: 2px;
    border-radius: 999px;
    background: linear-gradient(90deg, transparent, var(--star-color, #fff));
    transform-origin: 0 50%;
    will-change: transform, opacity;
    opacity: 0;
    animation: star-warp calc(var(--dur, 2.4s) * var(--speed, 1)) cubic-bezier(0.55, 0, 0.85, 0.45) var(--delay, 0s) infinite;
    animation-play-state: paused;
}

/* Stars above the current warp level's count sit out until their level is reached. */
.star--idle {
    display: none;
}

/* Streaks keep flying during --exit so the fade-out doesn't freeze them mid-flight. */
:is(.starfield--active, .starfield--exit) .star {
    animation-play-state: running;
}

@keyframes star-warp {
    0% { transform: rotate(var(--angle)) translateX(3vmax) scaleX(0.25); opacity: 0; }
    15% { opacity: calc(var(--star-opacity, 0.6) * var(--boost, 1)); }
    100% { transform: rotate(var(--angle)) translateX(80vmax) scaleX(2.6); opacity: 0; }
}

/* ---------- layout ---------- */
.page {
    max-width: 960px;
    margin: 0 auto;
    padding: 4rem 1.5rem 1.5rem;
    /* Flex column at full viewport height so the disclosure footer can sit at the very bottom
       of the screen (margin-top: auto) even when there is little content. */
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    transition: padding-top 600ms var(--ease);
}

.page__header {
    text-align: center;
    margin-bottom: 2.5rem;
    transition: margin-bottom 600ms var(--ease);
}

/* The pitch itself: it must be readable from across the room - brighter and larger than any
   supporting copy, second only to the search box in visual weight. */
.page__tagline {
    color: color-mix(in srgb, var(--text-primary) 88%, transparent);
    font-size: 1.3rem;
    line-height: 1.4;
    margin: 0.75rem 0 0.5rem;
}

/* The one-sentence value proposition under the tagline. Clearly secondary: smaller and more
   muted than the tagline, wide enough for a single line on desktop, and compact enough not to
   push the search box below the fold. */
.page__usp {
    max-width: 720px;
    margin: 0 auto 1.75rem;
    font-size: 0.88rem;
    line-height: 1.5;
    color: var(--text-secondary);
}

/* Launch mode (body.is-compact, any non-idle phase): the header folds away and the search box -
   still carrying the query - glides to the top of the screen, as if the search term launched
   into the starfield. Everything animates via transitions on flow properties, so the box rises
   in one continuous motion as the content above it compresses; returning to idle plays it all
   in reverse. The taglines collapse via max-height (generous open values; overflow stays
   hidden), their text fading out faster than the space closes so nothing gets visibly squashed.
   Scoped to body.home (index.html): result.html reuses .page__tagline for the result query,
   which may wrap arbitrarily and must never be height-clipped. */
body.home .page__tagline,
body.home .page__usp {
    max-height: 5rem;
    overflow: hidden;
    transition: max-height 600ms var(--ease), margin 600ms var(--ease), opacity 250ms var(--ease);
}

body.home .page__usp {
    max-height: 5rem;
}

body.is-compact .page {
    padding-top: 1.1rem;
}

body.is-compact .page__header {
    margin-bottom: 1.6rem;
}

body.is-compact .gradient-text {
    font-size: 1.15rem;
    /* The collapsed taglines no longer separate wordmark and search box - keep air between them. */
    margin-bottom: 1.15rem;
}

/* The header wordmark is always the way to a new search: a plain link home on the result page,
   an in-page reset on index (app.js owns that behavior and the tooltip) - the hover makes it
   read as clickable everywhere. Scoped to the header: the geo-block wordmark is no control. */
.page__header .gradient-text:hover {
    filter: brightness(1.25);
}

body.is-compact .page__tagline,
body.is-compact .page__usp {
    max-height: 0;
    margin-top: 0;
    margin-bottom: 0;
    opacity: 0;
}

/* ---------- gradient text (wordmark) ---------- */
/* Brand: "daskaufen.com" with the "das" carrying the weight - <b>das</b> is extra bold, the
   rest of the wordmark stays light so the emphasis reads instantly. Sized to stay clearly
   below the search box in visual weight: the box is the conversion point and must win the
   first eye fixation, not the logo. */
.gradient-text {
    font-size: 2.2rem;
    font-weight: 400;
    margin: 0;
    background: linear-gradient(90deg, var(--indigo), var(--teal));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* Shrinks smoothly into launch mode (body.is-compact) and back. */
    transition: font-size 600ms var(--ease);
}

.gradient-text b {
    font-weight: 800;
}

/* The wordmark's home link must not repaint the gradient text: inheriting the transparent
   color lets the parent's background-clip keep showing through the anchor's text. */
.gradient-text a {
    color: inherit;
    text-decoration: none;
}

/* ---------- search form ---------- */
/* The search box is the product's single conversion point and must be the visual hero of the
   idle screen: wider and taller than any other control, carrying its own violet accent and a
   soft ambient glow (everything else nearby is deliberately quieter glass). */
.search-form {
    display: flex;
    gap: 0.75rem;
    max-width: 680px;
    margin: 0 auto;
    /* Half of the box <-> shimmering-title morph (its other half is .search-title below):
       while the search runs the form folds shut as the title blooms in the same spot, and
       unfolds again when the search ends. visibility flips only after the fold (600ms delay)
       so the collapsed form is untabbable/unclickable without cutting the animation short. */
    max-height: 12rem;
    transition: max-height 600ms var(--ease), opacity 250ms var(--ease), visibility 0s 0s;
}

body.is-searching .search-form,
body.is-done .search-form {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    visibility: hidden;
    transition: max-height 600ms var(--ease), opacity 250ms var(--ease), visibility 0s 600ms;
}

/* The running search's headline: the query, front and center in glowing gradient text with a
   light sheen sweeping across - the page's whole focus while the agent works. Collapsed to
   nothing outside a running search; the max-height/opacity/transform transitions against
   .search-form's fold make box -> title (and back) read as one transformation. */
.search-title {
    max-width: 820px;
    margin: 0 auto;
    font-size: clamp(1.35rem, 3.2vw, 2rem);
    line-height: 1.3;
    font-weight: 700;
    text-align: center;
    background: linear-gradient(105deg,
        #a5b4fc 0%, #e0e7ff 38%, #ffffff 50%, #e0e7ff 62%, #a5b4fc 100%);
    background-size: 220% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* drop-shadow (not text-shadow) so the glow follows the gradient-clipped glyphs. */
    filter: drop-shadow(0 0 16px color-mix(in srgb, var(--violet) 45%, transparent));
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    transform: translateY(-8px) scale(0.97);
    transition: max-height 600ms var(--ease), opacity 250ms var(--ease), transform 600ms var(--ease);
}

body.is-searching .search-title {
    max-height: 9rem;
    opacity: 1;
    transform: none;
    animation: title-shimmer 3s linear infinite;
    /* Text fades in slightly late, once the box has mostly folded - reads as handing over. */
    transition: max-height 600ms var(--ease), opacity 400ms 150ms var(--ease), transform 600ms var(--ease);
}

/* Finished search (body.is-done): the query stays up as the headline instead of morphing back
   into the box - the result page's presentation, so "here are results" looks the same whether
   you just searched or opened a shared link. Static on purpose: the shimmer means "working". */
body.is-done .search-title {
    max-height: 9rem;
    opacity: 1;
    transform: none;
}

@keyframes title-shimmer {
    from { background-position: 120% 0; }
    to { background-position: -120% 0; }
}

/* Shared text-input recipe: the main search field and the choice form's free-text answers. */
.search-input,
.choice-input {
    border-radius: var(--radius-md);
    border: 1px solid var(--glass-border);
    background: var(--glass-bg);
    color: var(--text-primary);
}

.search-input {
    flex: 1;
    padding: 1.05rem 1.35rem;
    backdrop-filter: blur(var(--blur-amt));
    -webkit-backdrop-filter: blur(var(--blur-amt));
    font-size: 1.1rem;
    /* Hero accent: a violet tint + glow make the box the brightest resting element on the
       idle screen, tying it to the Suchen button's action color. Strong enough to win the
       first fixation now that the supporting copy below is brighter too. */
    border-color: color-mix(in srgb, var(--violet) 38%, var(--glass-border));
    box-shadow: 0 0 44px color-mix(in srgb, var(--violet) 20%, transparent);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.search-input::placeholder,
.choice-input::placeholder {
    color: var(--text-secondary);
}

.search-input:focus,
.choice-input:focus {
    outline: none;
    border-color: color-mix(in srgb, var(--indigo) 55%, var(--glass-border));
}

/* Focus turns the resting glow up a notch - the box visibly "wakes up" under the cursor. */
.search-input:focus {
    border-color: color-mix(in srgb, var(--indigo) 70%, var(--glass-border));
    box-shadow: 0 0 56px color-mix(in srgb, var(--violet) 34%, transparent);
}

/* ---------- buttons ---------- */
.btn-primary {
    background: linear-gradient(135deg, var(--indigo), var(--violet));
    color: #fff;
    border: none;
    border-radius: var(--radius-md);
    padding: 0.8rem 1.4rem;
    font-weight: 600;
    font-size: 1rem;
    cursor: pointer;
    transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}

.btn-primary:hover:not(:disabled) {
    box-shadow: 0 0 28px color-mix(in srgb, var(--violet) 55%, transparent);
    transform: translateY(-1px);
}

.btn-primary:disabled {
    opacity: 0.6;
    cursor: default;
    transform: none;
    box-shadow: none;
}

.btn-secondary {
    display: block;
    margin: 2rem auto 0;
}

/* Shared pill recipe: answer chips, source chips. */
.chip,
.src-chip {
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-secondary);
    border-radius: 999px;
    cursor: pointer;
    transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}

.chip {
    padding: 0.35rem 0.8rem;
    font-size: 0.85rem;
}

.chip:hover:not(:disabled) {
    color: var(--text-primary);
    border-color: color-mix(in srgb, var(--indigo) 55%, var(--glass-border));
}

/* ---------- example-query chips (idle state) ---------- */
/* Three of the typewriter's example queries as one-click chips under the search box - an ad
   visitor starts a real search with a single tap. A faint violet accent ties them to the Suchen
   button: they are actions, and must read differently from the "Gerade gesucht" result links
   below. app.js builds them and hides the row as soon as a search or clarify starts. */
.suggestions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    justify-content: center;
    align-items: center;
    margin-top: 1rem;
}

.suggestions .chip {
    border-color: color-mix(in srgb, var(--violet) 28%, var(--glass-border));
    background: color-mix(in srgb, var(--violet) 7%, var(--glass-bg));
}

.suggestions .chip:hover:not(:disabled) {
    border-color: color-mix(in srgb, var(--violet) 60%, var(--glass-border));
    background: color-mix(in srgb, var(--violet) 14%, var(--glass-bg));
}

/* ---------- trust line (idle state) ---------- */
/* Incentive alignment in one sentence: a quiet caption closing the "So funktioniert's" steps
   (it answers the trust question step 3 raises). It should reassure in passing, not shout. */
.trust-line {
    max-width: 640px;
    margin: 1.4rem auto 0;
    text-align: center;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--text-secondary);
}

/* ---------- how it works (idle state) ---------- */
/* The product in three numbered steps under the search cluster: what to do, what we do, what
   you get. Quiet explanatory typography - it supports the search CTA and must not compete
   with it; the gradient number badges tie the steps to the Suchen button's accent. */
.how-it-works {
    max-width: 820px;
    width: 100%;
    margin: 0 auto 2.25rem;
}

/* Same small-caps section-label recipe as .recent-strip__title, so the idle screen's two
   secondary sections read as siblings. */
.how-it-works__title {
    margin: 0 0 0.9rem;
    text-align: center;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.how-it-works__steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.25rem;
}

.how-it-works__step {
    display: flex;
    align-items: flex-start;
    gap: 0.65rem;
    text-align: left;
}

.how-it-works__num {
    flex-shrink: 0;
    width: 1.65rem;
    height: 1.65rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: linear-gradient(135deg, var(--indigo), var(--violet));
    color: #fff;
    font-size: 0.8rem;
    font-weight: 700;
    /* Optical centering: digits have no descenders, so a centered line box (which reserves
       descender space) makes them sit visibly high in the circle. Shrink the box to the em
       square and nudge down half the descender. */
    line-height: 1;
    padding-top: 0.1em;
}

.how-it-works__head {
    margin: 0.15rem 0 0;
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--text-primary);
}

.how-it-works__sub {
    margin: 0.2rem 0 0;
    font-size: 0.78rem;
    line-height: 1.45;
    color: var(--text-secondary);
}

/* ---------- landing social proof (idle state) ---------- */
/* The "Gerade gesucht" card: recent finished searches as list rows, each linking to its /s/<id>
   permalink, with the served-searches counter in the header. Deliberately a card with rows -
   content to inspect - so it can't be confused with the chip buttons above. Filled by app.js
   from /api/stats, kept rotating by the jittered highlight feed (see app.js); hidden outside
   the idle phase or when the
   data isn't available. */
.recent-strip {
    max-width: 520px;
    width: 100%;
    margin: 0 auto;
    padding: 1rem 1.25rem;
}

.recent-strip__head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.25rem 1rem;
    margin: 0 0 0.35rem;
}

/* Small-caps section label: quiet, but typographically distinct from every other line on the
   idle screen, so the card announces "this is a feed" without a loud heading. */
.recent-strip__title {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* Live-feed marker: the strip keeps rotating in fresh searches, and the gently pulsing dot
   says so - the
   card reads as current activity, not a static list. */
.recent-strip__title::before {
    content: "";
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    margin-right: 0.45rem;
    vertical-align: 1px;
    background: var(--teal);
    box-shadow: 0 0 8px color-mix(in srgb, var(--teal) 70%, transparent);
    /* Slow and shallow on purpose: it should read as "live" only when the eye is already on
       the card - the search box must stay the screen's motion-free center of gravity, and a
       fast blink here would pull fixation down the page. */
    animation: live-pulse 4s ease-in-out infinite;
}

@keyframes live-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.6; }
}

@media (prefers-reduced-motion: reduce) {
    .recent-strip__title::before {
        animation: none;
    }
}

/* The counter is supporting evidence, not a second heading - it must never compete with the
   section label for the first read. */
.recent-strip__count {
    font-size: 0.72rem;
    color: color-mix(in srgb, var(--text-secondary) 65%, transparent);
}

/* Row links: query left, arrow right. Queries are user text - ellipsized so one long search
   can't wreck the card. The queries are the card's content and read close to primary; the
   repeated "Ergebnisse" label would be five words of noise, so at rest only the arrow marks
   the row as a link and the label fades in on the hovered/focused row. Hairlines only BETWEEN
   rows - a line under the header would frame it as its own box. */
.recent-strip__item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    padding: 0.5rem 0;
    border-top: 1px solid color-mix(in srgb, var(--glass-border) 60%, transparent);
    font-size: 0.9rem;
    color: color-mix(in srgb, var(--text-primary) 60%, var(--text-secondary));
    text-decoration: none;
    transition: color var(--dur) var(--ease);
}

.recent-strip__item:first-child {
    border-top: 0;
}

.recent-strip__item:hover {
    color: var(--text-primary);
}

.recent-strip__go-label {
    display: inline-block;
    margin-right: 0.3rem;
    opacity: 0;
    transform: translateX(4px);
    transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.recent-strip__item:hover .recent-strip__go-label,
.recent-strip__item:focus-visible .recent-strip__go-label {
    opacity: 1;
    transform: none;
}

.recent-strip__query {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Five arrows at once read as ornament - at rest they are barely-there wayfinding, and only
   the hovered row's arrow speaks up (teal, with the "Empfehlungen" label fading in). */
.recent-strip__go {
    flex-shrink: 0;
    white-space: nowrap;
    color: color-mix(in srgb, var(--text-secondary) 40%, transparent);
    transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.recent-strip__item:hover .recent-strip__go {
    color: var(--teal);
    transform: translateX(2px);
}

/* ---------- status ---------- */
.status {
    text-align: center;
    color: var(--text-secondary);
    margin: 2rem 0 0;
}

/* ---------- patience note (long-running search) ---------- */
/* Shown by app.js once a search runs longer than 10s: a gentle "we're still on it" line under
   the agent log. js/patience.js rotates through several messages - each swap plays --out (fade
   up and away), replaces the text, then --in (rise up into place), so every message reads as a
   fresh thought instead of a static label. */
.patience-note {
    width: 100%;
    max-width: 560px;
    margin: 0.75rem auto 0;
    text-align: center;
    color: var(--text-secondary);
    font-size: 0.85rem;
    line-height: 1.5;
}

.patience-note--in {
    animation: patience-in 600ms var(--ease) both;
}

.patience-note--out {
    animation: patience-out 450ms var(--ease) both;
}

@keyframes patience-in {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: none; }
}

@keyframes patience-out {
    from { opacity: 1; transform: none; }
    to { opacity: 0; transform: translateY(-8px); }
}

/* ---------- agent activity log ---------- */
.agent-log {
    /* .page is a flex column: without an explicit width, the auto margins make this box
       shrink-to-fit its text (width jitter, off-center feel). Full width capped at 560px keeps
       it constant and aligned with the search bar's column. */
    width: 100%;
    max-width: 560px;
    margin: 1rem auto 0;
    padding: 1rem 1.25rem;
    font-size: 0.9rem;
}

.agent-log__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    /* app.js sets an explicit height per render: content height, capped at ~5 lines - so the
       box starts small, grows smoothly (this transition) as entries arrive, then scrolls. */
    overflow-y: auto;
    transition: height 350ms var(--ease);
    scrollbar-width: thin;
    scrollbar-color: var(--glass-border) transparent;
}

.agent-log__item {
    color: var(--text-secondary);
    animation: log-in 300ms var(--ease) both;
    /* One line per entry: long search queries ellipsize instead of wrapping (the full text sits
       in the title attribute). Keeps the log's rhythm steady - every tick is exactly one row.
       flex-shrink is load-bearing: overflow:hidden zeroes a flex item's automatic minimum size,
       and without it the fixed-height list would crush every line to a sliver instead of
       scrolling. The explicit line-height is too: with `normal`, descenders paint below the
       line box and the overflow clipping cuts them off. */
    flex-shrink: 0;
    line-height: 1.4;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.agent-log__item::before {
    content: "\25CF  ";
    color: var(--indigo);
    animation: pulse 1.2s ease-in-out infinite;
}

.agent-log__item--done {
    animation: none;
}

.agent-log__item--done::before {
    content: "\2713  ";
    color: var(--teal);
    animation: none;
}

/* Leading site icon on web-search lines (Reddit/Amazon/magnifier) - replaces a text prefix. */
.agent-log__icon {
    display: inline-block;
    width: 1em;
    height: 1em;
    margin-right: 0.35em;
    vertical-align: -0.125em;
}

.agent-log__icon svg {
    display: block;
    width: 100%;
    height: 100%;
}

/* Brand marks in brand color - at this size color, not shape, is what makes them readable.
   The magnifier stays on the inherited secondary text color. */
.agent-log__icon--reddit { color: #ff4500; }
.agent-log__icon--amazon { color: #ff9900; }

/* Typing-indicator ellipsis on the active line - reads as "the agent is typing". */
.agent-log__item:not(.agent-log__item--done)::after {
    content: "";
    display: inline-block;
    width: 1.2em;
    text-align: left;
    animation: ellipsis 1.6s steps(1) infinite;
}

@keyframes ellipsis {
    0% { content: ""; }
    25% { content: "."; }
    50% { content: ".."; }
    75% { content: "..."; }
}

.agent-log__progress {
    margin: 0.8rem 0 0;
    color: var(--text-secondary);
    font-size: 0.82rem;
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

/* Overall search progress bar - the fill's width transition makes every stage and completed
   check a visible, satisfying step toward the goal. */
.progress {
    flex: 1;
    height: 6px;
    border-radius: 999px;
    border: 1px solid var(--glass-border);
    overflow: hidden;
}

.progress__fill {
    height: 100%;
    width: 0;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--indigo), var(--teal));
    transition: width 600ms var(--ease);
}

@keyframes log-in {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: none; }
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}

/* ---------- glass card recipe ---------- */
.glass-card,
.card {
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-lg);
    backdrop-filter: blur(var(--blur-amt));
    -webkit-backdrop-filter: blur(var(--blur-amt));
    padding: var(--card-pad);
}

/* ---------- results grid ---------- */
.results-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 1rem;
    margin-top: 2rem;
}

/* Small, deliberately quiet toggle under the top-3: reveals the verified candidates that
   ranked below the cut. A full-width grid row so it reads as a footer of the results, not
   as a fourth card. */
.btn-more-checked {
    grid-column: 1 / -1;
    justify-self: center;
    padding: 0.35rem 0.9rem;
    border: 1px solid var(--glass-border);
    border-radius: 999px;
    background: transparent;
    color: var(--text-secondary);
    font: inherit;
    font-size: 0.8rem;
    cursor: pointer;
    transition: color 150ms var(--ease), border-color 150ms var(--ease);
}

.btn-more-checked:hover {
    color: var(--text-primary);
    border-color: color-mix(in srgb, var(--indigo) 55%, var(--glass-border));
}

.card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease);
    /* LLM text can contain long unbroken tokens (URLs, model numbers) - never overflow the card. */
    overflow-wrap: break-word;
    min-width: 0;
}

.card:not(.card--skeleton):hover {
    transform: translateY(-3px);
    border-color: color-mix(in srgb, var(--indigo) 40%, var(--glass-border));
}

/* Podium: the top match visibly wins. */
.card--top {
    border-color: color-mix(in srgb, var(--indigo) 55%, var(--glass-border));
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--indigo) 30%, transparent),
        0 0 32px color-mix(in srgb, var(--indigo) 18%, transparent);
}

/* ---------- podium layout (result page) ---------- */
/* The top results as a podium: the recommendation centered and emphasized, #2/#3 flanking it.
   Only result.js emits the wrapper - the live grid on the main page stays a plain grid. */
.results-grid > .podium {
    grid-column: 1 / -1;
}

@media (min-width: 860px) {
    .podium {
        display: grid;
        grid-template-columns: 1fr 1.14fr 1fr;
        gap: 1rem;
        align-items: start;
    }
    /* The winner is placed into the middle column - DOM (and tab) order stays 1-2-3. */
    .podium > .card:nth-child(1) { grid-column: 2; grid-row: 1; }
    .podium > .card:nth-child(2) { grid-column: 1; grid-row: 1; }
    .podium > .card:nth-child(3) { grid-column: 3; grid-row: 1; }
    /* The flanks step down and stand back a little; attention brings one forward. */
    .podium > .card:nth-child(2),
    .podium > .card:nth-child(3) {
        margin-top: 1.5rem;
        opacity: 0.88;
    }
    .podium > .card:nth-child(2):hover,
    .podium > .card:nth-child(3):hover,
    .podium > .card:nth-child(2):focus-within,
    .podium > .card:nth-child(3):focus-within {
        opacity: 1;
    }
}

/* Narrow screens: a peek carousel - the recommendation centered, the flanks showing at the
   edges inviting a swipe (result.js sets the initial scroll position). Top padding keeps the
   overhanging rank chips inside the scroll container's clip. */
@media (max-width: 859px) {
    .podium {
        display: flex;
        gap: 0.75rem;
        overflow-x: auto;
        padding: 0.85rem 0.25rem;
        scroll-snap-type: x mandatory;
        scrollbar-width: none;
    }
    .podium::-webkit-scrollbar {
        display: none;
    }
    .podium > .card {
        flex: 0 0 min(84%, 340px);
        scroll-snap-align: center;
    }
    /* Visual order 2-1-3 keeps the recommendation in the middle; DOM (and reading) order
       stays 1-2-3. */
    .podium > .card:nth-child(1) { order: 2; }
    .podium > .card:nth-child(2) { order: 1; }
    .podium > .card:nth-child(3) { order: 3; }
}

/* Product photo off the verified listing. Amazon shoots on pure white, so a white tile reads as
   a deliberate frame on the dark glass; object-fit keeps any aspect ratio unclipped. */
.card__img {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 150px;
    padding: 0.5rem;
    border-radius: var(--radius-md);
    background: #fff;
    border: 1px solid var(--glass-border);
}

.card__img img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.card__title {
    margin: 0;
    font-size: 1.1rem;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.card__meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.15rem 0.5rem;
    margin: 0;
    color: var(--text-secondary);
    font-size: 0.9rem;
}

/* Keep "33.800 Bewertungen" together - if the row is too narrow it wraps as a whole phrase. */
.card__reviews {
    white-space: nowrap;
}

/* ---------- star rating ---------- */
.stars {
    position: relative;
    display: inline-block;
    color: color-mix(in srgb, var(--text-secondary) 40%, transparent);
    letter-spacing: 2px;
    line-height: 1;
}

.stars__fg {
    position: absolute;
    left: 0;
    top: 0;
    overflow: hidden;
    white-space: nowrap;
    color: var(--gold);
}

/* ---------- score gauges ---------- */
.card__scores {
    display: flex;
    gap: 1.25rem;
}

.gauge {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
}

.gauge__ring {
    position: relative;
    width: 46px;
    height: 46px;
}

.gauge__ring svg {
    width: 100%;
    height: 100%;
    transform: rotate(-90deg);
}

.gauge__track,
.gauge__fill {
    fill: none;
    stroke-width: 3.5;
}

.gauge__track {
    stroke: var(--glass-border);
}

.gauge__fill {
    stroke-linecap: round;
}

.gauge__num {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    font-size: 0.82rem;
}

.gauge__label {
    font-size: 0.68rem;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* ---------- collapsed reasoning ---------- */
.card__why summary {
    cursor: pointer;
    color: var(--text-secondary);
    font-size: 0.88rem;
    font-weight: 600;
}

.card__why summary:hover {
    color: var(--text-primary);
}

.card__why p {
    margin: 0.4rem 0 0;
    color: var(--text-secondary);
    font-size: 0.88rem;
    line-height: 1.45;
}

/* ---------- source chips ---------- */
.card__sources {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    margin: 0;
    font-size: 0.78rem;
    color: var(--text-secondary);
}

.src-chip {
    padding: 0.12rem 0.5rem;
    text-decoration: none;
}

.src-chip:hover {
    color: var(--text-primary);
    border-color: color-mix(in srgb, var(--teal) 55%, var(--glass-border));
}

/* Each list under a small eyebrow heading (the gauge labels' visual language, tinted toward
   the list's sign color) - strengths and weaknesses parse as two labeled blocks at a glance. */
.card__list {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.card__list-head {
    margin: 0;
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.card__pros-head {
    color: color-mix(in srgb, var(--teal) 65%, var(--text-secondary));
}

.card__cons-head {
    color: color-mix(in srgb, var(--violet) 60%, var(--text-secondary));
}

.card__pros,
.card__cons {
    list-style: none;
    padding: 0;
    margin: 0;
    color: var(--text-secondary);
    font-size: 0.88rem;
    line-height: 1.4;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

/* Hanging indent: the +/- sign sits in its own gutter so wrapped lines align with the text,
   not with the sign. */
.card__pros li,
.card__cons li {
    position: relative;
    padding-left: 1.1em;
}

.card__pros li::before,
.card__cons li::before {
    position: absolute;
    left: 0;
}

.card__pros li::before {
    content: "+";
    color: var(--teal);
}

.card__cons li::before {
    content: "\2013";
    color: color-mix(in srgb, var(--violet) 70%, var(--text-secondary));
}

/* ---------- buy banner ---------- */
/* The page's one conversion action: a full-bleed gradient banner fusing action and price -
   label left, price right, arrow closing the line (the checkout-bar pattern). It breaks out
   of the card's padding to run edge to edge (hence square sides), so it reads as part of the
   card's architecture, and renders identically on every card at every width; a long price can
   never reshape the layout. */
.btn-amazon {
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
    /* Extra air around the banner (on top of the card's gap): the conversion element gets its
       own zone between the scores above and the pros/cons below. */
    margin-block: 0.4rem;
    margin-inline: calc(var(--card-pad) * -1);
    padding: 0.7rem var(--card-pad);
    border: none;
    background: linear-gradient(135deg, var(--indigo), var(--violet));
    color: #fff;
    text-decoration: none;
    transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.btn-amazon__label {
    flex: 1;
    font-weight: 600;
    font-size: 0.88rem;
    white-space: nowrap;
}

.btn-amazon__price {
    font-weight: 700;
    font-size: 0.95rem;
    white-space: nowrap;
}

/* State-driven banner color on the podium: at rest only the recommendation (or the AI pick)
   carries the full gradient; hovering any card hands the color to that card and fades the
   others out - the spotlight follows intent. filter (not background) because gradients can't
   transition. Hover-capable devices only: on touch there is no hover to win the color back,
   so every banner keeps it. Browsers without :has() simply never mute the recommendation. */
@media (hover: hover) {
    .podium > .card .btn-amazon {
        filter: saturate(0.12) brightness(0.8);
        transition: filter var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
    }

    .podium > .card--top .btn-amazon,
    .podium > .card--picked .btn-amazon,
    .podium > .card:hover .btn-amazon {
        filter: none;
    }

    .podium:has(> .card:hover) > .card:not(:hover) .btn-amazon {
        filter: saturate(0.12) brightness(0.8);
    }
}

.btn-amazon::after {
    content: "→";
    font-weight: 600;
}

/* The whole card is a click target for the buy link (render.js initCardClickthrough), so
   hovering anywhere on a linked card lights the button up - the card itself advertises where
   the click goes. */
.card--linked {
    cursor: pointer;
}

.btn-amazon:hover,
.card--linked:hover .btn-amazon {
    box-shadow: 0 0 20px color-mix(in srgb, var(--violet) 55%, transparent);
    transform: translateY(-1px);
}

/* ---------- card arrival & final ranking reveal ---------- */
/* A verified find popping in is the reward moment - spring in with a brief glow, then settle. */
.card--enter {
    animation: card-in 550ms var(--ease) both;
}

@keyframes card-in {
    0% {
        opacity: 0;
        transform: translateY(14px) scale(0.97);
        box-shadow: none;
    }
    55% {
        opacity: 1;
        box-shadow: 0 0 36px color-mix(in srgb, var(--teal) 35%, transparent);
    }
    100% {
        opacity: 1;
        transform: none;
        box-shadow: none;
    }
}

.card--ranked {
    animation: card-rank 420ms var(--ease) both;
}

@keyframes card-rank {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: none; }
}

.rank-chip {
    position: absolute;
    top: -10px;
    left: 1rem;
    font-size: 0.7rem;
    font-weight: 700;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    background: var(--bg);
    border: 1px solid var(--glass-border);
    color: var(--text-secondary);
}

.rank-chip--top {
    background: linear-gradient(90deg, var(--indigo), var(--teal));
    border: none;
    color: #06060a;
}

/* Fallback results: warm amber "Geheimtipp" treatment - a confident pick that simply lives
   outside Amazon, not a lesser find. */
.rank-chip--fallback {
    border-color: color-mix(in srgb, var(--ok) 55%, var(--glass-border));
    color: var(--ok);
}

.card--fallback {
    border-color: color-mix(in srgb, var(--ok) 35%, var(--glass-border));
}

.card__fallback-note {
    margin: 0;
    color: var(--ok);
    font-size: 0.85rem;
    line-height: 1.4;
}

@media (prefers-reduced-motion: reduce) {
    .card--enter,
    .card--ranked,
    .agent-log__item,
    .agent-log__item::before,
    .agent-log__item:not(.agent-log__item--done)::after,
    .card--skeleton,
    .card--skeleton__label--tick,
    .patience-note--in,
    .patience-note--out,
    body.is-searching .glow-bg,
    body.is-searching .search-title {
        animation: none;
    }

    .progress__fill,
    .agent-log__list,
    /* Launch mode still folds the header away and morphs box <-> title - it just cuts
       instead of gliding. */
    .page,
    .page__header,
    .gradient-text,
    .page__tagline,
    .page__usp,
    .search-form,
    .search-title {
        transition: none;
    }

    /* Without their animations the fairies are just static dots (and the starfield frozen
       streaks) - drop both layers entirely. */
    .fairies,
    .starfield {
        display: none;
    }
}

/* ---------- loading skeleton ---------- */
.card--skeleton {
    min-height: 220px;
    background: linear-gradient(100deg,
        var(--glass-bg) 40%,
        rgba(255, 255, 255, 0.09) 50%,
        var(--glass-bg) 60%);
    background-size: 200% 100%;
    animation: shimmer 1.6s ease-in-out infinite;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
}

/* Once assessment starts, each skeleton names the product it is verifying; the label ticks
   through the live per-candidate steps ("reading reviews…" -> "verifying the live price…"). */
.card--skeleton__label {
    color: var(--text-secondary);
    font-size: 0.88rem;
    font-style: italic;
    max-width: 85%;
}

.card--skeleton__label--tick {
    animation: step-tick 350ms var(--ease);
}

@keyframes step-tick {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
}

@keyframes shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* ---------- AI pick highlight ---------- */
.card--picked {
    border-color: color-mix(in srgb, var(--teal) 55%, var(--glass-border));
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--teal) 35%, transparent),
        0 0 32px color-mix(in srgb, var(--teal) 22%, transparent);
}

.badge {
    position: absolute;
    top: -10px;
    right: 1rem;
    background: linear-gradient(90deg, var(--indigo), var(--teal));
    color: #06060a;
    font-size: 0.7rem;
    font-weight: 700;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
}

.ai-pick {
    text-align: center;
}

.pick-banner {
    max-width: 560px;
    margin: 1.5rem auto 0;
    color: var(--text-secondary);
    text-align: left;
}

/* ---------- "help me choose" question form ---------- */
.choice-form {
    max-width: 560px;
    margin: 1.5rem auto 0;
    text-align: left;
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
    animation: log-in 300ms var(--ease) both;
}

/* Lead-in line above the questions (used by the pre-search clarify form). */
.choice-intro {
    margin: 0;
    font-weight: 600;
}

.choice-q {
    border: none;
    padding: 0;
    margin: 0;
}

.choice-q legend {
    padding: 0;
    margin-bottom: 0.5rem;
    font-weight: 600;
    font-size: 0.95rem;
}

.choice-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
}

.chip--on {
    color: #fff;
    background: linear-gradient(135deg, var(--indigo), var(--violet));
    border-color: transparent;
}

.choice-input {
    width: 100%;
    padding: 0.6rem 0.9rem;
    font-size: 0.92rem;
}

/* Reassurance above the submit button: every question may be left open. */
.choice-hint {
    margin: 0;
    align-self: center;
    color: var(--text-secondary);
    font-size: 0.82rem;
}

.choice-submit {
    align-self: center;
    margin-top: 0.25rem;
}

.choice-skip {
    align-self: center;
    background: none;
    border: none;
    color: var(--text-secondary);
    font-size: 0.82rem;
    cursor: pointer;
    text-decoration: underline;
}

.choice-skip:hover {
    color: var(--text-primary);
}

/* ---------- geo block (not available in your country) ---------- */
.geo-block {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 1.5rem;
    text-align: center;
}

.geo-block__card {
    max-width: 480px;
    padding: 2.5rem 2rem;
}

.geo-block__title {
    font-size: 1.15rem;
    font-weight: 600;
    margin: 1.25rem 0 0.5rem;
}

.geo-block__sub {
    color: var(--text-secondary);
    margin: 0 0 1.5rem;
}

/* ---------- footer (affiliate disclosure + legal links) ---------- */
.page__footer {
    /* Pushed to the bottom of the flex-column .page; padding keeps it clear of the content. */
    margin-top: auto;
    padding-top: 3rem;
    text-align: center;
    color: color-mix(in srgb, var(--text-secondary) 85%, transparent);
    font-size: 0.78rem;
}

.footer-links {
    display: flex;
    justify-content: center;
    gap: 1.25rem;
    margin-bottom: 0.5rem;
}

.footer-links a {
    color: var(--text-secondary);
    text-decoration: none;
}

.footer-links a:hover {
    color: var(--text-primary);
    text-decoration: underline;
}

/* ---------- legal pages (Impressum / Datenschutz) ---------- */
.legal {
    max-width: 640px;
    margin: 0 auto;
    padding: 1.75rem;
    line-height: 1.6;
    color: var(--text-secondary);
    font-size: 0.92rem;
}

.legal h2 {
    color: var(--text-primary);
    font-size: 1.05rem;
    margin: 1.5rem 0 0.5rem;
}

.legal h2:first-child {
    margin-top: 0;
}

.legal p,
.legal ul {
    margin: 0 0 0.75rem;
}

.legal a {
    color: var(--teal);
}

.legal-back {
    text-align: center;
    margin: 1.5rem 0 0;
}

.legal-back a {
    color: var(--text-secondary);
    text-decoration: none;
}

.legal-back a:hover {
    color: var(--text-primary);
}

/* ---------- responsive ---------- */
@media (max-width: 540px) {
    .search-form {
        flex-direction: column;
    }

    .gradient-text {
        font-size: 1.8rem;
    }

    /* Keep the search box above the fold despite the extra USP line: tighter top padding and a
       smaller, tighter subline on small screens. */
    .page {
        padding-top: 2.25rem;
    }

    .page__tagline {
        font-size: 1.1rem;
    }

    .page__usp {
        font-size: 0.82rem;
        margin-bottom: 1.4rem;
    }

    /* Narrow screens: the arrow alone marks the row as a link - the "Ergebnisse" hover label
       would eat the width the (ellipsized) queries need, and there is no hover anyway. */
    .recent-strip__go-label {
        display: none;
    }

    /* Steps stack into rows; tighter gap so all three stay on one phone screen. */
    .how-it-works__steps {
        grid-template-columns: 1fr;
        gap: 0.8rem;
    }

    .how-it-works {
        margin-bottom: 1.75rem;
    }
}

/* ---------- result page (result.html) + the share widget ---------- */

/* The wrapper anchors the floating share button + its popover to the results grid. */
.results-wrap {
    position: relative;
}

/* Round floating share button, straddling the results' top-right corner (rank chips sit
   top-left, so nothing collides). */
.share-fab {
    position: absolute;
    top: -22px;
    right: 0;
    z-index: 6;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: linear-gradient(135deg, var(--indigo), var(--violet));
    color: #fff;
    border: none;
    cursor: pointer;
    box-shadow: 0 6px 20px rgba(99, 102, 241, 0.45);
    transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.share-fab:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 26px rgba(99, 102, 241, 0.6);
}

.share-fab svg {
    width: 20px;
    height: 20px;
}

/* Icon-only as the floating corner button; the label only shows where the button dresses as a
   pill in the result page's header action row (see .result-cta .share-fab below). */
.share-fab__label {
    display: none;
}

/* The share popover: the permalink with a copy button plus share-intent links, anchored
   under the floating button. */
.share-pop {
    position: absolute;
    top: 32px;
    right: 0;
    z-index: 6;
    width: min(420px, calc(100vw - 3rem));
    padding: 1.25rem 1.35rem 1.1rem;
    text-align: left;
    /* Near-opaque dark surface instead of the .glass-card recipe: the popover overlays bright
       product photos, where translucent white glass washes out and the text becomes unreadable. */
    background: rgba(19, 19, 30, 0.97);
    box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
}

.share-pop__title {
    margin: 0 0 0.75rem;
    font-size: 1rem;
    font-weight: 700;
}

.share-pop__row {
    display: flex;
    gap: 0.6rem;
}

.share-pop__link {
    flex: 1;
    min-width: 0;
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-md);
    color: var(--text-primary);
    padding: 0.7rem 0.8rem;
    font-size: 0.95rem;
}

.share-pop__copy {
    padding: 0.7rem 1rem;
    font-size: 0.9rem;
    white-space: nowrap;
}

/* One row of share targets: round glass icon buttons with a tiny label underneath. */
.share-pop__socials {
    display: flex;
    justify-content: space-between;
    gap: 0.25rem;
    margin-top: 1rem;
}

.share-pop__social {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.2rem;
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-size: 0.72rem;
    text-decoration: none;
    transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.share-pop__icon {
    width: 42px;
    height: 42px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}

.share-pop__icon svg {
    width: 19px;
    height: 19px;
}

.share-pop__social:hover {
    color: var(--text-primary);
    transform: translateY(-2px);
}

.share-pop__social:hover .share-pop__icon {
    border-color: color-mix(in srgb, var(--indigo) 55%, var(--glass-border));
    background: color-mix(in srgb, var(--indigo) 18%, var(--glass-bg));
}

/* The dated "prices may have changed" note under the result query. A quiet caption - normal
   flow under the tagline (a negative margin here once compensated a header spacing that no
   longer exists and made the two lines overlap). */
.result-meta {
    color: color-mix(in srgb, var(--text-secondary) 80%, transparent);
    font-size: 0.82rem;
    margin: 0.35rem 0 0;
}

/* Result page (body.result): mirrors the main page's finished-search view - compact wordmark at
   the top (same sizes as body.is-compact, which result.html can't use: its collapse rules would
   hide the query) and the result query as the page's headline. A shared link should look like
   the search it came from, not like a second landing page. */
body.result .page {
    padding-top: 1.1rem;
}

body.result .page__header {
    margin-bottom: 1.6rem;
}

body.result .gradient-text {
    font-size: 1.15rem;
    margin-bottom: 1.15rem;
}

/* The result query wears the live search's .search-title dress, minus the morph machinery:
   the base rule collapses it (max-height 0) for the box <-> title transition on the main page,
   but here nothing morphs - the page loads directly into its "done" state. No shimmer either;
   that animation means "working right now", which a finished result is not. */
body.result .search-title {
    max-height: none;
    overflow: visible;
    opacity: 1;
    transform: none;
}

/* The result page's header action row: the nudge into one's own search plus the re-share
   button, visible without scrolling. Inline-flex so the header's text-align centers it, and
   the positioning anchor for the share popover. */
.result-cta {
    position: relative;
    display: inline-flex;
    align-items: center;
    /* Up to three pills (re-share, stale-only "Suche aktualisieren", own-search nudge) -
       wrap instead of overflowing on narrow screens. */
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.75rem;
    margin: 1.4rem 0 0;
}

/* One pill recipe for both actions in the row - quiet violet, inviting but subordinate to the
   results (a full primary button here would compete with the product links). Serves anchors
   and buttons alike (the main page's „Neue Suche" is a button), hence font/cursor resets. */
.result-cta__btn,
.result-cta .share-fab {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 1.35rem;
    border-radius: 999px;
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-primary);
    text-decoration: none;
    cursor: pointer;
    background: color-mix(in srgb, var(--indigo) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--indigo) 45%, var(--glass-border));
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
        box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.result-cta__btn:hover,
.result-cta .share-fab:hover {
    background: color-mix(in srgb, var(--indigo) 26%, transparent);
    border-color: color-mix(in srgb, var(--violet) 60%, var(--glass-border));
    box-shadow: 0 0 26px color-mix(in srgb, var(--violet) 30%, transparent);
    transform: translateY(-1px);
}

/* The arrow leans into the click on hover - a small "come on" gesture. */
.result-cta__arrow {
    transition: transform var(--dur) var(--ease);
}

.result-cta__btn:hover .result-cta__arrow {
    transform: translateX(3px);
}

/* In the action row the share button sheds its floating-corner form (the base .share-fab
   rules above): back into the flow, sized by the pill recipe, label visible. It leads the row
   (sharing.js injects it at the row's start), the own-search nudge follows. */
.result-cta .share-fab {
    position: static;
    width: auto;
    height: auto;
    box-shadow: none;
}

.result-cta .share-fab svg {
    width: 16px;
    height: 16px;
}

.result-cta .share-fab__label {
    display: inline;
}

/* The popover follows its new anchor: centered under the action row instead of hanging off
   the results grid's corner. */
.result-cta .share-pop {
    top: calc(100% + 12px);
    right: auto;
    left: 50%;
    transform: translateX(-50%);
}

/* Server-rendered results (ResultPage.kt): the crawlable page content, and what no-JS visitors
   read. result.js swaps it for the interactive cards, so with JS it only bridges the API fetch -
   styled as a quiet readable list, not competing with the cards that replace it. */
.result-ssr {
    max-width: 720px;
    margin: 0 auto 1.5rem;
    text-align: left;
}

.result-ssr h2 {
    font-size: 1.05rem;
    margin: 0 0 0.9rem;
}

.result-ssr ol {
    margin: 0;
    padding-left: 1.3rem;
    display: grid;
    gap: 0.9rem;
}

.result-ssr h3 {
    margin: 0 0 0.25rem;
    font-size: 1rem;
}

.result-ssr a {
    color: var(--text-primary);
}

.result-ssr p {
    margin: 0;
    color: var(--text-secondary);
    font-size: 0.92rem;
    line-height: 1.5;
}
