/* =========================================================================
   responsive.css — page-specific refinements below the frame (Wave 2)
   -------------------------------------------------------------------------
   The layout frame lives in style.css. This file holds page-specific layout
   on top of it — the campaign page's two-column split, and refinements at
   narrow widths. It must never fight the grid.

   Pages are scoped with :has() on their own content rather than a body class.
   base.html.twig renders <bodyclass="..."> with no space, so the body carries
   no classes at all and body[class*="campaign-id-"] never matches. That is a
   separate bug; this file does not depend on it.

   The 687px breakpoint is the frame's single-column threshold: a 316px track
   plus its 16px gutter is 332px, and the viewport loses 16px to the body
   margin and 24px to the wrapper padding, so two columns need 688px. Keep
   this number in step with style.css if the frame ever changes.

   !important is permitted here ONLY against a Vue-bound inline style
   (:style="..."), because inline styles beat any stylesheet. Every such use
   must name the binding it overrides in a comment.
   ========================================================================= */

/* -------------------------------------------------------------------------
   Status card dates + share icons (all widths).

   .leftside's own dates + share icons (next to the ingress text) are
   replaced everywhere by a compact copy in .statusbox-meta (campaign_page.
   html.twig, right after the status card's opening tag), fit into that
   card's spare upper-right corner. Originally mobile-only; now unconditional
   since the statusbox is a similarly-narrow ~316-335px card at every
   breakpoint in this sidebar layout, not just on phones.
   ------------------------------------------------------------------------- */
.leftside div.info,
.leftside #social_share_wrapper {
    display: none;
}

/* style.css:1664's padding-right: 150px reserved room for .leftside div.info
   next to the ingress text — dead weight now that div.info never renders
   there at all. Same story for min-height: 140px (style.css:2046): it kept
   the box tall enough for that block to sit inside it too. With the end
   date and share icons living in the status card now, short ingress copy
   just leaves a bare gap below the text before the bottom border — most
   visible on wide screens where the box's width doesn't force extra wraps. */
.leftside p.ingress {
    padding-right: 0;
    min-height: 0;
}

/* Its two date lines and the share-icon row are each positioned
   independently (below) rather than stacked as one flowing block, so each
   can be pinned to line up with a specific row of the card's own content
   (h3.backers' "11", h3.funding's amount, the days-left h3) instead of
   just sitting at whatever height flow happens to produce. */
.statusbox-meta {
    display: block;
}

/* Right-edge x (16px) matches h3.backers/h3.funding's own left inset
   (.col1.project_white_box's 16px padding, style.css:561) mirrored to
   the other side, so the corner block reads as aligned with the rest
   of the card's content grid, not just "close to the edge". Narrower
   than the first mobile-only pass (110px, not 130px) — this card is
   tighter at some widths than the 375px reference it was tuned against. */
.statusbox-meta-start,
.statusbox-meta-end {
    position: absolute;
    right: 16px;
    width: 110px;
    margin: 0;
    padding: 0;
    color: #fff;
    font-family: "adelle-sans", sans-serif;
    font-size: 14px;
    line-height: 18px;
    text-align: right;
}

/* Top offsets measured against h3.backers/h3.funding's own .number span
   (the "11" / funding-amount figures), not guessed — this is what "match
   the upper parts of 11 and 236€" means in practice. Both cards' first two
   rows are a fixed layout (backers count, then funding amount), so this
   holds across campaigns; it would need re-measuring if that structure
   changes. */
.statusbox-meta-start {
    top: 4px;
}

.statusbox-meta-end {
    top: 74px;
}

/* Sits in the gap between the dates block (ends ~94px, .statusbox-meta-end
   top: 74px + its own line height) and the icon row (top: 180px below) —
   room enough for one label line without pushing either. +5px from the
   original 152px/175px pair below — sat a little too close to the dates. */
.statusbox-share-label {
    position: absolute;
    top: 157px;
    right: 16px;
    width: 130px;
    margin: 0;
    padding: 0;
    color: #9fa0a0;
    font-family: "adelle-sans", sans-serif;
    font-size: 12px;
    line-height: 15px;
    text-align: right;
}

/* top: 180px — was 175px (the third stat row's own top, the "days left"
   h3), +5px so the row doesn't crowd .statusbox-share-label right above
   it now that there's a label there at all. width: 130px (was 110px):
   3 icons at 34px + 2 gaps at 6px = 114px, which no longer fit the old
   two-icon width. */
