/* Plucky Works marketing page layout, loaded only by marketingbase.html.
   Design tokens and the brand font come from marketing_chrome.css, which is
   always loaded first. Design spec:
   docs/superpowers/specs/2026-08-02-marketing-site-visual-design.md */
* { box-sizing: border-box; }
/* Sticky-footer shell: on short pages (demo confirmation) main absorbs the
   leftover viewport height, so the footer stays a compact strip at the bottom
   instead of the page ending mid-viewport with the ground color pooling into
   the footer. Marketing pages only; the Bootstrap blog pages load chrome CSS
   but not this file. */
body { margin: 0; font-family: var(--pw-font); color: var(--pw-text); background: var(--pw-ground); font-size: 16.5px; line-height: 1.65; overflow-x: clip; min-height: 100vh; display: flex; flex-direction: column; }
.wrap { max-width: 1120px; margin: 0 auto; padding: 0 24px 64px; }
main.wrap { flex: 1 0 auto; width: 100%; }
/* The ink footer sits flush on every marketing page: a closing band lands
   right on the footer hairline, and a plain ending keeps its breathing room
   from .wrap's own bottom padding. (The 72px chrome default is for the blog
   pages, which have no .wrap padding of their own.) */
body > footer.sitefoot { flex-shrink: 0; margin-top: 0; }
/* The nav is sticky, so in-page anchors need clearance or they land underneath it. */
[id] { scroll-margin-top: 96px; }
/* Line breaking. Headings balance: a heading in a narrow card column otherwise
   breaks wherever the line happens to run out ("Explore new revenue /
   opportunities"), which reads as a mistake rather than a line. Balancing
   evens the lines instead. Prose gets `pretty`, which only reflows the last
   couple of lines to clear a one-word orphan ("...in print or / online."). */
h1, h2, h3 { text-wrap: balance; }
p, ul.plain li { text-wrap: pretty; }
/* Leads and subs are decks, not body copy: 2-4 lines sitting under a heading.
   Left to `pretty` they run the full 68ch measure and then end on a stub of a
   line (13-41% of the longest), which is the wrapping that reads as broken.
   Balancing spreads them over even lines, which also pulls the used measure
   from ~75 characters down toward the 45-75 range. Chromium stops balancing
   past 6 lines, so a lead that ever grows that long degrades to `pretty`. */
.lead, .sub { text-wrap: balance; }
h1 { color: var(--pw-ink); font-size: 46px; font-weight: 800; letter-spacing: -0.025em; line-height: 1.1; margin: 0 0 18px; }
h2 { color: var(--pw-ink); font-size: 30px; font-weight: 800; letter-spacing: -0.015em; line-height: 1.15; margin: 0 0 16px; }
h3 { color: var(--pw-ink); font-size: 21px; font-weight: 800; line-height: 1.25; margin: 0 0 10px; }
p { margin: 0 0 12px; }
main a { color: var(--pw-red-text); font-weight: 700; text-decoration: none; }
main a:hover { color: var(--pw-red-hover); text-decoration: underline; }
/* No italics: Manrope has no italic face, and muted color alone carries the aside tone. */
.sub { font-style: normal; font-size: 14px; font-weight: 500; color: var(--pw-muted); margin: 0 0 12px; }
.lead { font-size: 18px; margin: 0 0 20px; }
/* Prose caps at a readable measure so no line runs the full 1120px column. */
.lead, .sub, .hero p, .ztext p, .qa .a, .qa ul.plain, .card > p { max-width: 68ch; }
/* Opt a lead paragraph out of the readable-measure cap to span the full section width. */
.lead.lead-full { max-width: none; }
/* Sections carry the page's vertical rhythm: the gap between two sections always reads larger than any gap inside one. */
.section { margin-top: 64px; } .section:first-child { margin-top: 0; } .section > :last-child { margin-bottom: 0; } .section > :first-child { margin-top: 0; }
.hero { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 48px; align-items: center; }
.hero.single { display: block; }
.hero > :last-child, .herotext > :last-child { margin-bottom: 0; }
.narrow { max-width: 620px; }
/* Single centered column for section content that has no second column beside
   it: an uncentered .narrow block strands the right half of a wide viewport, so
   a lone card reads as a grid that lost its other cards. Centering the heading
   and the card together makes the whitespace symmetric and deliberate. Text
   inside stays left-aligned. */
