/* Shop-page-only additions on top of style.css */

/* One paper card for the whole header block (title, description, shipping
   note, and -- when active -- the campaign banner as an inset strip inside
   it) instead of separate plain-text-on-mat lines next to a standalone
   floating banner card. */
.hero-shop {
  text-align: center;
  max-width: 760px;
  width: 95%;
  margin: 18px auto 30px;
  padding: 38px clamp(20px, 5vw, 48px) 34px;
}
.hero-shop h1 { font-size: 2.4rem; margin: 0 0 10px; color: var(--text-dark); }
.hero-shop p { font-size: 1.15rem; color: var(--text-dark); }
.hero-shop .shop-shipping-note { font-size: 0.85rem; margin-top: 6px; color: var(--text-muted); }

/* Inset highlight strip, not a second torn-paper card nested inside the
   first (two stacked .rip shapes would clip against each other) -- same
   "dashed border, tinted panel" language as legal.css's .todo-flag. Empty
   (js/catalog.js's loadCampaignBanner only fills it when a promo is
   actually active) collapses to nothing via :empty rather than leaving a
   stray gap. */
#shop-campaign-banner:empty { display: none; }
.shop-campaign-banner {
  display: flex; align-items: center; justify-content: center; gap: 14px; flex-wrap: wrap;
  margin: 24px 0 0;
  padding: 14px 24px; text-align: center;
  background: var(--paper-accent);
  border: 2px dashed var(--accent-2);
  border-radius: 4px;
  font-weight: bold; color: var(--text-dark);
}
.shop-campaign-code { color: var(--accent-2); }
/* --accent-2 already gets a brighter dark-mode value in style.css's own
   :root[data-theme="dark"] block, so no separate override is needed here. */
.shop-campaign-code strong { letter-spacing: 1px; }

/* ---------- Filter bar ---------- */
.filter-bar {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: center;
  margin: 30px 0 50px;
  max-width: 1000px;
  width: 100%;
  overflow-x: auto;
  padding-bottom: 4px;
}
.filter-pill {
  padding: 10px 20px;
  font-weight: bold;
  font-size: 0.95rem;
  white-space: nowrap;
  color: var(--text-dark) !important;
  border: none;
  background: none;
  transition: transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.filter-pill.is-active::before { background: var(--paper-accent); }
.filter-pill.is-active { box-shadow: 0 0 0 2px var(--blue-mat); border-radius: 4px; }
.filter-pill:hover { transform: scale(1.05) rotate(0deg); }
.filter-pill:hover::before { box-shadow: var(--shadow-hover); }

/* ---------- Product grid & cards ---------- */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 34px 28px;
  width: 100%;
  max-width: 1200px;
}
/* A lone .empty-state/.error-state inside this auto-fill grid otherwise
   only occupies the first track and sits pinned to the left. */
.product-grid > .empty-state, .product-grid > .error-state { grid-column: 1 / -1; }
.product-card {
  padding: 18px 18px 22px;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  transition: transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  animation: fadeUp 0.5s ease both;
  position: relative;
}
/* No per-card vertical stagger here on purpose (there used to be an
   :nth-child(3n+2)/:nth-child(3n+3) offset for a "scattered pinboard" look)
   -- it assumed an exact 3-column row, which .product-grid's own
   auto-fill/minmax(240px,1fr) track sizing doesn't guarantee (4 or 5 columns
   on a wide desktop silently misaligns the stagger). A calm, evenly aligned
   grid reads as consistent at every width instead. */
.product-card:hover { transform: rotate(0deg) scale(1.035) translateY(-6px) !important; }
.product-card:hover::before { box-shadow: var(--shadow-hover); }

/* Same cork-mat recipe as .rip--photo::before in style.css (used by the
   quick-view's main photo) — kept in sync so the grid thumbnails and the
   enlarged view read as the same material.
   The grain is an SVG feTurbulence noise filter, not stacked gradients —
   gradients tile in an obviously regular grid at this scale, where fractal
   noise reads as genuine material grain. stitchTiles="stitch" makes it wrap
   seamlessly at the tile edge, but only once the filter's own region is
   pinned to exactly the tile size (x/y/width/height + userSpaceOnUse on
   both <filter> and <feTurbulence>) — left at SVG's default ~120%-expanded
   region, the noise doesn't actually align at the background-size boundary,
   which showed up as a visible seam down the middle of every photo.
   Kept deliberately low-contrast and fine-grained (high baseFrequency, low
   numOctaves, a gentle linear contrast curve, soft-light not overlay) —
   an earlier, punchier version read as blobby ("melted cheese") or
   harshly rough ("turbulence" noise at coarser frequency) rather than the
   smooth, even, understated grain real cork actually has. */
