/* PrintOnion design language — single source of truth.
   Linked by Design Language.dc.html and every page DC.
   Edit a value here and every page updates. Tokens only: no classes. */

/* Manrope + Outfit are self-hosted via po-fonts.css (loaded independently
   in <head>) — this @import used to pull the same families from
   fonts.googleapis.com a SECOND time, a render-blocking CDN round-trip
   nested inside another stylesheet's own load. Removed; the fonts
   are already available by the time anything here references them. */

:root{
  /* Ground & ink */
  --po-paper:#FFFFFF;
  --po-paper-2:#F7F6F3;
  --po-shell:#EFEBE4;
  --po-ink:#171310;
  --po-ink-70:#5B534C;
  --po-ink-45:#6E655D;

  /* Accent — deep green. One accent for the whole system: brand and commerce.
     Swap these five to change the accent everywhere. */
  --po-accent:#1E5945;
  --po-accent-300:#79B49C;
  --po-accent-600:#17493A;
  --po-accent-700:#10382C;
  --po-accent-100:#E7EFEA;

  /* Deprecated aliases — old pink names, now resolving to the oxide accent. */
  --po-pink:var(--po-accent);
  --po-pink-600:var(--po-accent-600);
  --po-pink-700:var(--po-accent-700);
  --po-pink-100:var(--po-accent-100);

  /* Second data series — for charts only, never for UI. Kiln clay: reads clearly
     against the green accent ramp at any size. */
  --po-series-2:#B4561E;
  --po-series-2-300:#DFA97F;

  /* Shop aliases — same accent, kept so commerce markup reads by intent. */
  --po-shop:var(--po-accent);
  --po-shop-300:var(--po-accent-300);
  --po-shop-600:var(--po-accent-600);
  --po-shop-700:var(--po-accent-700);
  --po-shop-100:var(--po-accent-100);

  /* Commerce green — the buy action and the price-led badge. Brighter than
     the brand accent on purpose: it only ever means "put this in the cart". */
  --po-buy:#218838;
  --po-buy-600:#1B6F2E;
  --po-buy-700:#155724;

  /* Product badges — one lightness/chroma, three hues. Ribbon, not pill. */
  --po-badge-new:oklch(0.53 0.17 27);
  --po-badge-new-fold:oklch(0.36 0.13 27);
  --po-badge-popular:oklch(0.53 0.14 253);
  --po-badge-popular-fold:oklch(0.36 0.11 253);
  --po-badge-price:var(--po-buy);
  --po-badge-price-fold:var(--po-buy-700);

  /* Grounds & lines */
  --po-graphite:#211B15;      /* the one dark ground: campaign panels + footer */
  --po-plum:var(--po-graphite);
  --po-on-dark:#EDE7E0;       /* body copy on graphite */
  --po-on-dark-70:#C9C2B9;
  --po-on-dark-45:#A79F96;
  --po-line:#E3DBD2;
  --po-line-strong:#D5C9BB;

  /* Semantic */
  --po-ok:#4F7A4A;      --po-ok-bg:#EAF0E8;   --po-ok-ink:#3A5C36;
  --po-warn:#B4761E;    --po-warn-bg:#F6EBDA; --po-warn-ink:#8A5A14;
  --po-err:#B02725;     --po-err-bg:#F6E3E2;  --po-err-ink:#8A1E1C;

  /* Geometry — continuous (squircle) corners. Radii run larger than a
     circular corner would, because a superellipse reads tighter. */
  --po-r-sm:8px; --po-r-md:14px; --po-r-lg:20px; --po-r-pill:12px;

  /* Controls — the one button/input scale. Height drives the size, padding
     is horizontal only, so labels never inflate a control.
     sm = chips and inline actions · md = default · lg = hero and checkout. */
  --po-btn-sm-h:34px;  --po-btn-sm-px:16px;  --po-btn-sm-fs:13.5px;
  --po-btn-h:40px;     --po-btn-px:20px;     --po-btn-fs:14.5px;
  --po-btn-lg-h:46px;  --po-btn-lg-px:26px;  --po-btn-lg-fs:15px;
  --po-input-h:44px;

  /* Elevation */
  --po-float:0 2px 18px rgba(23,19,16,0.06);
  --po-overlay:0 12px 44px rgba(23,19,16,0.12);
  --po-shadow-btn:0 1px 2px rgba(23,19,16,0.07), 0 6px 16px rgba(23,19,16,0.09);
  --po-shadow-btn-hover:0 2px 4px rgba(23,19,16,0.08), 0 12px 28px rgba(23,19,16,0.14);
  --po-trust-star:#00B67A;

  /* Motion */
  --po-micro:140ms ease-out;
  --po-reveal:220ms cubic-bezier(.2,.7,.2,1);

  /* Type */
  --font-heading:"Outfit",sans-serif;
  --font-body:"Manrope",sans-serif;

  /* Layout */
  --po-max:1180px;
  --po-gutter:clamp(20px,5vw,64px);
}

/* Every corner in the system is a continuous curve, not a circular arc.
   Opt back out with corner-shape:round on anything meant to be a true circle. */
*,*::before,*::after{corner-shape:squircle}

body{
  margin:0;
  background:var(--po-paper);
  color:var(--po-ink);
  font-family:var(--font-body);
  text-wrap:pretty;
  -webkit-font-smoothing:antialiased;
}
a{color:var(--po-accent-600);text-decoration:none}
a:hover{color:var(--po-accent)}
::selection{background:var(--po-accent-100);color:var(--po-ink)}
*:focus-visible{outline:2px solid var(--po-accent);outline-offset:2px}
/* Reduce motion: every animation collapses to 1ms. A LOOPING one (marquee,
   spool, skeleton pulse) then restarted 1000 times a second and strobed -
   the opposite of reduced motion (Felix saw the payment spinner "spinning
   too fast", 2026-09-23). Loops now play once and rest at their end frame.
   A loader that must keep turning (.po-pay-ring) opts back in with its own
   !important rule, which a class selector wins over this *. */