.statusbox-share {
    position: absolute;
    top: 180px;
    right: 16px;
    width: 130px;
    display: flex;
    justify-content: flex-end;
    gap: 6px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.statusbox-share svg {
    width: 16px;
    height: 16px;
}

/* Plain 36x30 icon buttons instead of the rrssb vendor markup used in
   .leftside — rrssb's icons are absolutely positioned and sized for its
   own ~35px buttons (MesenaattiAgitator.css:18-23), and shrinking that
   with transform: scale clipped the Facebook glyph against its own icon
   wrapper's box instead of just shrinking cleanly. A plain flex-centered
   button with an inline SVG sized to fit has no such assumption baked in.

   Fully qualified (.col1...statusbox .statusbox-share a.statusbox-
   share-fb, not a bare .statusbox-share a): style.css's .col1.
   project_white_box.statusbox.planb a sets display: block (among other
   things) for the "Rahoita" CTA at (0 ids, 4 classes, 1 element)
   specificity. A plain .statusbox-share a (0,1,1) loses to that outright.
   One extra class per selector, to (0,5,1), wins regardless of source
   order; same fix applied to these two selectors' background-color
   below. */
.col1.project_white_box.statusbox .statusbox-share a.statusbox-share-fb,
.col1.project_white_box.statusbox .statusbox-share a.statusbox-share-x,
.col1.project_white_box.statusbox .statusbox-share a.statusbox-share-whatsapp {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 30px;
    padding: 0;
    border: 0;
    box-sizing: border-box;
    border-radius: 4px;
}

/* Brand colors matching rrssb.css's own (li.rrssb-facebook/twitter a,
   lines 38/62) so this still reads as the same two networks — X's is its
   own current brand black, not blue, since X dropped Twitter's light blue
   when it dropped the bird mark. WhatsApp's is its own standard brand
   green (#25D366), not from rrssb — that vendor sheet predates WhatsApp
   being a common share target and has no rule for it. */
.col1.project_white_box.statusbox .statusbox-share a.statusbox-share-fb {
    background-color: #306199;
}

.col1.project_white_box.statusbox .statusbox-share a.statusbox-share-x {
    background-color: #000;
}

.col1.project_white_box.statusbox .statusbox-share a.statusbox-share-whatsapp {
    background-color: #25d366;
}

/* style.css's .col1.project_white_box.statusbox.planb a (0 ids, 4 classes,
   1 element) and its :hover (0,5,1) paint every <a> in this card orange —
   built for the single old "Rahoita" link, now also catching the redesign's
   CTA and the new monochrome share-row icons. Qualified to the same
   .col1.project_white_box.statusbox.planb prefix plus the new class, at
   equal specificity this file loading after style.css (templates/parts/
   header.html.twig) settles the tie in these rules' favor without
   !important. statusbox-cta--ended is a <p>, not an <a> — style.css's rule
   only matches anchors, so no conflict there.

   The older, unqualified .col1.project_white_box.statusbox a (style.css,
   0,3,1 — built for the old absolutely-positioned bottom-pinned button)
   also sets font-size: 28px, padding: 5px 10px, width: 264px, display:
   block and text-decoration: underline, and nothing at (0,4,1)+ was
   resetting those, so the redesign's CTA was rendering at a 28px/54px-tall
   size instead of its intended 19px/~43px. Reinstated here alongside the
   color/border/background fix already in place. */
.col1.project_white_box.statusbox.planb a.statusbox-cta {
    border: 0;
    background-color: var(--ms-orange-500);
    color: var(--ms-paper);
    font-family: var(--font-display);
    font-size: 19px;
    font-weight: 600;
    text-decoration: none;
    text-align: center;
    display: block;
    width: auto;
    padding: 10px 0;
}

.col1.project_white_box.statusbox.planb a.statusbox-cta:hover {
    border: 0;
    background-color: var(--ms-orange-600);
    color: var(--ms-paper);
}

/* Same leak as the CTA above: font-size: 28px and padding: 5px 10px from
   the old unqualified rule were inflating each icon link to ~32px tall
   (and display: block was undoing the flex centering .statusbox-share-mono
   a asks for at style.css:1002), bloating the whole share row to ~49px. */
.col1.project_white_box.statusbox.planb .statusbox-share-mono a {
    border: 0;
    background-color: transparent;
    color: var(--ms-ink-300);
    display: flex;
    padding: 0;
    font-size: 0;
    text-decoration: none;
}

.col1.project_white_box.statusbox.planb .statusbox-share-mono a:hover {
    border: 0;
    background-color: transparent;
    color: var(--ms-paper);
}

/* Same reasoning as div.data below: in normal flow, the progress bar sits
   right after the description, and description length varies card to
   card, so the bar lands at a different height in every card in a row.
   Pin it the same way div.data is pinned — position:absolute at a fixed
   offset from the card's bottom edge — so every card in a row shows it at
   the same height regardless of how much description text preceded it.
   54px clears div.data's own 40px-tall footer (bottom:0) with a small gap. */
@media (min-width: 688px) {
    .col1.project_white_box.card .progress {
        position: absolute;
        left: 16px;
        right: 16px;
        bottom: 54px;
    }
}

/* style.css:3442 pins the "Hybridirahoitus"-style label (shown on cards for
   campaigns with match_funding, e.g. Tampere's hybrid-funded ones) at
   bottom:39px — inside where the progress bar now sits (54-57px from the
   bottom, see above). Move it clear above the bar instead of into it. */
.col1.project_white_box p.hybrid {
    bottom: 64px;
}

/* style.css:471 sets #body-emulate's align-items:start on purpose ("boxes
   keep their own height, no row stretching") — grid rows size to the
   tallest item, but shorter siblings are NOT stretched to match, they just
   sit at the top of the row at their own height. Numbers must stay pinned
   to the very bottom of a FIXED card height — div.data's original
   position:absolute; bottom:0 (style.css:1331) already guarantees that
   regardless of description length. The card stays at the frame's normal
   400px (style.css:532), not a taller number: now that the progress bar
   above is also position:absolute (see the rule further down), it no
   longer adds real in-flow height, so nothing pushes into div.data's
   40px footer any more. A taller card here would only reintroduce the
   original problem this whole file is about — every non-card .col1 tile
   sharing a row (e.g. .col1.navi's category list) stays at 400px too, and
   a mismatched height between them is exactly the "no row stretching"
   trap described above. */

/* style.css:552 gives every .col1 a 16px margin-bottom, ON TOP OF the
   grid's own 16px row-gap (style.css:470) — 32px vertical vs. 16px
   horizontal, doubling the gap wherever both actually apply. But grid-gap
   only reaches DIRECT children of #body-emulate; pages that wrap their
   cards in a plain (non-"grid-passthrough") div before the .col1 — e.g.
   pro/tampere-mun.html.twig's campaign sidebar — never got the grid-gap
   at all and rely on this margin as their ONLY spacing. Scoping to direct
   children only removes the real double-up without stripping the sole
   spacing mechanism from a page that never had the other half. */
#body-emulate > .col1,
#body-emulate > .grid-passthrough > .col1 {
    margin-bottom: 0;
}

/* .data ul li's fixed 69px left-padding (style.css:1359) assumed a fixed
   283px-wide box (desktop's position:absolute .data). It only ever "looked"
   centered by coincidence of that one specific width; once the card's own
   width varies — the flexible single-column layout added above — the
   middle "koossa" column drifts left instead of tracking the available
   space. flex + space-between adapts to any container width instead of a
   magic-number offset, and looks identical at the original 283px too. */
.data ul {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
}

.data ul li {
    float: none;
    padding-left: 0;
}

.data ul li:last-child {
    float: none;
    text-align: right;
}

/* The status box was built around a fixed height (style.css:765 — 380px)
   with a single button pinned to its bottom (style.css:870 — position:
   absolute, bottom: 14px). Funding-terms copy length varies per campaign,
   and long copy runs under the button — at any width, not only mobile. Let
   the box grow with its content everywhere; nothing downstream depends on
   this box matching a fixed height (the >=1020px grid stacks column-3 boxes
   independently, and the <=687px and 688-1019px rules below reorder by
   `order`, not by height). */