.product-photo-frame {
  padding: 14px;
  margin-bottom: 14px;
  position: relative;
  background-color: var(--photo-mat);
  background-image:
    url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22220%22%20height%3D%22220%22%3E%3Cfilter%20id%3D%22n%22%20x%3D%220%22%20y%3D%220%22%20width%3D%22220%22%20height%3D%22220%22%20filterUnits%3D%22userSpaceOnUse%22%20primitiveUnits%3D%22userSpaceOnUse%22%3E%3CfeTurbulence%20type%3D%22fractalNoise%22%20baseFrequency%3D%220.22%200.26%22%20numOctaves%3D%222%22%20seed%3D%227%22%20stitchTiles%3D%22stitch%22%20x%3D%220%22%20y%3D%220%22%20width%3D%22220%22%20height%3D%22220%22%2F%3E%3CfeColorMatrix%20type%3D%22matrix%22%20values%3D%220.33%200.33%200.33%200%200%200.33%200.33%200.33%200%200%200.33%200.33%200.33%200%200%200%200%200%200%201%22%2F%3E%3CfeComponentTransfer%3E%3CfeFuncR%20type%3D%22linear%22%20slope%3D%220.9%22%20intercept%3D%220.05%22%2F%3E%3CfeFuncG%20type%3D%22linear%22%20slope%3D%220.9%22%20intercept%3D%220.05%22%2F%3E%3CfeFuncB%20type%3D%22linear%22%20slope%3D%220.9%22%20intercept%3D%220.05%22%2F%3E%3C%2FfeComponentTransfer%3E%3C%2Ffilter%3E%3Crect%20width%3D%22220%22%20height%3D%22220%22%20filter%3D%22url(%23n)%22%2F%3E%3C%2Fsvg%3E"),
    radial-gradient(ellipse at center, transparent 65%, rgba(0,0,0,.1) 100%);
  background-size: 220px 220px, 100% 100%;
  background-blend-mode: soft-light, multiply;
  box-shadow: inset 0 0 8px rgba(0,0,0,.12), inset 0 0 0 1px rgba(0,0,0,.12);
  /* Chipped/irregular edge instead of a stark rectangle -- reuses the same
     --tear-b polygon the rest of the site's .rip cards already cut their
     torn-paper silhouette with (style.css), just applied to this mat
     instead of inventing a new shape. NOT a change to the grain recipe
     above (still the exact same feTurbulence texture the CLAUDE.md note on
     this block warns not to re-litigate) -- purely an outline change. The
     14px padding keeps the shallow (a few percent) edge undulations from
     ever clipping into the actual product photo sitting inside it. */
  clip-path: var(--tear-b);
}
.product-photo-frame img { width: 100%; aspect-ratio: 1 / 1; object-fit: contain; transition: transform 0.5s ease, opacity 0.35s ease; cursor: zoom-in; }
.product-card:hover .product-photo-frame img { transform: scale(1.06); }
/* Second photo (front/back, different angle) crossfades in on hover instead
   of the zoom, when a product has one -- stacked on top of the primary shot
   via absolute positioning, invisible until hovered. Cards with only one
   photo never get a .product-photo-hover element at all (see catalog.js),
   so they keep the plain zoom-in-place from the rule above unchanged. */
.product-photo-hover {
  position: absolute;
  inset: 14px;
  opacity: 0;
}
/* Real bug, not a design nitpick: the shared ".product-photo-frame img"
   rule above sets width:100% on every <img> in this frame, including this
   one -- for an absolutely positioned element that resolves against the
   frame's *padding box* (100% of the full frame, not "100% minus the 14px
   inset"), which out-constrains inset's own right/bottom and makes the
   browser drop them. Net effect: this image rendered 28px wider/taller
   than the primary photo while sharing its top-left corner, so it crept
   off-center to the bottom-right instead of sitting in the same inset the
   photo it's replacing uses. Needs a selector at least as specific as
   ".product-photo-frame img" (class+type, (0,1,1)) to actually win --
   ".product-photo-hover" alone is only (0,1,0) and silently lost the
   cascade despite coming later in the file, which is why the first fix
   attempt here (plain width/height on .product-photo-hover) only fixed
   height (nothing else was competing for that) and not width. */
.product-photo-frame .product-photo-hover {
  width: calc(100% - 28px);
  height: calc(100% - 28px);
}
.product-card:hover .product-photo-hover,
.product-card.is-flipped .product-photo-hover { opacity: 1; transform: scale(1.06); }
.product-card:hover .product-photo-primary,
.product-card.is-flipped .product-photo-primary { opacity: 0; }