@media (prefers-reduced-motion:reduce){*,::before,::after{animation-duration:1ms!important;animation-iteration-count:1!important;transition-duration:1ms!important}}

/* --------------------------------------------------- page-to-page rendering
   The white flash between pages had two separate causes.

   1. Only <body> carried a background. Between two documents the browser
      paints the canvas before any of the new page's CSS applies, and with a
      transparent <html> that canvas is the browser's own default white. Giving
      the root element the same paper colour means the gap is painted in our
      colour instead of white - even before the new document's body exists.

   2. A cross-document navigation is two separate documents, so the old page is
      torn down and the new one painted from scratch. @view-transition tells
      the browser to keep the outgoing frame on screen and cross-fade to the
      incoming one, which removes the blank frame entirely. Pages here share
      one header, one stylesheet and the same layout, so the fade reads as the
      content swapping rather than the page reloading.

   Both are progressive: a browser that does not know @view-transition simply
   navigates as before, and the root background still removes the white. */
html{background:var(--po-paper)}

@view-transition{navigation:auto}

/* Keep it quick - this should feel like a swap, not an effect.
   !important because the `*{animation-duration:1ms!important}` reduced-motion
   rule at the top of this file matches these pseudo-elements too; without it
   the fade collapsed to 1ms and the old frame was gone before the new one
   painted, which is exactly the blank the transition is meant to remove. */
::view-transition-old(root),::view-transition-new(root){animation-duration:120ms!important}

/* The header is identical on every page, so hold it still across the
   navigation instead of cross-fading it with the rest of the page. */
@media (prefers-reduced-motion:no-preference){
  ::view-transition-group(po-header){animation-duration:120ms}
}

/* Reduced motion: keep the transition, drop the MOVEMENT.
   `animation:none` here was wrong - a view transition IS its animation, so
   removing it aborts the whole thing and the browser falls back to the plain
   tear-down-and-repaint, i.e. the blank frame comes back. A cross-fade is not
   the kind of motion the setting is about (no travel, no parallax), so the
   honest reading is to keep it and make it near-instant. The `*` rule at the
   top of this file also lands on ::view-transition-* with !important, which
   would do the same damage, so both pseudo-elements are pinned explicitly. */
@media (prefers-reduced-motion:reduce){
  ::view-transition-old(root),::view-transition-new(root){
    animation-duration:1ms!important;
  }
  ::view-transition-group(po-header){animation-duration:1ms!important}
}

/* ---------------------------------------------------------------- webfonts
   Merged in from po-fonts.css. That file was 939 bytes sitting behind its own
   render-blocking <link>: the browser could not paint until a second HTTP
   round-trip finished - measured at ~93ms - for nothing but two @font-face
   rules. Both stylesheets lived at the site root, so the relative url()s
   resolve exactly as before. font-display:swap means text paints before the
   font arrives, so that round-trip was pure dead time ahead of first paint.

   Self-hosted Manrope + Outfit (replaces the fonts.googleapis.com round-trip):
   variable-font files served same-origin, so there is no DNS+TLS hop to
   fonts.googleapis.com + fonts.gstatic.com before text can render. */
@font-face{font-family:'Manrope';font-style:normal;font-weight:200 800;font-display:swap;src:url('./vendor/fonts/manrope-variable.woff2') format('woff2-variations'),url('./vendor/fonts/manrope-variable.woff2') format('woff2')}
@font-face{font-family:'Outfit';font-style:normal;font-weight:100 900;font-display:swap;src:url('./vendor/fonts/outfit-variable.woff2') format('woff2-variations'),url('./vendor/fonts/outfit-variable.woff2') format('woff2')}

/* ------------------------------------------------ offscreen card rendering
   The landing page's "Bästsäljare" strip is a horizontal scroller holding
   186 product cards across 30,542px, of which THREE are on screen at load.
   Measured 2026-09-14: that one section is 2,243 of the mobile page's 2,661
   elements, and Lighthouse put the landing LCP at 3.2s with 2,556ms of it
   pure render delay - the hero image was loaded and waiting while the
   browser laid out cards nobody can see.

   content-visibility:auto lets the browser skip layout and paint for a card
   until it scrolls near the viewport. The cards stay in the DOM (so search
   engines, Cmd+F and the scroll position all behave exactly as before) -
   only the rendering work is deferred. contain-intrinsic-size supplies a
   placeholder size so the scroller's width does not collapse before a card
   has been rendered, which would otherwise make the scrollbar jump.

   Scoped to cards inside a horizontal scroller on purpose: applying it to
   ordinary vertical page sections risks visible pop-in on fast scrolls, and
   those sections are only ~185 elements in total - not worth the risk. */
.po-hscroll > * {
  content-visibility: auto;
  contain-intrinsic-size: auto 150px auto 260px;
}

/* The first cards are on screen at load, and their images are loading="lazy".
   content-visibility:auto on a lazy image's container stops the browser
   noticing the image is in the viewport, so it never starts the fetch and the
   card renders blank - reproduced 2026-09-14 on cards 1 and 3. Exempt enough
   cards to cover the widest viewport (a 150px card on a 1440px screen is ~9
   across, so 12 is a safe margin) and let the rest defer as intended. */
.po-hscroll > *:nth-child(-n+12) {
  content-visibility: visible;
}