.col1.project_white_box.statusbox {
    height: auto;
    padding-bottom: 9px;
}

.col1.project_white_box.statusbox a {
    position: static;
    width: auto;
    margin-top: 12px;
}

/* The rule above targets the pre-redesign CTA (originally position:
   absolute, bottom-pinned). It also matches every <a> in the redesign
   markup — the CTA and the three share icons — stamping margin-top: 12px
   over their own (much smaller) intended values. Requalified at (0,4,*)
   to win back the real numbers; the share icons in particular were each
   getting 12px of top margin inside a flex row, inflating the whole
   share row to ~49px when it should read as a single compact line. */
.col1.project_white_box.statusbox .statusbox-cta {
    margin-top: 14px;
}

.col1.project_white_box.statusbox .statusbox-share-mono a {
    margin-top: 0;
}

/* -------------------------------------------------------------------------
   Campaign page, two-column range (688-1019px).

   Below 1020px the leftside-spans-two-columns rule above does not apply, so
   the frame's default auto-placement puts the (very tall) description in one
   column and whatever comes next — usually the main menu card, ~400px tall —
   in the other, sharing that row. Grid rows size to their tallest item, so
   the short card sits at the top of a row that is thousands of pixels tall,
   leaving a dead strip the width of a card below it (grid auto-placement is
   sparse by default and never backfills that gap with a later item).

   Fix: give the description the full row to itself (span both columns, no
   partner to strand) and move the status card up beside the main menu
   instead, so the two short cards share the first row on purpose.

------------------------------------------------------------------------- */
@media (min-width: 688px) and (max-width: 1019px) {
    #body-emulate:has(.leftside.view) .col1.project_white_box.statusbox { order: 1; }
    #body-emulate:has(.leftside.view) .col1.navi { order: 2; }

    #body-emulate:has(.leftside.view) .leftside.view {
        order: 3;
        grid-column: 1 / -1;
    }

    #body-emulate:has(.leftside.view) .col1.stretch.rewards_container { order: 4; }
    #body-emulate:has(.leftside.view) .col1.project_white_box.profilebox { order: 5; }
    #body-emulate:has(.leftside.view) .col1.logo,
    #body-emulate:has(.leftside.view) .col1.rotate { order: 6; }
    #body-emulate:has(.leftside.view) .creator-cta { order: 7; }

    .leftside.view { min-height: 0 !important; }
}

/* Respect the OS setting rather than forcing a long animated scroll on
   someone who asked for less motion. */
@media (prefers-reduced-motion: no-preference) {
    html {
        scroll-behavior: smooth;
    }
}

/* -------------------------------------------------------------------------
   Campaign page: description left, everything else stacked right.

   This is the layout the float version had — .leftside floated left at 616px
   and .col1.navi floated right — and it is why .control_panel is absolutely
   positioned at left: 847px inside .leftside.

   Auto-placement cannot reproduce it: .leftside is several thousand pixels
   tall, so it owns the whole first row and every sidebar box lands below it.
   The description therefore spans a large fixed number of rows. That is only
   safe with row-gap: 0 — the boxes already carry margin-bottom: 16px, so the
   spacing is unchanged, and the unused rows collapse to nothing instead of
   adding a gap each.
   ------------------------------------------------------------------------- */
@media (min-width: 1020px) {

    #body-emulate:has(.leftside.view) {
        row-gap: 0;
        /* Backfill the sidebar column. .leftside spans rows 1/span 100 in
           columns 1-2, so once the sidebar tiles run out (rewards end well
           before the description does) column 3 is left as a tall empty
           strip, and the trailing decorative tiles are pushed below the
           full-width recommendations band instead — leaving columns 1-2
           blank for another ~800px.

           Dense flow lets those later single-column tiles move up into the
           hole they fit. Measured on a long campaign: logo 3301 -> 2063,
           rotate 3717 -> 2479, total page height 4141 -> 3699.

           Safe here because every item in this layout is either a single
           column (backfillable) or spans 1 / -1 (never backfills, so the
           recommendations and CTA keep their order relative to each other
           and simply follow the tiles). Scoped to >=1020px, where this
           three-column campaign layout exists at all. */
        grid-auto-flow: row dense;
    }

    #body-emulate:has(.leftside.view) .col1,
    #body-emulate:has(.leftside.view) .col2 {
        grid-column: 3;
        margin-bottom: 16px;
    }

    #body-emulate:has(.leftside.view) .leftside {
        grid-column: 1 / 3;
        grid-row: 1 / span 100;
        margin-bottom: 16px;
    }
}