/* Explicit flip control, not just a :hover discovery -- :hover never fires
   on touch, so without this a phone visitor could never see the second
   photo at all. Bottom-left (badge-soldout/badge-sale already own the
   top-right corner). Only rendered when a product actually has a second
   photo (see catalog.js), same gating as the hover crossfade above. */
.product-photo-flip {
  position: absolute;
  bottom: 22px;
  left: 22px;
  z-index: 2;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275), background 0.2s ease;
}
.product-photo-flip:hover, .product-photo-flip:focus-visible { transform: scale(1.12); background: rgba(0, 0, 0, 0.75); }
.product-photo-flip:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

.product-card h3 { margin: 0 0 6px; font-size: 1.05rem; color: var(--text-dark); line-height: 1.3; }

.price-row { display: flex; align-items: center; gap: 8px; margin-top: auto; }
.price-tag {
  display: inline-block;
  font-weight: bold;
  color: var(--text-dark);
  background: var(--paper-accent);
  padding: 4px 12px;
  border-radius: 3px;
  transform: rotate(-2deg);
  box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.15);
}
.price-compare { text-decoration: line-through; color: var(--text-muted); font-size: 0.9rem; }

.badge-soldout, .badge-sale {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
  width: 68px;
  height: 68px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  line-height: 1.15;
  background: transparent;
  border: 5px double currentColor;
  transform: rotate(-14deg);
  mix-blend-mode: multiply;
  opacity: 0.92;
}
.badge-soldout { color: var(--accent-red); }
.badge-sale { color: var(--accent-green); }

/* ---------- Quick-view modal ----------
   .qv-panel/.qv-close/.qv-divider themselves moved to style.css -- they're
   generic modal chrome now (reused by the YouTube/Twitch video modals and
   the account login modal, on pages that never load this file), not
   shop-specific. What's left here is the shop's own lightbox/gallery
   pieces built on top of that shared chrome. */
.qv-zoom-hint {
  position: absolute;
  bottom: 10px;
  right: 10px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  color: var(--text-dark);
  display: flex;
  align-items: center;
  justify-content: center;
}
.qv-zoom-hint svg { width: 18px; height: 18px; }