.single-col { max-width: 620px; margin-left: auto; margin-right: auto; }
main :focus-visible { outline: 2px solid var(--pw-ink); outline-offset: 2px; }
/* --- cards and grids --- */
.card { background: var(--pw-surface); border: 0; border-radius: var(--pw-radius-card); box-shadow: var(--pw-shadow-card); padding: 24px; margin-bottom: 16px; display: flex; flex-direction: column; }
.card > :last-child { margin-bottom: 0; }
.card h3 { font-size: 17px; }
.grid { display: grid; gap: 20px; } .g2 { grid-template-columns: 1fr 1fr; } .g3 { grid-template-columns: 1fr 1fr 1fr; } .g4 { grid-template-columns: 1fr 1fr 1fr 1fr; } .grid .card { margin-bottom: 0; }
/* Availability chip beside a title. The fill state carries the meaning:
   solid ink = shipped, hollow outline = pre-release, so no color leaves the
   neutral palette. Every badge site sits on band-white, where the old ground
   fill vanished; the border is what makes the hollow chip read as a chip.
   Transparent fill keeps --pw-muted text at 7:1 on white. The solid variant
   keeps the same 1px border (ink-on-ink) so both chips are the same height.
   letter-spacing must be reset: the h1's -0.025em inherits as its computed
   value (-1.15px at 46px), which at 13px all but deletes the word space. */
.status-badge { border: 1px solid var(--pw-dark-body); border-radius: 999px; background: transparent; color: var(--pw-muted); display: inline-block; font-size: 13px; line-height: 1.5; font-weight: 700; letter-spacing: normal; padding: 2px 10px; margin-left: 8px; vertical-align: 3px; white-space: nowrap; }
.status-badge.available { background: var(--pw-ink); border-color: var(--pw-ink); color: #fff; }
/* Inside home-card titles the chip drops onto its own line: "Assignments"
   plus the chip overflows a g4 column, and one wrapped chip in a row of four
   inline ones reads as a mistake. Below the title, all four align. */
.card h3 .status-badge { display: block; width: fit-content; margin: 6px 0 0; }
/* --- placeholders as quiet browser-frame cards --- */
.imgbox { position: relative; overflow: hidden; background: var(--pw-fill); border: 0; border-radius: var(--pw-radius-frame); box-shadow: var(--pw-shadow-card); color: var(--pw-text); font-style: normal; font-size: 13px; padding: 44px 16px 16px; min-height: 220px; display: flex; align-items: center; justify-content: center; text-align: center; margin-top: 16px; }
.imgbox::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 28px; background: var(--pw-surface); border-bottom: 1px solid var(--pw-fill); }
.imgbox::after { content: ""; position: absolute; top: 11px; left: 14px; width: 6px; height: 6px; border-radius: 50%; background: var(--pw-dark-body); box-shadow: 12px 0 0 var(--pw-dark-body), 24px 0 0 var(--pw-dark-body); }
.hero-visual { min-height: 320px; margin-top: 0; box-shadow: var(--pw-shadow-float); }
.zart.imgbox { margin-top: 0; box-shadow: var(--pw-shadow-float); }
/* Section graphics are drawings, not screens: the frame's browser chrome would
   be a lie on one, and each drawing carries its own ground, so the frame drops
   to a plain white card that clips and shadows it. White, not the drawing's own
   cream, because the drawings round their own corners against white. */