@media (max-width: 687px) {

    /* Reading order on one column. This used to lead with the status card
       ("what it raised, then what it is"). At one column that card is
       full-width and ~600px tall, so it WAS the whole first screen: the
       campaign's own image, title and pitch were entirely below the fold.
       The card is now split (see .statusbox-essentials) — the funding
       numbers ride along inside the description column right under the
       ingress, and what is left of the card (terms, Rahoita, end date,
       share) drops below the rewards as a footer. So the first screen is
       image, title, ingress, numbers; the money is still the first thing
       read, just no longer at the cost of everything else.

       Site navigation stays at the bottom — it is the least useful thing on
       a phone once you have arrived on a campaign.

       Scoped to the campaign page. Site-wide these rules would push the logo
       and the main menu to the foot of the front page and the listings, where
       they are the header rather than a leftover. */
    #body-emulate:has(.leftside.view) .col1,
    #body-emulate:has(.leftside.view) .col2,
    #body-emulate:has(.leftside.view) .col2c,
    #body-emulate:has(.leftside.view) .col3,
    #body-emulate:has(.leftside.view) .leftside { order: 4; }
    #body-emulate:has(.leftside.view) .leftside.view { order: 1; }
    #body-emulate:has(.leftside.view) .col1.stretch.rewards_container { order: 2; }
    #body-emulate:has(.leftside.view) .col1.project_white_box.statusbox { order: 3; }
    #body-emulate:has(.leftside.view) .col1.project_white_box.profilebox { order: 5; }

    /* The recommendations band and its CTA are grid items too (included
       after </campaign-page>), and with no order they defaulted to 0 —
       which sorts BEFORE every ordered item, putting "Käynnissä olevat
       kampanjat" above the campaign itself on one column. They belong
       after the campaign and its rewards, but still above the site
       navigation, so navi and the decorative tiles shift down by two. */
    #body-emulate:has(.leftside.view) .recommendations { order: 6; }
    #body-emulate:has(.leftside.view) .creator-cta { order: 7; }

    #body-emulate:has(.leftside.view) .col1.navi { order: 8; }
    #body-emulate:has(.leftside.view) .col1.logo,
    #body-emulate:has(.leftside.view) .col1.rotate { order: 9; }

    /* campaign_page.html.twig: :style="[isModeEdit ? {'min-height':'1550px'} : '']"
       on .leftside.view — inline height reservation for the editor, wrong on mobile. */
    .leftside.view { min-height: 0 !important; }

    /* Reward and profile cards: let content set the height. The frame pins
       tiles to 400px so desktop rows stay even; on one column there is no row
       to line up with, and clipped content would be the only result. */
    .col1.project_white_box { height: auto; }

    /* style.css:1797 reserves a 140px right margin on every description
       paragraph — room that makes sense in the wide desktop leftside column,
       but eats nearly half of a 335px mobile column. Long words then overflow
       what's left of it. On one column there is nothing to reserve room for. */
    .leftside p {
        margin-right: 0;
    }

    /* The dates + share icons now render a second time inside the status
       card too (campaign_page.html.twig, .statusbox-meta, right after the
       card's opening tag) — see that rule below for why. With a home in
       the status card's spare upper-right corner, this original copy (in
       .leftside, next to the title) is redundant weight on mobile: it's
       what used to force p.ingress's fixed padding-right: 150px
       (style.css:1664) to reserve room for it on every line, not just the
       few it actually overlapped. Hiding it removes that need entirely —
       simpler than the float/clear/divider-swap it took to tame it in
       place, because there's nothing left to make room for. */
    /* One column means the card fills the screen, so its preview image should
       fill the card. The 284px cap is the desktop column's content width and
       would otherwise leave a dead strip down the right of every card. */
    /* style.css:705-716 sets width:100%; max-width:284px; height:auto;
       aspect-ratio:284/189 on this image. At single-column width that
       284px cap leaves a dead strip on the right (the bug Wave 2's
       max-width:none was meant to prevent) unless width also stays 100%.
       So: keep width:100% (fill the wide card, per Wave 2 intent),
       fix the rendered height at a controlled 260px instead of letting
       an unconstrained 284:189 aspect-ratio box grow the card taller as
       it widens, and override aspect-ratio back to auto so the browser
       doesn't fight the explicit width/height before object-fit crops
       the result cleanly (using the higher-res 3x/567px srcset variant
       added alongside this rule, not the shrunk intrinsic 284x189 image). */
    .col1.project_white_box.card img {
        max-width: none;
        width: 100%;
        height: 260px;
        aspect-ratio: auto;
        object-fit: cover;
        display: block;
    }

    /* div.data (style.css:1331) is position:absolute; bottom:0 with a fixed
       283px width — it never contributed to the card's auto height. At full
       single-column width the image above can grow far taller than the
       316px-card assumption, so in-flow content (title, description) now
       runs into where the absolute footer sits. Put it back in normal flow;
       the ul/li rules underneath (style.css:1343-1373) still apply. One
       column has no row to line up with (unlike >=688px, see the fixed
       416px height further up), so there is no downside to letting it
       flow here — every card is its own row regardless. */
    .col1.project_white_box.card div.data {
        position: static;
        width: auto;
        float: none;
        padding-top: 0;
        margin-top: 12px;
    }

    /* The card's own 16px padding-bottom (style.css:561) used to sit
       "inside" the old absolute .data footer, invisible in practice. Now
       that .data flows normally here and already adds 12px of its own
       breathing room above, the standard 16px below it is surplus.
       Scoped to :has(div.data) — the LEGACY card. The redesigned card
       (campaign_index_parts/campaign_card.html.twig) has no div.data; its
       last in-flow line (.grid-card-time, "N päivää jäljellä") needs the
       16px below it, so it must keep the base padding, not this zero. */
    .col1.project_white_box.card:has(div.data) { padding-bottom: 0; }

    /* Checkout form: full-width fields, 16px text so iOS does not zoom. */
    #pledgeform input,
    #pledgeform select,
    #pledgeform textarea {
        width: 100%;
        font-size: 16px;
        box-sizing: border-box;
    }

    /* Widening the fields alone is not enough. style.css has
       `form p { width: 384px }`, sized for the old 616px column, and a
       fieldset's UA min-width is min-content — so the helper paragraph set a
       398px floor on every row and the form overflowed whatever the inputs
       did. Release both. */
    #pledgeform fieldset {
        min-width: 0;
        max-width: 100%;
    }

    #pledgeform p {
        width: auto;
        max-width: 100%;
    }

    /* Tables in CMS content scroll rather than widen the page. */
    .entry table,
    .blog-post table {
        display: block;
        overflow-x: auto;
        max-width: 100%;
    }

    /* =====================================================================
       Campaign editor — one field style, one font                (2026-09)

       The description-column edit form grew three input treatments over the
       years and two fonts inside them:
         - name field  : no rule sets a border, so the browser default shows
         - video URL + short-description textarea : border:0 (style.css:3600)
         - date pickers : border:none, font:inherit (style.css:40)
       plus adelle (serif) vs adelle-sans drift between them.

       Normalise every text field in .leftside.view to one bordered box on
       one font. Scoped to the fields the editor actually types into — the
       Quill long-description editor is a <div>, not matched. Desktop (the
       floating .info-edit card, the wide two-column form) is untouched:
       every rule here is inside the 687px query.
       ===================================================================== */
    .leftside.view .post input[type="text"],
    .leftside.view .post textarea,
    .leftside.view .info-edit .datepicker-input,
    .leftside.view .info-edit-2 .datepicker-input,
    .leftside.view > div > form input[name="video_url"] {
        box-sizing: border-box;
        width: 100%;
        margin: 0;
        padding: 8px 10px;
        background-color: #e7f4f6;
        border: 1px solid var(--ms-ink-400);
        border-radius: 0;
        font: 400 15px/1.4 var(--font-sans);
        color: #333;
    }

    /* Single-line fields get a fixed height and a sane cap so they read as
       fields, not full-bleed strips; the textarea keeps its own height. */
    .leftside.view .post input[type="text"],
    .leftside.view .info-edit .datepicker-input,
    .leftside.view .info-edit-2 .datepicker-input,
    .leftside.view > div > form input[name="video_url"] {
        height: 38px;
        max-width: 20rem;
    }

    /* The video-URL and name inputs sit in bare <form>s with no inset, so
       they start 14px left of their own <h2> label and of every field
       below them (.leftside h2/p carry margin-left:14px, style.css:2470;
       .info-edit adds its own 14px). Give them the same inset so the whole
       column's left edge lines up. */
    .leftside.view .post input[name="title"],
    .leftside.view > div > form input[name="video_url"] {
        margin-left: 14px;
        margin-right: 14px;
        width: calc(100% - 28px);
    }

    /* Edit-mode date pickers. style.css:107/120 pins div.info-edit /
       div.info-edit-2 with position:absolute; right:30px; max-width:200px
       (and .info-edit-2 a hardcoded top:750px) so they float in the wide
       column's right margin. One column has no margin — they landed on the
       description textarea and the KAMPANJA/VASTIKKEET tabs. Put them back
       in normal flow, aligned and spaced like every other edit field.
       (View mode's div.info is already display:none above, replaced by
       .statusbox-meta.) */
    .leftside div.info-edit,
    .leftside div.info-edit-2 {
        position: static;
        right: auto;
        top: auto;
        max-width: none;
        padding-top: 0;
        margin: 0 14px 6px;   /* 14px = .leftside h2/p inset (style.css:2470) */
    }
    .leftside div.info-edit h2,
    .leftside div.info-edit-2 h2 {
        margin: 10px 0 4px;   /* label gap, not .leftside h2's 22px section gap */
        font-size: 15px;
    }
    .leftside div.info-edit .datepicker-parent,
    .leftside div.info-edit-2 .datepicker-parent {
        margin: 0;
        padding: 0;
    }

    /* Save / Reset across the editor carry :disabled="!isModified" and
       otherwise sit as a permanent greyed row under every field. Hide them
       until the field is actually edited — same "actions appear on edit"
       rule as the image-upload buttons. */
    .leftside.view .button.submit:disabled {
        display: none;
    }

    /* The date and video forms end with <div><small>&nbsp;</small></div>, a
       desktop layout spacer that on one column is just a blank line under
       the field. Collapse it, but let a real validation message through. */
    .leftside.view div.info-edit form > div:last-child,
    .leftside.view div.info-edit-2 form > div:last-child,
    .leftside.view > div > form > div:last-child {
        line-height: 0;
    }
    .leftside.view div.info-edit form > div:last-child small,
    .leftside.view div.info-edit-2 form > div:last-child small,
    .leftside.view > div > form > div:last-child small {
        line-height: 1.3;
    }
}