.qv-lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(0, 0, 0, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 30px;
  box-sizing: border-box;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.qv-lightbox.is-open { opacity: 1; pointer-events: auto; }
.qv-lightbox img { max-width: 100%; max-height: 100%; object-fit: contain; }
/* Dark backdrop, not the faint white-wash the base rule would give it here —
   this sits over the full-bleed product photo, which can be light or dark. */
.qv-lightbox .qv-close { background: rgba(10, 10, 14, 0.6); color: #fff; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35); top: 20px; right: 20px; }
.qv-lightbox .qv-close:hover { background: rgba(10, 10, 14, 0.8); }

/* ---------- Product detail page ---------- */
.shop-back-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: #fff;
  text-decoration: none;
  font-weight: bold;
  font-size: 0.95rem;
  margin: 18px 0 8px;
  opacity: 0.9;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.shop-back-link svg { width: 18px; height: 18px; flex-shrink: 0; }
.shop-back-link:hover { opacity: 1; transform: translateX(-3px); }

.product-page {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 50px;
  max-width: 1100px;
  width: 95%;
  margin: 10px auto 40px;
  align-items: flex-start;
}
.product-gallery-main { padding: 16px; }
.product-gallery-main img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }
.product-thumbs { display: flex; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
.product-thumb {
  width: 68px; height: 68px; background: var(--photo-mat); padding: 4px;
  box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.25); border: none; cursor: pointer;
  transition: transform 0.25s ease;
}
.product-thumb img { width: 100%; height: 100%; object-fit: cover; }
.product-thumb:hover, .product-thumb.is-active { transform: translateY(-4px) rotate(-2deg); }

.product-info { padding: 36px; }
.product-info h1 { margin: 0 0 14px; font-size: 2rem; }
.product-price-block { display: flex; align-items: center; gap: 12px; margin-bottom: 22px; }
.product-price-block .price-tag { font-size: 1.3rem; transform: rotate(-1.5deg); }
.product-description { line-height: 1.6; margin-bottom: 24px; color: var(--text-dark); }

.option-group { margin-bottom: 20px; }
.option-group > label { display: block; font-weight: bold; margin-bottom: 8px; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.5px; }
.option-swatches { display: flex; gap: 10px; flex-wrap: wrap; }

.swatch-color { display: flex; flex-direction: column; align-items: center; gap: 3px; background: none; border: none; padding: 6px 4px 2px; cursor: pointer; }
.swatch-color .clip { font-size: 1.15rem; transform: rotate(-20deg); margin-bottom: -9px; filter: drop-shadow(1px 2px 1px rgba(0, 0, 0, 0.35)); transition: transform 0.2s ease; }
.swatch-color:nth-child(3n + 1) .clip { transform: rotate(-14deg); }
.swatch-color:nth-child(3n + 2) .clip { transform: rotate(10deg); }
/* The white swatch's own fill is white, same as the border below it — the
   extra thin ring keeps it from disappearing into a light-mode panel;
   barely visible for any other color. */
.swatch-color .chip { width: 32px; height: 32px; border-radius: 2px; border: 3px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.18), 1px 2px 5px rgba(0, 0, 0, 0.3); transition: transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
.swatch-color .label { font-size: 0.62rem; font-weight: bold; color: var(--text-muted); max-width: 50px; text-align: center; line-height: 1.1; }
.swatch-color:hover .chip { transform: scale(1.08) rotate(-3deg); }
.swatch-color.is-selected .chip { transform: scale(1.1) rotate(-2deg); box-shadow: 0 0 0 3px var(--blue-mat), 1px 2px 5px rgba(0, 0, 0, 0.3); }
.swatch-color.is-selected .clip { transform: rotate(-20deg) translateY(2px) scale(1.1); }

.swatch-size {
  /* Same bug class CLAUDE.md's own .cart-close note warns about: a custom
     <button> never inherits text color from its ancestors (browser
     default), so with no color set here this rendered black-on-black once
     --paper turned dark in dark mode -- coincidentally fine in light mode
     only because --paper happens to be light there too. */
  position: relative; padding: 10px 16px 10px 22px; background: var(--paper); color: var(--text-dark); border: none;
  font-weight: bold; font-size: 0.9rem; box-shadow: 1px 2px 5px rgba(0, 0, 0, 0.25);
  clip-path: polygon(14% 0%, 100% 0%, 100% 100%, 14% 100%, 14% 62%, 0% 50%, 14% 38%);
  transition: all 0.2s ease;
}
.swatch-size:nth-child(2n) { transform: rotate(-1.2deg); }
.swatch-size:nth-child(2n + 1) { transform: rotate(1deg); }
.swatch-size:hover { transform: translateY(-3px) rotate(0deg); }
.swatch-size.is-selected { background: var(--paper-accent); box-shadow: 0 0 0 2px var(--blue-mat); transform: translateY(-2px) rotate(0deg); }
.swatch-size:disabled { opacity: 0.4; text-decoration: line-through; cursor: not-allowed; transform: none; }

.qty-stepper { display: inline-flex; align-items: center; background: var(--paper); border-radius: 6px; box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.2); overflow: hidden; }
/* Same missing-color bug as .swatch-size just above -- neither a custom
   button nor a plain input inherits text color from this dark-in-dark-mode
   background on their own. */
.qty-stepper button { background: none; border: none; width: 38px; height: 38px; font-size: 1.1rem; font-weight: bold; color: var(--text-dark); }
.qty-stepper button:hover { background: var(--paper-accent); }
.qty-stepper input { width: 40px; text-align: center; border: none; background: none; font-weight: bold; font-size: 1rem; color: var(--text-dark); }

.add-to-cart-row { display: flex; align-items: center; gap: 16px; margin-top: 26px; flex-wrap: wrap; }
.stock-note { font-size: 0.85rem; color: var(--text-muted); }

/* Its own inset panel (same "tinted box inside the card" language as
   .shop-campaign-banner) instead of a flush dashed-divider section with no
   texture of its own -- reads as its own distinct paper surface, same idea
   as the datenschutz/impressum pages presenting their content in one
   bounded .legal-sheet card rather than loose paragraphs. Not a second
   nested .rip torn edge on purpose (like the campaign banner, two stacked
   torn shapes inside one card would clip against each other) -- a plain
   rounded panel instead. */
.info-accordion {
  margin-top: 30px;
  padding: 6px 18px;
  background: var(--paper-accent);
  border-radius: 4px;
}
.info-accordion details { border-bottom: 2px dashed rgba(0, 0, 0, 0.15); padding: 14px 0; }
.info-accordion details:last-child { border-bottom: none; }
.info-accordion summary { font-weight: bold; cursor: pointer; }
.info-accordion .body-html { margin-top: 10px; line-height: 1.55; color: var(--text-dark); }

@media (max-width: 860px) {
  .product-page { grid-template-columns: 1fr; }
  .product-info { padding: 26px 6px; }
}