.imgbox.has-graphic { display: block; padding: 0; min-height: 0; background: var(--pw-surface); }
.imgbox.has-graphic::before, .imgbox.has-graphic::after { display: none; }
.imgbox.has-graphic img { display: block; width: 100%; height: auto; }
/* A frame holding a real screenshot rather than placeholder text: the image
   runs edge to edge under the chrome bar, so the frame reads as a window onto
   the product instead of a mat around a picture. The placeholder min-height
   and centering exist to give an empty box a shape; a real image sets its own. */
.imgbox.has-shot { display: block; padding: 28px 0 0; min-height: 0; }
.imgbox.has-shot img { display: block; width: 100%; height: auto; }
/* Hero tool tiles (home): four equal tiles, one per tool, so the four-tool
   structure reads before any body copy. Each tile holds the tool's wordmark
   set in Plucky Script in brand red on the quiet ground-and-fill treatment.
   The min-height keeps the two-by-two block tall enough to hold its own
   against the headline column. The wordmark tracks the viewport because the
   tile does: 3.6vw keeps "Assignments" (4.31em of glyph advance) inside the
   tile at every two-column width, capped at 40px once the layout maxes out. */
.hero-gallery { display: grid; position: relative; grid-template-columns: 1fr 1fr; gap: 16px; }
.tool-tile { display: flex; align-items: center; justify-content: center; min-height: 148px; padding: 36px 16px; background: var(--pw-ground); border-radius: var(--pw-radius-card); box-shadow: inset 0 0 0 1px var(--pw-fill); }
.tool-wordmark { font-family: "Plucky Script", var(--pw-font); font-weight: 400; font-size: clamp(24px, 3.6vw, 40px); line-height: 1; color: var(--pw-red); }
/* The hub: a white circle floated over the center of the two-by-two grid,
   covering all four inner tile corners, with the Plucky Works bird inside.
   It says the four tools are one ecosystem with Plucky Works at the middle.
   The diameter scales with the gallery (11vw across the two-column range) and
   is capped low enough that the circle never reaches the "Assignments"
   wordmark, whose inner edge passes ~72px from the gallery center at the
   tightest two-column width. The bird's width is set against the circle;
   its 420x708 intrinsic ratio makes that ~54% of the diameter tall. */
.hero-hub { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); display: flex; align-items: center; justify-content: center; width: clamp(88px, 11vw, 124px); aspect-ratio: 1; border-radius: 50%; background: var(--pw-surface); box-shadow: inset 0 0 0 1px var(--pw-fill), var(--pw-shadow-card); }
.hero-hub img { display: block; width: 32%; height: auto; }
/* Mini placeholders inside cards: flat fill, no frame chrome. */
.grid .card > .imgbox { margin-top: auto; min-height: 160px; box-shadow: none; padding: 16px; background: var(--pw-ground); }
.grid .card > .imgbox::before, .grid .card > .imgbox::after { display: none; }
/* Card thumbnails have no chrome bar, so the screenshot fills the whole frame. */
.grid .card > .imgbox.has-shot { padding: 0; min-height: 0; }
/* Card thumbnails. Three whites stacked (band, card, screenshot) left the
   first pass with no edges at all, so the frame carries a hairline and the
   card shadow: enough to read as an object on white, quiet enough to stay
   below the heading and the link in the card's order of importance.

   Inside the frame, a fixed 16:10 window shows the one element that identifies
   the tool (the coverage map, a day of events, the assignment steps, the story
   list); at this size a whole app screen is unreadable texture. --shot-zoom is
   how many frame widths the image spans; --shot-x / --shot-y are the point of
   the image parked at the center of the frame. All three are set inline per
   screenshot, since the telling part sits somewhere different in each one.
   Sizing the image up in layout (rather than transform: scale) keeps it
   rendering at full resolution; the transform only moves it. */
.grid .card > .imgbox.shot-zoom {
    aspect-ratio: 16 / 10;
    padding: 0;
    background: var(--pw-surface);
    border: 1px solid var(--pw-fill);
    border-radius: 10px;
    box-shadow: var(--pw-shadow-card);
}
/* Outranks the .imgbox.has-shot img fit rule above, which would otherwise
   size the image to the frame and undo the zoom. */