/* -------------------------------------------------------------------------
   Vastikekortit kolmeen palstaan (Wave 4).

   #vastikkeet (rewards_container) on yksi tavallinen .col1-ruudukkosolu
   (~316px), ja sen sisällä yksittäiset <reward>-elementit pinoutuvat
   pystyyn saman solun sisällä. Ongelma ei ole ulompi ruudukko vaan se,
   ettei rewards_container koskaan levene eikä sen sisältö jakaudu
   palstoihin. Levennetään solu .col3:n tapaan ja jaetaan sisältö omaksi
   flex-ruudukokseen.
   ------------------------------------------------------------------------- */
/* Campaign pages (#body-emulate:has(.leftside.view) .col1 in the block above,
   ~line 120) force every .col1/.col2 tile to grid-column: 3 at >=1020px so
   they stack in the single right-hand column next to the description. That
   selector is #body-emulate:has(.leftside.view) .col1 — specificity
   (1 id, 3 classes) once :has()'s argument is counted. Plain
   .col1.stretch.rewards_container (0 ids, 3 classes) always loses that
   fight, so the span rule below never took effect on a real campaign page.
   Matching on #vastikkeet directly restores the id and wins.

   grid-column: span 3 (with an implicit auto start) told the browser to
   find 3 free columns wherever they first occur — and columns 1-2 are
   reserved for .leftside across its whole grid-row: 1 / span 100 (~line
   205), so the first 3-wide gap is only AFTER row 100, i.e. below the
   entire two-column description. That pushed rewards out of the sidebar
   stack entirely, past every other .col1 sidebar box, instead of
   continuing that stack right after .profilebox like every other .col1
   here does. Anchoring the start at column line 3 keeps it out of
   .leftside's reserved columns without searching for free space, so it
   auto-places into the very next open row in column 3 — same as its
   sidebar neighbors. The end (-1, the last column line) still lets it
   widen past a single column on screens with more than 3 tracks (see
   style.css:445-448's 5-column wrapper-wide). */
@media (min-width: 1020px) {
    #vastikkeet.col1.stretch.rewards_container { grid-column: 3 / -1; }
}

@media (min-width: 688px) and (max-width: 1019px) {
    #vastikkeet.col1.stretch.rewards_container { grid-column: span 2; }
}

/* <reward-index> and <reward> are Vue 2 inline-template components: Vue
   replaces the custom tag with the root node of its own template rather
   than keeping the tag name, so neither "reward-index" nor "reward" ever
   appears in the rendered DOM. reward-index's template root is a plain
   <div>, and each reward.html.twig / level.html.twig root is also a plain
   <div>, nested one level deeper as #vastikkeet > div > div. Select on that
   structure instead of the (never-matching) custom tag names. */
