/* Generated by wordpress/sync-from-static.sh from ../index.html.
   Edit the source page, then re-run - direct edits here are overwritten. */
    /* ============ PALETTE ============
       One colour, used at many depths. The page reads white-first: the 430px
       column stays #fff and every tint is a wash, never a fill. Colour arrives
       in three places only - the accent on anything clickable, one tint per
       feature frame, and the deep band under Contact.

       Everything here is the same blue and nothing else. Hue ~214 is the
       anchor: the neutrals are that blue desaturated nearly to grey, the tints
       are it at ~96% lightness, the CTA is it at ~45%, the Contact band is it
       at ~15%. Two analogous tints - a cyan-leaning mist and an indigo-leaning
       lilac - sit within 20 degrees of the anchor, close enough to read as the
       same family but far enough that six feature frames do not look like one
       swatch repeated.

       HIERARCHY. How loud an element should be picks which step it gets:
         --accent-strong  9.8:1 on white  the loudest mark on a white ground
         --accent         5.1:1           every clickable thing: CTA, active
                                          dots, FAQ chevrons, link hover
         --accent-mid     4.0:1           the light end of the hero's gradient
                                          word; supporting marks
         --accent-soft    2.2:1           decoration only
         --accent-quiet   1.4:1           inactive outlines, hairlines
       Below --accent the colour is graphic, not informational: if colour is the
       only cue that something is active, it must be --accent or darker. That is
       what makes the dot rows work - the active dot is --accent and also larger
       and filled, the inactive ones are --accent-quiet outlines.

       CONTRAST. White text on --grad-cta clears 5.14:1 at the gradient's
       lightest stop, so it passes at any size - the warm gradient this replaced
       only cleared 3.30:1 and was restricted to large bold type. Body copy
       holds 5.6:1 on white and >=4.8:1 on every tint; the Contact band runs
       13:1 or better for white. */
    :root {
      /* The ramp. Every other token in this block is one of these steps, or a
         gradient between two of them - there are no off-ramp blues. */
      --blue-950: #0b1a2e;
      --blue-900: #10243f;
      --blue-800: #16304f;
      --blue-700: #1d4573;
      --blue-600: #1d4e93;
      --blue-500: #2e6fb8;
      --blue-400: #3d82cc;
      --blue-300: #86b2e4;
      --blue-200: #cfdcec;
      --blue-100: #e3eaf4;
      --blue-50:  #eff4fb;

      /* Ink and neutrals - the ramp desaturated, not grey. A true grey beside a
         blue tint reads as dirt; these keep just enough hue to belong to it. */
      --ink:       #16233a;  /* headings              15.7:1 */
      --ink-soft:  #33405a;  /* body copy, nav, inputs 10.4:1 */
      --muted:     #59697f;  /* subheads, FAQ answers   5.6:1 */
      --muted-2:   #7b8aa3;  /* footer, placeholders    3.5:1 */
      --hairline:  var(--blue-100);  /* header rule */

      /* Accents - one hue, five volumes. See HIERARCHY above. */
      --accent-strong: var(--blue-700);
      --accent:        var(--blue-500);
      --accent-mid:    var(--blue-400);
      --accent-soft:   var(--blue-300);
      --accent-quiet:  var(--blue-200);

      /* Tints - three, rotated across the feature frames in DOM order
         (sky, -, mist, sky, indigo). The second frame is the app demo, which
         carries no tint and no halo at all - the phone sits on the white card
         and the frame is masked to a bottom fade instead, so the rotation just
         skips it. Three tints rather than one per frame because a separate
         wash of one hue per frame reads as an accident, while
         three 20 degrees apart read as a set. Nothing bakes a tint in any more:
         --tint-sky used to be flattened into the frame-*.jpg fallbacks of the
         "Frames in your colors" carousel, and that feature is gone. */
      --tint-sky:    #eaf1fa;
      --tint-indigo: #ecedfa;
      --tint-mist:   #e9f2f6;

      /* Halos - each frame's shadow is its own tint's hue at speaking volume,
         so the colour reads as light coming off the card, not dirt under it. */
      --halo-sky:    rgba(46,111,184,0.30);
      --halo-indigo: rgba(74,86,178,0.26);
      --halo-mist:   rgba(31,120,150,0.28);

      /* The one gradient that repeats: every primary action wears it. Narrow on
         purpose - --blue-500 to --blue-600, about 12 degrees of hue - so the
         button reads as one colour with depth rather than a spectrum. Its left
         end is the lightest, so that is the stop carrying white text: #2e6fb8
         is 5.14:1. Keep any future stop between --blue-500 and --blue-700. */
      --grad-cta:       linear-gradient(100deg, #2e6fb8 0%, #1d4e93 100%);
      --grad-cta-hover: linear-gradient(100deg, #27619f 0%, #183f79 100%);
      /* Page ground outside the 430px column (only visible above 430px wide). */
      --grad-page:      linear-gradient(165deg, #f3f7fd 0%, #eef3fb 52%, #f0f6fa 100%);
      /* Contact band: white, like the rest of the page. It reads as its own
         surface only because it overlaps FAQ's tinted foot by its corner
         radius - see the -32px margin-top on #contact. */
      --grad-contact:   #ffffff;

      /* On-band type - the band is white, so these are the blue accents that
         carry it. Kept as tokens (rather than reusing --ink/--accent inline)
         because the band's hierarchy is its own: heading darkest, then body,
         then the footer line. */
      --on-band:       var(--accent-strong);  /* headings           9.8:1 */
      --on-band-soft:  var(--muted);          /* the band's subhead 5.6:1 */
      --on-band-quiet: var(--muted);          /* the footer line    5.6:1 */
      --on-band-mark:  var(--accent);         /* the success check  5.1:1 */
      --on-band-line:  var(--accent-quiet);   /* rules and well borders */
      --field:         #f4f8fd;  /* input wells - a shade off white, so they
                                    need the --on-band-line border to read */

      /* Every shadow on the page is the ramp's darkest step at low alpha, never
         neutral grey - a grey shadow under a blue tint turns muddy. */
      --shadow-soft: rgba(16,35,63,0.14);
    }

    html, body { margin: 0; padding: 0; }
    body { min-height: 100vh; background: var(--grad-page) fixed no-repeat; background-color: #f3f7fd; }
    html { scroll-behavior: smooth; color-scheme: light; }
    * { box-sizing: border-box; }
    input::placeholder, textarea::placeholder { color: var(--muted-2); }
    @keyframes swipeHint { 0%, 100% { transform: translateY(-50%) translateX(0); opacity: 0.55; } 50% { transform: translateY(-50%) translateX(5px); opacity: 1; } }
    [data-hover-darken]:hover { background: var(--grad-cta-hover) !important; }
    .desktop-nav a:hover { color: var(--accent); }
    .faq-item summary::-webkit-details-marker { display: none; }
    .faq-item summary::marker { content: ""; }
    .faq-item[open] .faq-arrow { transform: rotate(180deg); }

    /* The hero's one flourish: the middle word of the title carries the accent
       gradient instead of a flat colour. background-clip: text needs a
       transparent fill, and -webkit-text-fill-color is the one that actually
       wins in WebKit - keep both. If the gradient ever fails to paint, the
       declared `color` is what shows, so it must stay a real ink value. */
    .grad-text {
      color: var(--ink);
      background: linear-gradient(100deg, #3d82cc 0%, #2e6fb8 45%, #24478a 100%);
      -webkit-background-clip: text; background-clip: text;
      -webkit-text-fill-color: transparent;
    }
    @supports not ((-webkit-background-clip: text) or (background-clip: text)) {
      .grad-text { -webkit-text-fill-color: currentColor; background: none; }
    }

    /* The hero title breaks on fixed lines at every width - "memories." /
       "In E-ink." - so it is a stack, not a sentence that happens to wrap.
       Block spans rather than <br>: the words stay real words (so selection,
       screen readers and translation still see "memories. In E-ink."), the
       whitespace between them collapses away on its own, and the gradient line
       can carry both classes without a wrapper. Because the breaks are
       explicit, the h1 drops `text-wrap: balance` - there is nothing left for
       it to balance.
       nowrap holds the second line together: it is the only span with a space
       in it, and "E-ink." would otherwise be free to break at its hyphen. The
       fluid font size is what keeps that safe - the line's width is a fixed
       multiple of the type size, which is itself a slice of the viewport, so
       it cannot outgrow the column. */
    .hero-word { display: block; white-space: nowrap; }

    /* Each feature frame carries its own tint plus a halo in the same hue -
       a coloured shadow rather than a grey one, so the colour reads as light
       coming off the card instead of dirt under it. Set inline per item. */
    .feature-media { box-shadow: 0 16px 30px -18px var(--halo, var(--shadow-soft)); }

    /* ============ STORY (between hero and features) ============
       One portrait clip, borrowing the features' frame language - same 22px
       radius, same tinted ground, same coloured halo - so it reads as part of
       the page rather than an embed dropped into it. The clip is 9:16, which
       at the full 430px column would be 764px tall and push everything below
       it off screen, so the frame is capped and centred instead: the width is
       the variable, the ratio holds it. Plays on scroll like the other two
       clips (data-play-in-view in app.js), muted and looping. */
    .story-media {
      position: relative;
      width: 100%;
      max-width: 330px;
      margin: 0 auto;
      aspect-ratio: 9 / 16;
      border-radius: 22px;
      overflow: hidden;
      background: var(--tint-indigo);
      box-shadow: 0 18px 34px -18px var(--halo, var(--shadow-soft));
    }
    .story-media video {
      position: relative; z-index: 1;
      display: block; width: 100%; height: 100%;
      object-fit: cover;
    }

    /* ============ APP DEMO (features > "Your photos, your order") ============
       The recording is the app screen only - the export lost the device bezel -
       so the phone is drawn here in CSS and the clip sits in its screen well.
       Everything is a percentage of the frame so it scales with the column: the
       shell is 84% of the frame's width, its padding 2.6% of its own width, and
       the screen well carries the clip's exact 356:520 ratio so nothing is
       cropped sideways. The shell is deliberately ~22px taller than the space
       below its 6% top offset, so the frame's overflow:hidden cuts the clip's
       hard bottom edge and the phone reads as running out of the card. */
    /* The one .feature-media with no tint and no halo: the phone sits directly
       on the white card, like the pricing shot. Nothing behind it means the
       bottom cut would land as a hard line on white, so the frame is masked
       instead - the last ~18% fades out and the phone dissolves into the page.
       The mask is on the frame, not the shell, because the shell overhangs the
       frame's bottom edge and its own fade would happen out of view. */
    .feature-item--demo .feature-media {
      box-shadow: none;
      -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 80%, rgba(0,0,0,0.55) 91%, transparent 100%);
      mask-image: linear-gradient(to bottom, #000 0%, #000 80%, rgba(0,0,0,0.55) 91%, transparent 100%);
    }
    .app-demo-phone {
      position: absolute; z-index: 1; top: 6%; left: 50%; transform: translateX(-50%);
      width: 84%; box-sizing: border-box;
      padding: 2.6% 2.6% 0;
      background: var(--blue-950);
      border-radius: 34px 34px 0 0;
      box-shadow: 0 18px 22px rgba(0,0,0,0.5);
    }
    .app-demo-screen {
      aspect-ratio: 356 / 520; overflow: hidden;
      border-radius: 26px 26px 0 0; background: #ffffff;
    }
    .app-demo-video {
      display: block; width: 100%; height: 100%;
      object-fit: cover; object-position: center top;
    }

    /* ============ MEDIA CAROUSEL (two features swipe: drawn look, and rooms) ============
       Shared by "A timeless, drawn look" and "At home in every room" - same
       markup, same CSS, two `setupCarousel` calls in app.js. Slides are
       full-bleed - each is exactly the wrap's width, no peeking neighbour - so
       app.js steps both by the wrap's measured width, with no side padding.
       Arrows and dots sit INSIDE the 4:3 frame on purpose: the desktop grid
       pins every .feature-media to the bottom of its cell to line the images
       up, and a dot row below the frame would break that alignment.
       Both carousels are edge-to-edge photographs with no gutter for the
       controls to land in, so both carry --photo (below). The bare variant -
       contain, no scrim, for a product shot on a flat backdrop - is what the
       old "Frames in your colors" carousel used; it is kept for the next
       feature of that shape. */
    .media-track {
      display: flex; height: 100%; will-change: transform;
      transition: transform 0.4s cubic-bezier(0.22,0.68,0,1.02);
    }
    .media-slide { flex: 0 0 100%; width: 100%; height: 100%; }
    .media-slide picture { display: block; width: 100%; height: 100%; }
    .media-slide img {
      display: block; width: 100%; height: 100%; object-fit: contain;
      -webkit-user-select: none; user-select: none; -webkit-user-drag: none;
    }
    /* Photo variant: the slide is a full-bleed scene, not a product on a flat
       backdrop. Two consequences. The image fills the frame (cover, like the
       static photo features) instead of being letterboxed, and the dot row can
       no longer count on a pale background - the three rooms run from a white
       wall to dark wood. A short scrim under the dots gives them one constant
       ground across all three, and it is short and soft enough not to read as
       a band over the photo. Arrows carry their own shadow, so they are fine
       without it. */
    .media-carousel--photo .media-slide img { object-fit: cover; }
    .media-carousel--photo::after {
      content: ''; position: absolute; z-index: 1; left: 0; right: 0; bottom: 0;
      height: 74px; pointer-events: none;
      background: linear-gradient(to top, rgba(11,26,46,0.36) 0%, rgba(11,26,46,0.17) 45%, rgba(11,26,46,0) 100%);
    }
    /* app.js re-applies the dots inline on every render, so the outline of the
       inactive ones has to win with !important. The active dot has border:none,
       so this only ever touches the inactive ones. */
    .media-carousel--photo .media-dot { border-color: rgba(255,255,255,0.9) !important; }
    .media-arrow {
      position: absolute; z-index: 2; top: 50%; transform: translateY(-50%);
      width: 34px; height: 34px; border-radius: 50%; background: #fff;
      color-scheme: light; padding: 0; box-sizing: border-box; border: none;
      box-shadow: 0 2px 10px rgba(16,35,63,0.22);
      display: flex; align-items: center; justify-content: center; cursor: pointer;
    }
    .media-arrow:hover { background: var(--blue-50); }
    .media-carousel:active { cursor: grabbing; }
    .media-dots {
      position: absolute; z-index: 2; left: 0; right: 0; bottom: 8px;
      display: flex; justify-content: center; align-items: center; gap: 8px;
    }

    /* ============ PRICING (#pricing) ============
       One product, one card. The card reuses the feature frames' vocabulary -
       4:3 tinted media block, 22px radius, a halo in the tint's own hue - so it
       reads as part of the same page rather than a bolted-on store widget.
       The finish picker is NOT a carousel: there is no sequence here, the three
       finishes are peers and the shopper picks one directly. So the three shots
       are stacked in the same box and cross-faded on opacity (see setupFinish in
       app.js) instead of translated on a track. This card is now the only
       reference to frame-{black,white,wood} at all - the features carousel that
       shared the .webp was removed - which also leaves the tinted frame-*.jpg
       (flattened onto --tint-sky for that carousel) unreferenced; the picker
       points at the -plain.jpg flattened onto white instead.
       All three stay loading="lazy" even though only one is
       visible: opacity:0 still counts as rendered, and the three share one box,
       so entering the viewport fetches all three together - the shopper never
       clicks a swatch and gets a blank frame, and nothing is fetched while the
       section is still 4,000px below the fold. Do not swap opacity for
       display:none here, which would break exactly that. */
    .price-card {
      background: #ffffff; border: 1px solid var(--blue-100); border-radius: 24px;
      padding: 16px 16px 22px; box-shadow: 0 10px 30px -18px var(--shadow-soft);
    }
    /* No tint and no halo here, unlike every .feature-media frame: the product
       sits directly on the card. That is why the fallbacks are the -plain jpgs
       (flattened onto #ffffff) rather than the frame-*.jpg the features carousel
       uses, which are flattened onto --tint-sky and would show as a pale blue
       square on this white card. The .webp are shared - they keep their alpha
       and do not care what is behind them. */
    .price-media {
      position: relative; aspect-ratio: 4 / 3;
    }
    /* All three stacked in the same box; only the active one is opaque. The
       inactive ones stay in the layout (opacity, not display) so the box never
       reflows and the fade has something to fade to. */
    .price-shot {
      position: absolute; inset: 0; display: block;
      opacity: 0; transition: opacity 0.35s ease; pointer-events: none;
    }
    .price-shot.is-active { opacity: 1; }
    .price-shot img { display: block; width: 100%; height: 100%; object-fit: contain; }

    .price-title {
      font-size: 27px; font-weight: 600; color: var(--ink);
      text-align: center; margin: 20px 0 8px; line-height: 1.3; text-wrap: balance;
    }
    .price-desc {
      font-size: 18px; color: var(--muted); text-align: center;
      margin: 0 0 20px; line-height: 1.5; text-wrap: pretty;
    }

    /* Finish picker. The swatch carries the finish's own colour, which is the
       one place on the page where a non-blue fill is deliberate - it is a
       sample of the product, not UI. Per the palette's rule (a) that makes the
       fill graphic, not informational, so "which one is selected" is carried by
       the --accent ring and the aria-checked state, never by the colour alone. */
    .price-swatches {
      display: flex; justify-content: center; align-items: center; gap: 18px; margin: 0 0 10px;
    }
    /* 48px: comfortably over the 44px minimum touch target, and big enough that
       the finish itself is readable as a sample rather than a dot - which is the
       whole job here, since the swatch is what the shopper compares before
       committing to a colour. The ring geometry below is tuned to this size. */
    .price-swatch {
      width: 48px; height: 48px; border-radius: 50%; padding: 0; cursor: pointer;
      background: var(--swatch); border: 1px solid rgba(16,35,63,0.18);
      box-shadow: 0 0 0 0 var(--accent); transition: box-shadow 0.2s ease, transform 0.2s ease;
    }
    .price-swatch:hover { transform: scale(1.06); }
    /* Ring sits off the swatch (a 3px gap of white) so it reads on the cream
       one, which is nearly the card's own background. */
    .price-swatch.is-active { box-shadow: 0 0 0 3px #ffffff, 0 0 0 5px var(--accent); }
    .price-finish {
      font-size: 15.5px; color: var(--muted); text-align: center; margin: 0 0 20px;
    }

    .price-specs {
      list-style: none; margin: 0; padding: 20px 0 0; border-top: 1px solid var(--blue-100);
      display: grid; grid-template-columns: 1fr; gap: 13px;
    }
    .price-spec {
      display: flex; align-items: flex-start; gap: 11px;
      font-size: 16.5px; color: var(--ink-soft); line-height: 1.4;
    }
    /* The icon keeps its own line-height-independent box so a spec that wraps to
       two lines still hangs its text off one left edge. */
    .price-spec svg { flex: 0 0 auto; margin-top: 2px; }

    /* Frosted strip behind the pinned CTA. Full-bleed (left/right: 0) rather
       than tracking the 430px column, so on desktop it reads as a page-wide
       bottom bar. It sits at z-index 39 - under the button (40), over the
       content - and is pointer-events: none so it never swallows a tap or a
       click on whatever scrolls beneath it.
       Opacity and delicacy are tuned independently, which is why both the fill
       and the mask carry five stops instead of two. The fill is what makes it
       read as a solid surface - it is 0.97 white at the bottom edge, so the
       button lands on something rather than floating. The mask is what keeps
       that weight from turning into a slab: it fades the tint AND the backdrop
       blur out together, and the extra stops hold the ramp shallow through the
       middle before dropping off late, so the strip has no visible top edge and
       no banding across a gradient. Retune the two together - raising the fill
       alone reads as a hard white block, softening the mask alone washes it
       out. Keep the mask entirely if you change the height. */
    .sticky-cta-bar {
      position: fixed; z-index: 39; left: 0; right: 0; bottom: 0;
      height: 112px; pointer-events: none;
      background: linear-gradient(to top, rgba(255,255,255,0.97) 0%, rgba(255,255,255,0.95) 34%, rgba(255,255,255,0.84) 60%, rgba(255,255,255,0.46) 80%, rgba(255,255,255,0.14) 92%, rgba(255,255,255,0) 100%);
      -webkit-backdrop-filter: blur(20px) saturate(150%);
      backdrop-filter: blur(20px) saturate(150%);
      -webkit-mask-image: linear-gradient(to top, #000 0%, #000 44%, rgba(0,0,0,0.78) 64%, rgba(0,0,0,0.42) 80%, rgba(0,0,0,0.14) 92%, transparent 100%);
      mask-image: linear-gradient(to top, #000 0%, #000 44%, rgba(0,0,0,0.78) 64%, rgba(0,0,0,0.42) 80%, rgba(0,0,0,0.14) 92%, transparent 100%);
    }

    /* ============ SCROLL REVEAL ============
       One delicate move, used everywhere: anything marked `data-reveal` starts
       14px low and transparent and eases up into place when the observer in
       app.js says it has entered the viewport. It fades back out only once it
       has left the viewport entirely, so the fade-out itself always happens
       off-screen and scrolling back up replays the reveal.
       Two properties, both compositor-only (opacity + transform) - no layout,
       no scroll listener, no library, nothing added to the page weight.
       Gated on `html.js`, set by the one-line inline script at the end of
       <head>: with JS off, or if app.js fails to load, nothing is ever hidden.
       Elements carrying this must not be a carousel wrap or contain anything
       position: fixed - a transform makes them the containing block. */
    .js [data-reveal] {
      opacity: 0;
      transform: translateY(14px);
      transition: opacity 620ms cubic-bezier(0.22,0.68,0,1.02),
                  transform 620ms cubic-bezier(0.22,0.68,0,1.02);
    }
    .js [data-reveal].is-revealed { opacity: 1; transform: none; }

    /* ============ DESKTOP ( >= 900px ) ============
       The page is authored mobile-first (430px column, all inline styles) and
       driven by app.js, which sets inline transforms to run the two carousels.
       On wide screens we break out of the column, turn both carousels into
       static grids, and lay the stacked card sections out in rows. Rules use
       !important so they win over the inline styles app.js keeps re-applying. */
    /* Reduced motion: drop the hero video and fall back to the still that the
       section already carries as its background. */
    @media (prefers-reduced-motion: reduce) {
      .hero-video { display: none; }
      /* Reveal on: everything is simply there. app.js also skips the observer
         and marks every element revealed, so this only has to kill the
         transition - but keep both, so the page is right even if JS is not. */
      .js [data-reveal] { opacity: 1; transform: none; transition: none; }
    }

    @media (min-width: 900px) {
      .memorink-page { max-width: 1080px !important; box-shadow: 0 0 60px rgba(16,35,63,0.10); }

      /* Header: hide the hamburger, show inline nav links */
      [data-screen-label="header"] button[aria-label="Menu"] { display: none !important; }
      .desktop-nav { display: flex !important; }
      .site-logo { height: 40px !important; }

      /* Hero: same full-viewport photo backdrop and same sixth line, just larger
         type and more inset. The title box loses 96 instead of 48, twice the
         padding, so its centre still lands on hero/6.
         The h1 keeps ramping here because the column does too: 8.35vw is 90px
         at the 1080px layout width, floored at the 56px the mobile ramp tops
         out at and capped at 90px so it stops growing on ultrawide screens. */
      [data-screen-label="hero"] { padding: 48px 48px 48px !important; }
      [data-screen-label="hero"] h1 { font-size: clamp(3.5rem, 8.35vw, 5.625rem) !important; }
      [data-screen-label="hero"] div:has(> h1) { min-height: calc(var(--hero-h) / 3 - 96px) !important; }

      /* Sticky CTA: 90% of a 1080px column would be an 972px banner, not a
         button, so hold it near its mobile size and let it sit taller. */
      .sticky-cta { max-width: 480px !important; height: 64px !important; font-size: 26px !important; bottom: 38px !important; }
      /* Taller button + taller lift needs a taller strip to keep the same
         ~26px of fade breathing above the button. */
      .sticky-cta-bar { height: 132px !important; }
      /* Wide viewports crop the portrait photo hard, so the picture frame rises
         behind the paragraph - reach the scrim further down to keep it crisp. */
      [data-screen-label="hero"] .hero-scrim { background: linear-gradient(to bottom, rgba(250,252,255,0.94) 0%, rgba(246,250,254,0.82) 24%, rgba(244,249,253,0) 58%) !important; }

      /* Story: same inset as Features, and the frame grows a little - but only
         a little. A 9:16 clip scales its height by 1.78 for every point of
         width, so a full-column frame here would be over 1000px tall. */
      #story { padding: 40px 48px 0 !important; }
      #story h2 { font-size: 40px !important; }
      #story p { font-size: 21px !important; }
      .story-media { max-width: 360px !important; }

      /* Features: stacked list -> 2-column grid (the 4:3 frames stay large) */
      #features { padding: 40px 48px 24px !important; }
      #features-list { display: grid !important; grid-template-columns: repeat(2, 1fr) !important; gap: 52px 32px !important; }
      /* Both step down from mobile - a half-width column is narrower than the
         430px one, so the mobile sizes would wrap every title to two lines. */
      .feature-item h3 { font-size: 27px !important; }
      .feature-item p { font-size: 19px !important; }
      /* Subheads wrap to one or two lines depending on the copy, which would
         leave the 4:3 frames sitting at different heights across a grid row.
         Stretch the item and pin its frame to the bottom so the images line up. */
      .feature-item { display: flex !important; flex-direction: column !important; }
      .feature-item .feature-media { margin-top: auto !important; }
      /* The demo is portrait and much taller than a 4:3 still, so it takes the
         full row instead of leaving its neighbour a column of dead space. */
      .feature-item--demo { grid-column: 1 / -1 !important; }
      .feature-item--demo .feature-media { max-width: 420px !important; margin: 0 auto !important; }
      /* Both media carousels stay carousels here, unlike pricing: four drawings
         (or three rooms) laid out side by side in a half-width cell would each
         be too small to read. Only the controls change - sized for a mouse
         instead of a thumb, and pushed further into the wider gutters the
         bigger frame leaves. */
      .media-arrow { width: 38px !important; height: 38px !important; }
      .media-arrow--prev { left: 12px !important; }
      .media-arrow--next { right: 12px !important; }
      .media-dots { bottom: 12px !important; gap: 10px !important; }
      .media-carousel--photo::after { height: 88px !important; }

      /* Pricing: the stacked card becomes one wide row - shot on the left, the
         whole buying decision (price, name, finish, specs) on the right, so
         nothing about the product needs a scroll to reach. Capped well under
         the 1080px column: a 4:3 shot at half of 1080 would tower over the FAQ
         below it. Type goes UP here, unlike the feature grid, because this
         column is wider than the mobile one rather than narrower. */
      #pricing { padding: 40px 48px 8px !important; }
      .price-card {
        max-width: 900px; margin: 0 auto; padding: 26px !important;
        display: grid !important; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: 34px; align-items: center;
      }
      /* The body column is centred as a block but its contents go left-aligned:
         a centred spec list beside a photo reads as a poster, not a spec sheet. */
      .price-body { text-align: left !important; }
      .price-title { font-size: 31px !important; text-align: left !important; margin-top: 0 !important; }
      .price-desc { font-size: 19px !important; text-align: left !important; }
      .price-swatches { justify-content: flex-start !important; }
      .price-finish { text-align: left !important; }
      /* Specs stay ONE column here, unlike most things that widen on desktop.
         Two columns fit across the card but leave each cell ~200px, which wraps
         four of the six specs onto a second line - a ragged block of half-lines
         reads worse than a taller tidy one. One column at this width puts every
         spec on exactly one line. */
      .price-specs { gap: 14px !important; }

      /* FAQ: cap the accordion width */
      #faq > div { max-width: 760px; margin-left: auto !important; margin-right: auto !important; width: 100%; }

      /* Contact: cap and center the form column inside the full-width navy band */
      #contact { padding: 48px 24px 28px !important; }
      #contact > * { max-width: 560px; width: 100%; margin-left: auto !important; margin-right: auto !important; }
    }