.grid .card > .imgbox.shot-zoom img {
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(var(--shot-zoom) * 100%);
    max-width: none;
    height: auto;
    transform: translate(calc(-1 * var(--shot-x)), calc(-1 * var(--shot-y)));
}
/* The thumbnail sits above the link and takes the card's slack (.imgbox
   already has margin-top: auto), so the links line up across the row however
   long each card's copy runs. Without this the .actions auto margin would
   split the slack with it and nothing would align. */
.grid .card > .imgbox + .actions { margin-top: 0; }
.photo { display: block; width: 100%; height: auto; border-radius: var(--pw-radius-frame); }
.imgbox + .byline, .photo + .byline { margin-top: 10px; }
/* Actions sit at the foot of a card, so the links line up across a row of cards. */
.actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: auto; padding-top: 14px; }
.actions .cta { margin: 0; }
/* A contact address presented as plain text, deliberately not a button: a
   mailto link styled as a pill looks clickable but silently fails in browsers
   with no mail handler, so the address reads as copy-and-paste text instead. */
.contact-email { margin-top: 2px; font-weight: 800; font-size: 18px; color: var(--pw-ink); }
/* --- z rows: text sits directly on the band, art floats --- */
.zrow { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; align-items: center; margin-top: 32px; }
.zrow .imgbox { margin-top: 0; }
.zrow .card, .ztext.card { background: transparent; border: 0; box-shadow: none; padding: 0; margin-bottom: 0; display: block; }
.zrow.flip .ztext { order: 2; } .zrow.flip .zart { order: 1; }
.zrow.wide-text { grid-template-columns: 1.6fr 1fr; }
/* Stacked z rows need more air between them than the 40px gap inside a row, or
   one row's art reads as belonging to the next row's text. This exceeds the
   64px section margin, which is fine here: every section that stacks z rows is
   bounded by banded sections, so a section boundary is carried by a background
   change plus the band's own 56/64px padding (~120px of air), not by the margin
   alone. Revisit if a plain section ever stacks z rows next to another plain one. */
.zrow + .zrow { margin-top: 72px; }
.zrow + .card, .grid + .card, .grid + p, .grid + .cta { margin-top: 24px; }
/* --- lists: red dot bullets (decorative, non-text) --- */
ul.plain { list-style: none; margin: 8px 0 14px; padding-left: 0; }
ul.plain li { position: relative; padding-left: 18px; margin-bottom: 8px; max-width: 66ch; }
ul.plain li::before { content: ""; position: absolute; left: 0; top: 0.58em; width: 6px; height: 6px; border-radius: 50%; background: var(--pw-red); }
/* --- quotes --- */
.quote p:first-child { color: var(--pw-ink); font-size: 17px; font-weight: 600; line-height: 1.55; }
.byline { color: var(--pw-muted); font-size: 13px; font-weight: 600; }
.headshot { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; display: block; margin-top: 14px; }
.quote .headshot + .byline { margin-top: 8px; }
.card > .headshot:first-child { margin-top: 0; margin-bottom: 12px; }
/* Team bio cards. Each one stacks a bold name, a lighter role line, an email
   address, the disclosure toggle, then the bio itself. The identity block is
   three lines in every card, so the three toggles land on one baseline without
   being pinned to the card foot -- which is the point: nothing above the toggle
   changes when a bio opens, so the toggle holds its place instead of sliding as
   the row grows. */
/* Team headshots run 1.5x the 48px avatar the quote blocks use: these are the
   subject of the card, not a byline attached to someone else's words. */
.bio-card .headshot { width: 72px; height: 72px; }
.bio-card h3 { margin-bottom: 4px; }
.bio-card .role { color: var(--pw-muted); font-size: 14px; font-weight: 500; margin: 0 0 4px; }
/* Between the 760px single-column switch and ~850px the three columns are narrow
   enough that a longer role wraps to a second line, which would drop that card's
   toggle 23px below the other two. Reserving the second line across the band holds
   the baseline; wider than that every role fits on one line, so no space is held
   back. A browser without `lh` takes the px fallback. */