/* Wave 5: masonry instead of equal-height rows. The flex-wrap row layout
   this replaces stretched every card in a row to match its tallest sibling
   (flexbox's default align-items: stretch), so a short reward sat in a box
   padded out with dead space to line up with a tall one next to it. CSS
   multi-column lets each card keep its own natural height and flow into
   whichever column has room next — cards of different heights can sit side
   by side without either faking a shared height or leaving a gap.

   Gated behind 1352px on purpose. #vastikkeet's own grid cell (grid-column:
   3 / -1, ~line 362) is a single ~316-380px track from 1020px up to
   1351px — 3 total grid columns in that range, and #vastikkeet only ever
   starts at column 3. columns: 2 with no floor was splitting that one
   narrow track into ~150-190px slivers (title text wrapping three lines
   deep). A 4th grid column, and with it #vastikkeet's second track, only
   exists from 1352px (see the threshold note further up), so that's where
   two-up first has room to not be cramped. Below it, cards stay one to a
   row — the same single column the mobile/narrow layout already uses. */
#vastikkeet > div {
    column-gap: 16px;
}

/* Two-up only when the reward CELL is actually wide enough for it — a
   container query on #vastikkeet's own inline size (see container-type
   below), not the viewport.

   The old gate was @media (min-width: 1352px), which keyed off the viewport
   on the assumption that a >=1352px viewport meant #vastikkeet had grown a
   second grid track. That only holds on layout_wide pages (#wrapper.wrapper-
   wide, max-width 1668px -> up to 5 tracks). An individual campaign page is
   NOT layout_wide (routes.php only sets it for the index and the campaign
   listing), so its #wrapper stays capped at 1004px = always exactly 3 grid
   tracks at every viewport, and #vastikkeet (grid-column: 3 / -1) is a
   single ~316px track forever. On such a page a >=1352px viewport still
   triggered columns: 2, splitting that one 316px track into two ~150px
   slivers — long reward titles then overflow their column and paint over
   the neighbouring one (the "vastikepalsta menee rikki ja sisäkkäin" bug).

   Keying on the cell's real width fixes it at the source: 316px stays one
   column; only a genuinely wide cell (two tracks, ~648px) goes two-up. In
   browsers without @container support the query simply never matches, so
   the safe single-column layout is also the fallback. */
@container (min-width: 640px) {
    #vastikkeet > div {
        columns: 2;
    }
}

/* column-span: all instead of flex: 1 1 100% — multi-column has no row
   concept to fill, so a title needs to explicitly break out of the 2-col
   flow to sit above both of them rather than becoming a very short column
   1 with column 2 starting beside it. */
#vastikkeet > div > h1,
#vastikkeet > div > .addReward {
    column-span: all;
}

/* The "show all N" link closes out the capped list (~line 946 in
   campaign_page.html.twig) — same column-span: all reasoning as the
   heading above, so it reads as a footer for the whole section rather
   than getting stuck at the bottom of whichever of the 2 columns happens
   to be shorter. A prominent turquoise CTA (the design system's
   secondary action colour, white on #127A85 ~5:1 AA) rather than the orange
   .button used for "Rahoita" on every card — this isn't a funding action,
   it's a wayfinding one, and repeating the same orange CTA here would
   make it compete with (or get mistaken for) the real ones above it. */
#vastikkeet > div > a.reward-show-all {
    column-span: all;
    display: block;
    box-sizing: border-box;
    width: 100%;
    height: auto;
    margin: 12px 0 0;
    padding: 13px 18px;
    background: #127A85;
    color: #fff;
    border: 0;
    border-radius: 4px;
    font-family: 'Adelle Sans', 'Source Sans 3', Helvetica, Arial, sans-serif;
    font-size: 17px;
    font-weight: 600;
    line-height: 1.2;
    text-align: center;
    text-transform: none;
}

#vastikkeet > div > a.reward-show-all:hover {
    background: #0E5F68;
    color: #fff;
}

/* Each reward's root div (reward.html.twig / level.html.twig) had no card
   styling of its own — it relied on the old stacked single-column layout's
   <hr> (style.css:1310, 8px solid #addfe5, a FIXED width:312px) to read as
   a separator between items. With no background/padding of its own, the
   card was just floating text with a stray blue line under it, not a card;
   the white card background below does that job now instead.

   break-inside: avoid keeps a card from being sliced across the column
   break (the browser would otherwise happily split it mid-description).
   margin-bottom supplies the vertical gap between cards stacked in the
   same column — columns only add horizontal gap (column-gap above), not
   a row-gap equivalent. */
#vastikkeet > div > div {
    display: flex;
    flex-direction: column;
    break-inside: avoid;
    width: 100%;
    box-sizing: border-box;
    background: #fff;
    border-radius: 3px;
    padding: 16px;
    margin-bottom: 16px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .12);
}

#vastikkeet > div > div hr {
    display: none;
}

/* .col1's own white background (style.css:556) doubles as this container's
   background too, so the 16px flex gap between cards (line 365 above) shows
   white — indistinguishable from the cards sitting on it, which is why the
   grid read as one slab rather than separate cards. Each reward now paints
   its own white card (above), so let the container go transparent instead
   and expose the page's turquoise (body, style.css:243) through the gaps —
   the same "card floats on turquoise" look the campaign-listing cards
   (.col1.project_white_box) already have against #body-emulate. */
#vastikkeet.col1.stretch.rewards_container {
    background: transparent;
    /* Query container for the two-up rule above: the reward cards ask
       #vastikkeet's own inline size whether there's room for a second
       column, instead of guessing from the viewport. inline-size contains
       only the horizontal axis, so the cell still grows in height with its
       content and is still sized/placed by the outer grid (grid-column:
       3 / -1) exactly as before. */
    container-type: inline-size;
}

/* style.css:1229 gives the section title its signature turquoise/uppercase
   treatment (matching h2 elsewhere), assuming it always sits on a white
   .col1 background. Now that the container above is transparent, the same
   turquoise text sits directly on the page's turquoise — unreadable. Give
   the title its own white chip, in the same card language as the reward
   boxes below it, instead of recoloring the text. */
#vastikkeet.col1.stretch.rewards_container > div > h1 {
    background: #fff;
    border-radius: 3px;
    padding-right: 16px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .12);
    box-sizing: border-box;
}

/* style.css:1252/1271 pad each reward's own h2/p by 16px on the left and
   right — sized for the old single-column reward div, which carried no
   padding of its own and relied on this text-level inset instead. Now
   that div carries a real 16px card padding (above), the two insets stack
   to 32px, and style.css:2268's fixed 286px button width (sized for that
   same zero-padding ~316px column) no longer fits the ~268px left after
   both insets — it pushes past the card's right and bottom edges instead
   of wrapping inside it. Zero out the old text inset and let the button
   size to the card's own content box. */
#vastikkeet > div > div h2,
#vastikkeet > div > div p {
    padding-left: 0;
    padding-right: 0;
}

/* style.css:1274 zeroes every margin on these paragraphs (sized for the old
   layout, where the fixed-width column supplied all spacing via padding).
   The title (h2) and description (p.pre-wrap) therefore sat flush against
   each other with no gap. */
#vastikkeet > div > div p.pre-wrap {
    margin-top: 10px;
}

/* style.css:2276's margin-top: 0.5em only pushes the button down from
   whatever content happens to precede it — a card with a short description
   and no stock line ends up with the button floating mid-box, while a
   longer card pins it near the bottom. margin-top: auto on the button
   (below) still absorbs any leftover height a stretched sibling might
   have, but in a masonry layout a card's height IS its content's height
   most of the time, so there's rarely free space for auto to consume —
   without a floor the button can end up flush against the text above it.
   margin-bottom here guarantees at least 10px no matter what auto resolves
   to; flexbox margins between items don't collapse, so the two add up
   instead of the larger one winning. */
#vastikkeet > div > div p.pre-wrap,
#vastikkeet > div > div p.product_info {
    margin-bottom: 10px;
}

#vastikkeet > div > div a.button {
    width: 100%;
    margin-left: 0;
    margin-top: auto;
    flex-shrink: 0;
    box-sizing: border-box;
}

/* =========================================================================
   Campaign page, mobile (6A).

   Scoped to <=687px, the frame's single-column threshold. The two-column
   (688-1019) and desktop (>=1020) layouts are balanced as they are and
   are deliberately left alone — the problem 6A solves is specific to the
   one-column case, where a full-width status card pushes the campaign
   itself off the first screen.

   Two specificity notes, both load-order-proof rather than relying on
   which <link> comes last (header.html.twig loads MesenaattiAgitator.css
   AFTER responsive.css, so equal-specificity rules here would lose):

   - .leftside h1's uppercase (style.css:2314, 0-1-1) is beaten by
     .leftside.view h1 (0-2-1).
   - .main_image / .main_image img (MesenaattiAgitator.css:60/69, 0-1-0 and
     0-1-1) are beaten by prefixing .leftside.view (0-2-0 / 0-2-1).
   ========================================================================= */

/* The inline half of the split status card is one-column-only; from 688px
   up the card itself carries these rows, exactly as before. Declared
   outside the media query so the markup can never leak into the wide
   layout the way the tabs did. */
.statusbox-essentials--inline {
    display: none;
}

/* Tabs are a mobile-only affordance: on desktop the rewards live in their
   own visible column, so there is nothing to tab between. Default hidden
   so the markup never leaks into the wide layout as two stray links. */
.campaign-tabs {
    display: none;
}