@media (min-width: 761px) and (max-width: 850px) {
    .bio-card .role { min-height: 46px; min-height: 2lh; }
}
/* The email sits with the role in the muted identity block rather than taking
   main's bold-red link styling, so the toggle below stays the card's one
   pop-color action. Hover darkens it to ink under the inherited underline so it
   still reads as a link when pointed at. align-self keeps the hit area hugging
   the address in the flex-column card. */
.bio-card .email { display: block; align-self: flex-start; color: var(--pw-muted); font-size: 14px; font-weight: 500; margin: 0 0 12px; }
/* The envelope is masked out of the shared static asset and filled with
   currentColor, so it tracks the warm muted gray and the ink hover exactly --
   the black fill an img tag would render sits off this palette. As an
   inline-block atomic box it also escapes the hover underline, which only the
   address text should carry. Decorative: the address already says "email". */
.bio-card .email::before { content: ""; display: inline-block; width: 1em; height: 1em; margin-right: 6px; vertical-align: -0.125em; background: currentColor; -webkit-mask: url("../../img/phosphor-envelope-simple.svg") center / contain no-repeat; mask: url("../../img/phosphor-envelope-simple.svg") center / contain no-repeat; }
.bio-card .email:hover { color: var(--pw-ink); }
/* The toggle changes content rather than navigating, so it is a button, but it
   borrows the light-band link styling above so it reads as one instead of competing
   with the name. align-self keeps it hugging its label in the flex-column card. */
.bio-toggle { align-self: flex-start; padding: 0; border: 0; background: none; font-family: inherit; font-size: inherit; font-weight: 700; color: var(--pw-red-text); cursor: pointer; }
.bio-toggle:hover { color: var(--pw-red-hover); text-decoration: underline; }
/* The bio reads a step down from body copy, the size the card was already set in
   before the placeholder treatment came off it. */
.bio-card .bio { font-size: 15px; line-height: 1.6; margin: 12px 0 0; }
/* --- pricing --- */
.price { color: var(--pw-ink); font-size: 20px; font-weight: 800; margin: 0 0 12px; }
/* Visually-hidden text: keeps the annual rate available to screen readers and to
   the no-JS render without showing both billing periods at once. */
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* Elements Alpine reveals once it initializes; hidden until then so the annual
   note never flashes on load. */
[x-cloak] { display: none !important; }
/* --- pricing plans (comparison cards + collaborative estimator) --- */
/* Monthly/Annual segmented control: an ink fill marks the active period so red
   stays reserved for the featured-plan ring and the CTA. */