@media (max-width: 687px) {

    /* --- 2. Hero image: full card width, 16:10, cover ------------------- */
    .leftside.view .main_image {
        max-width: none;
        aspect-ratio: 16 / 10;
    }

    /* The base rule is height:100%/min-width:100%, which stretches the
       photo to the box. object-fit crops it instead. */
    .leftside.view .main_image img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
    }

    /* --- 3. Title: sentence case, 17px --------------------------------- */
    /* text-transform: none, NOT lowercase — the title is real campaign copy
       and lowercasing it would destroy proper nouns ("Fighting Fantasy"
       -> "Fighting fantasy"). Removing the uppercase is the whole fix. */
    .leftside.view h1 {
        text-transform: none;
        font-family: var(--font-display);
        font-size: 17px;
        line-height: 1.2;
        margin-top: 16px;
        margin-bottom: 8px;
    }

    /* --- 4. Ingress: 13px, secondary ink (cyan fails contrast here) ----- */
    .leftside.view p.ingress {
        font-family: var(--font-sans);
        font-size: 13px;
        line-height: 1.45;
        color: var(--fg-2);
        margin-bottom: 16px;
    }

    /* --- 5. Status, first half ------------------------------------------
       The funding numbers move up into the description column, directly
       under the ingress, so image + title + ingress + numbers all land on
       the first screen. The card's own copy goes away at this width; what
       remains of the card (terms, stats, Rahoita, end date, share) is
       ordered below the rewards as the page footer.

       Side margins match style.css:2346's 14px on .leftside h1/p so the
       dark block lines up with the title and ingress above it. Every rule
       for the rows inside it (.statusbox-amount-number and friends,
       style.css:907-1070) is a plain single-class selector written for a
       dark ground, so the inline copy picks them up untouched — it only
       needs the ground itself. */
    .statusbox-essentials--card {
        display: none;
    }

    .statusbox-essentials--inline {
        display: block;
        background: var(--bg-inverse);
        margin: 0 14px 18px;
        padding: 10px 14px 14px;
    }

    /* --- 6. Tabs -------------------------------------------------------- */
    .campaign-tabs {
        display: flex;
        gap: 0;
        margin: 0 14px;
        border-bottom: 1px solid var(--border);
        /* A long localisation must not push the row past the card. */
        overflow-x: auto;
    }

    .campaign-tabs .campaign-tab {
        padding: 10px 14px;
        font-family: var(--font-display);
        font-size: 12px;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        text-decoration: none;
        white-space: nowrap;
        color: var(--fg-2);
    }

    .campaign-tabs .campaign-tab--active {
        color: var(--ms-ink-1000, #1a1a1a);
        box-shadow: inset 0 -3px 0 var(--brand);
    }

    /* --- 7. Long description ------------------------------------------- */
    /* The <p> children must be named explicitly: style.css's bare "p"
       (16px/22px) and ".leftside p" both beat a font-size merely inherited
       from .entry, so setting it on the container alone left every
       paragraph at 16px. .leftside.view .entry p (0-3-1) clears both. */
    .leftside.view .entry {
        padding-top: 14px;
    }

    .leftside.view .entry,
    .leftside.view .entry p,
    .leftside.view .entry li {
        font-size: 13px;
        line-height: 1.55;
    }

    /* --- 8. Rewards list ------------------------------------------------
       DOM (campaign_page.html.twig:1214 + parts/reward.html.twig):
         #vastikkeet                     <- IS .rewards_container, not a parent
           > div                         <- reward-index inline-template root
               > h1                      <- "Vastikkeet"
               > div                     <- one reward  <= the row
                   > p > img.vastikekuva <- optional thumbnail
                   > a > h2              <- name <br> amount
                   > p.pre-wrap          <- description
                   > a.button            <- Rahoita
                   > hr
               > a.reward-show-all

       Earlier attempt used "#vastikkeet .rewards_container > div > div",
       which cannot match: #vastikkeet carries .rewards_container itself,
       so a descendant combinator looks for a second one inside it. */
    #vastikkeet > div > div {
        display: grid;
        grid-template-columns: 1fr;
        column-gap: 12px;
        align-items: start;
        padding: 12px 0;
        border-bottom: 1px solid var(--border);
    }

    /* The thumbnail column is opt-in. Verified against four live campaigns:
       most rewards carry no image_path at all, and an unconditional
       "64px 1fr" reserved 64px + 12px gap of a 335px row for nothing on
       every one of them. :has() is already used throughout this file. */
    #vastikkeet > div > div:has(> p:first-child img) {
        grid-template-columns: 64px 1fr;
    }

    #vastikkeet > div > div:last-of-type {
        border-bottom: none;
    }

    /* Thumbnail spans every row of the right-hand stack; everything else is
       pushed to column 2. Both are scoped to the has-image case, so an
       image-less reward stays a plain single-column stack with no phantom
       second track. */
    #vastikkeet > div > div:has(> p:first-child img) > p:first-child {
        grid-column: 1;
        grid-row: 1 / -1;
        margin: 0;
    }

    #vastikkeet > div > div:has(> p:first-child img) > *:not(p:first-child) {
        grid-column: 2;
    }

    #vastikkeet > div > div .vastikekuva {
        width: 64px;
        height: 64px;
        object-fit: cover;
        display: block;
        border-radius: 0;
    }

    #vastikkeet > div > div > a,
    #vastikkeet > div > div > p,
    #vastikkeet > div > div > button {
        margin-left: 0;
        margin-right: 0;
    }

    #vastikkeet > div > div > a h2 {
        font-family: var(--font-display);
        font-size: 13px;
        line-height: 1.3;
        text-transform: none;
        margin: 0;
    }

    /* Description is the one thing with no room on a 64px-thumbnail row. */
    #vastikkeet > div > div > p.pre-wrap {
        display: none;
    }

    #vastikkeet > div > div > p.product_info {
        font-size: 11px;
        margin: 4px 0 0;
    }

    #vastikkeet > div > div > a.button {
        justify-self: start;
        width: auto;
        margin-top: 8px;
        padding: 7px 14px;
        border-radius: 0;
        font-size: 12px;
        text-transform: uppercase;
        letter-spacing: 0.04em;
    }

    /* The row's own border-bottom is the separator now. */
    #vastikkeet > div > div > hr {
        display: none;
    }

    /* No cards. responsive.css:704 gives every reward its own white box
       (radius + 16px padding + shadow + 16px margin) because the desktop
       layout floats them as a multi-column masonry on a transparent
       container. On one column that reads as four disconnected cards
       separated by turquoise gutters. Move the white to the container so
       the list is a single panel, and let each row's border-bottom be the
       only divider. */
    #vastikkeet.col1.stretch.rewards_container > div {
        background: #fff;
        padding: 0 16px 8px;
        box-sizing: border-box;
    }

    /* With the panel white again, the title no longer needs its own chip
       (added at :746 for the transparent-container case). */
    #vastikkeet.col1.stretch.rewards_container > div > h1 {
        background: transparent;
        box-shadow: none;
        border-radius: 0;
        padding-left: 0;
        padding-right: 0;
    }

    #vastikkeet > div > div {
        background: transparent;
        border-radius: 0;
        box-shadow: none;
        padding: 12px 0;
        margin-bottom: 0;
    }
}

/* -------------------------------------------------------------------------
   Yläkorttien karsinta kapeilla näytöillä (2026-08)

   Sivun kolme ensimmäistä ruutua ovat tunnuskortti, kolibrikortti ("Luo /
   Rahoita / Mahdollista") ja valikkokortti. Työpöydän kolmen palstan
   ruudukossa ne mahtuvat yhdelle riville ja toimivat sivun ylätunnisteena.
   Kaksi palstaa alaspäin ne eivät enää ole ylätunniste vaan pino: jokainen
   vie oman rivinsä, ja lukija selaa kolme brändiruutua ennen kuin pääsee
   siihen mitä tuli lukemaan.

   Karsinta on porrastettu sen mukaan paljonko tilaa on:
     688-1019px (2 palstaa)  tunnuskortti pois          -> säästyy 1 rivi
     <= 687px   (1 palsta)   myös kolibrikortti pois     -> säästyy 2 riviä

   ETUSIVU ON POIKKEUS molemmissa. Siellä nämä eivät ole toistoa vaan sivun
   varsinainen sisältö: tunnuskortti kantaa alustan luvut (7D-kortti) ja
   kolibrikortti on etusivun kolme reittiä.

   Tunnistus tapahtuu markupista eikä body-luokasta, koska sellaista ei ole:
   etusivu renderöi parts/stats_tile.html.twig:n (.col1.logo.stats-tile) ja
   kaikki muut parts/logo.html.twig:n (.col1.logo ilman .stats-tile). Sama
   ero kertoo kolibrikortille onko se etusivulla — siksi :has(). Jos selain
   ei tue :has():ia, sääntö hylätään kokonaan ja kortti jää näkyviin, eli
   pahin tapaus on nykyinen ulkoasu.

   Rajat 687/1019 eivät ole arvattuja: ruudukon container on viewport - 40px,
   kolme 316px:n raidetta + kaksi 16px:n väliä vaatii 980px (viewport 1020) ja
   kaksi raidetta 648px (viewport 688). Mitattu selaimesta. */

@media (max-width: 1019px) {
    .col1.logo:not(.stats-tile) {
        display: none;
    }
}

@media (max-width: 687px) {
    #body-emulate:not(:has(.stats-tile)) .col1.routes {
        display: none;
    }
}