.plan-toggle { display: inline-flex; gap: 4px; background: var(--pw-fill); border-radius: 999px; padding: 4px; margin: 0 0 20px; }
.plan-toggle button { font-family: inherit; font-size: 14px; font-weight: 700; color: var(--pw-ink); background: none; border: 0; border-radius: 999px; padding: 8px 18px; cursor: pointer; }
.plan-toggle button.is-active { background: var(--pw-ink); color: #fff; }
.plan { position: relative; }
/* The featured plan takes the 2px inset red ring the design spec reserves for it. */
.plan.featured { box-shadow: inset 0 0 0 2px var(--pw-red), var(--pw-shadow-card); }
.plan-tag { position: absolute; top: -12px; left: 24px; background: var(--pw-red); color: #fff; font-size: 12px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; border-radius: 999px; padding: 3px 12px; }
.plan .price .sub { display: block; margin-top: 2px; }
/* Estimator: the range accent matches the brand red; the two output boxes sit on
   the ground fill so they read as results inside the white card. */
.estimator input[type="range"] { width: 100%; accent-color: var(--pw-red); margin: 8px 0 14px; }
.estimator #bulk-discount-label { color: var(--pw-ink); font-weight: 800; }
.estimator .estimate { background: var(--pw-ground); border-radius: var(--pw-radius-frame); padding: 16px; }
.estimator .estimate > :last-child { margin-bottom: 0; }
.plan-compare .sub a, .estimator label strong { color: var(--pw-ink); }
/* --- faq --- */
.qa { border-top: 1px solid var(--pw-fill); padding: 20px 0; }
.qa .q { color: var(--pw-ink); font-weight: 800; font-size: 17px; margin: 0 0 8px; }
.qa .a { margin: 0 0 10px; } .qa .a:last-child { margin-bottom: 0; }
/* --- full-bleed bands: escape the 1120px .wrap without changing the template structure --- */
.band-white, .band-dark { margin-inline: calc(50% - 50vw); padding-inline: calc(50vw - 50%); }
.section.band-white { background: var(--pw-surface); padding-top: 56px; padding-bottom: 64px; }
.section.band-dark { background: var(--pw-ink); color: var(--pw-dark-body); padding-top: 56px; padding-bottom: 64px; }
.band-dark h2, .band-dark h3 { color: #fff; }
.band-dark p, .band-dark .sub { color: var(--pw-dark-body); }
.band-dark a { color: #fff; text-decoration: underline; }
.band-dark a:hover { color: var(--pw-dark-body); }
/* On the dark band the CTA inverts (a red fill only reaches 2.5:1 against the ink band).
   The CTA is not a plain link, so it opts back out of the underline and bumps to the
   800 weight the spec calls for on this variant. */
.band-dark .cta, .band-dark .cta.todo { background: var(--pw-surface); color: var(--pw-red); opacity: 1; text-decoration: none; font-weight: 800; }
.band-dark .cta:hover { color: var(--pw-red); background: var(--pw-surface); }
.band-dark .cta.todo { opacity: 0.55; cursor: not-allowed; }
.band-dark :focus-visible { outline-color: #fff; }
/* Bands are flush color blocks: their internal padding carries the spacing, so the
   base section margin must not stack on top of it when one band follows another
   (fix from Task 4 review). A band that follows PLAIN content still needs the
   standard inter-section gap, or the plain section's last line sits flush against
   the band's colored edge (fix from final review). */
.section + .section.band-white, .section + .section.band-dark { margin-top: 64px; }
.section.band-white + .section.band-white,
.section.band-white + .section.band-dark,
.section.band-dark + .section.band-white,
.section.band-dark + .section.band-dark { margin-top: 0; }
/* When a page's last section is a color band (dark closing band, or the white
   band the pricing and demo pages end on), let the band's own color run into
   .wrap's bottom padding so the band lands right on the footer's top edge
   instead of leaving a hollow ground-colored gap that reads as a stray band. */
.wrap > .section.band-dark:last-child, .wrap > .section.band-white:last-child { margin-bottom: -64px; }
/* Short pages (demo confirmation): the sticky-footer shell leaves spare
   viewport height inside main; when the page ends on a white band, the band
   absorbs that height so the white hero runs all the way to the footer
   instead of ground color pooling beneath it. Tall pages are unaffected:
   there is no spare height. */
main.wrap:has(> .section.band-white:last-child) { display: flex; flex-direction: column; }
main.wrap > .section.band-white:last-child { flex: 1 0 auto; }
/* Uppercase micro label for the social-proof strip. */
.micro { font-size: 13px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--pw-muted); text-align: center; margin: 0 0 14px; }
/* Eyebrow kicker above an audience page h1: names the audience the nav item
   promised, so the page orients the reader without giving up its benefit-led
   headline. Same treatment as .micro but left-aligned with the hero text, and
   the tight margin binds it to the h1 rather than floating between sections. */
.eyebrow { font-size: 13px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--pw-muted); margin: 0 0 10px; }
#support-plans { scroll-margin-top: 90px; }
@media (max-width: 359px) {
    #support-plans { scroll-margin-top: 120px; }
}
/* The logo strip placeholder stays short and quiet. */
.imgbox.logo-strip { min-height: 120px; }
/* Jump list: an at-a-glance map of the question groups below it. */
.jump { display: flex; flex-wrap: wrap; gap: 10px; list-style: none; margin: 0 0 8px; padding: 0; }
.jump a { display: inline-block; background: var(--pw-surface); color: var(--pw-ink); font-size: 14px; font-weight: 700; border-radius: 999px; padding: 7px 16px; box-shadow: inset 0 0 0 1px var(--pw-fill); }
.jump a:hover { color: var(--pw-ink); background: var(--pw-ground); text-decoration: none; }
@media (max-width: 1000px) { .g4 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 760px) {
    .hero, .g2, .g3, .g4, .zrow, .zrow.wide-text { grid-template-columns: 1fr; }
    .hero { display: block; }
    .hero .hero-visual { margin-top: 20px; }
    /* The tiles keep their two-by-two shape on mobile, just tighter. The
       gallery spans the full viewport here, so the wordmark and the hub
       scale against larger vw shares: "Assignments" clears both the tile
       and the hub circle down to a 320px screen. */
    .hero-gallery { gap: 12px; margin-top: 20px; }
    .tool-tile { min-height: 104px; padding: 24px 12px; }
    .tool-wordmark { font-size: clamp(24px, 7vw, 34px); }
    .hero-hub { width: clamp(72px, 21vw, 104px); }
    .zrow.flip .ztext, .zrow.flip .zart { order: 0; }
    .section { margin-top: 44px; }
    .section + .section.band-white, .section + .section.band-dark { margin-top: 44px; }
    .zrow + .zrow { margin-top: 56px; }
    h1 { font-size: 32px; }
    h2 { font-size: 25px; }
}
/* --- demo request form --- */
/* The demo pages put the form/panel card inside the white hero band (the
   product-page hero treatment); the card needs its own gap under the lead. */
.hero.single .demo-form, .hero.single .demo-panel { margin-top: 36px; }
.demo-form .field { margin: 0 0 18px; border: 0; padding: 0; }
.demo-form .field > label, .demo-form .field > legend { display: block; font-weight: 700; color: var(--pw-ink); padding: 0; margin: 0 0 6px; max-width: 68ch; }
.demo-form input[type="text"], .demo-form input[type="email"], .demo-form textarea { width: 100%; padding: 10px 12px; border: 1.5px solid var(--pw-fill); border-radius: 8px; background: var(--pw-surface); color: var(--pw-text); font-family: var(--pw-font); font-size: 15px; }
.demo-form textarea { min-height: 96px; resize: vertical; }
/* Option rows: both the hand-rendered .choice labels and Django's own
   RadioSelect/CheckboxSelectMultiple output (labels nested in divs). The grid
   gives wrapped label text a hanging indent clear of its input. label.choice
   (not bare .choice) so this outranks the .field > label question-label rule
   above for the interests checkboxes, which sit directly in their fieldset. */
.demo-form label.choice, .demo-form .field div label { display: grid; grid-template-columns: 16px 1fr; gap: 0 8px; align-items: start; font-weight: 500; color: var(--pw-text); margin: 0 0 8px; }
.demo-form input[type="checkbox"], .demo-form input[type="radio"] { justify-self: start; margin: 6px 0 0; accent-color: var(--pw-red); }
/* Interest options carry a one-line summary under the name, so they need
   more breathing room than plain single-line option rows. */
.demo-form label.choice { margin-bottom: 12px; }
.demo-form .choice-desc { display: block; font-size: 14px; color: var(--pw-muted); }
.demo-form .field-error { color: var(--pw-red-text); font-size: 14px; font-weight: 700; margin: 0 0 6px; }
.demo-form .form-error-summary { border: 1.5px solid var(--pw-red); border-radius: 8px; color: var(--pw-red-text); font-size: 15px; font-weight: 700; padding: 10px 14px; margin: 0 0 18px; }
.demo-form h2 { font-size: 24px; margin-top: 28px; }
.demo-form .cta { margin-top: 20px; }
