/* ==========================================================================
   DOLL HAUS — base.css
   Reset, @font-face, type primitives, layout primitives, placeholder system.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. FONTS
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Chocogom';
  src: url('../fonts/Chocogom-K7PM7.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* TODO (production): add a WOFF2 above this line for ~30% smaller payload.
   Chocogom currently ships WOFF only, single Regular weight. */

/* POPPINS IS SELF-HOSTED (Blake, 29 Sep 2026), from the OFL release he
   supplied - converted here to woff2 and woff, not re-drawn or substituted.

   WHY. It came from fonts.googleapis.com until now, which was the only
   third-party request left on the site. JAKKS reported type rendering
   wrongly for them; it renders correctly from every machine we have tried,
   and a corporate network blocking fonts.googleapis.com / fonts.gstatic.com
   is the ordinary explanation for a difference that follows the viewer
   rather than the page. Self-hosting removes the question.

   Chocogom was never part of it: it has been self-hosted since the start,
   and the woff in the zip Blake sent is byte-identical to the one already
   in this folder (md5 1c92549c...).

   woff2 first, woff second. woff2 is what every current browser takes; the
   woff is there for whatever the client is running, since something in
   their environment is already behaving unlike ours.

   Four uprights only - 300/400/500/600, exactly what the Google Fonts link
   requested. No italics: nothing in the CSS or the markup sets one. */
@font-face {
  font-family: 'Poppins';
  src: url('../fonts/poppins-300.woff2') format('woff2'),
       url('../fonts/poppins-300.woff') format('woff');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Poppins';
  src: url('../fonts/poppins-400.woff2') format('woff2'),
       url('../fonts/poppins-400.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Poppins';
  src: url('../fonts/poppins-500.woff2') format('woff2'),
       url('../fonts/poppins-500.woff') format('woff');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Poppins';
  src: url('../fonts/poppins-600.woff2') format('woff2'),
       url('../fonts/poppins-600.woff') format('woff');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}


/* --------------------------------------------------------------------------
   2. RESET
   -------------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol {
  margin: 0;
  padding: 0;
}

ul[role='list'], ol[role='list'] { list-style: none; }

img, picture, video, canvas, svg {
  display: block;
  max-width: 100%;
}

input, button, textarea, select { font: inherit; color: inherit; }

button {
  background: none;
  border: 0;
  cursor: pointer;
}

a { color: inherit; text-decoration: none; }

/* PARKED LINK — a route whose target is not built or not signed off. It looks
   exactly as it will when it goes live; it simply does not navigate. Keeping
   the appearance identical is the point: this is a review build, and greying
   out half the site would misrepresent the design. */
a[aria-disabled='true'] { cursor: default; }

body {
  min-height: 100vh;
  background: var(--dh-paper);
  color: var(--dh-ink);
  font-family: var(--f-body);
  font-size: var(--t-body);
  font-weight: var(--t-body-w);
  line-height: var(--t-body-lh);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

body.is-locked { overflow: hidden; }

/* The header is fixed, so every page except the homepage (whose hero sits
   under a transparent overlay header) needs to clear it. */
body:not([data-header='overlay']) [data-main] { padding-top: var(--header-h); }


/* --------------------------------------------------------------------------
   3. FOCUS — every interactive element gets a visible ring.
   -------------------------------------------------------------------------- */

:focus { outline: none; }

/* DUAL-TONE RING — VISIBLE ON ANY SURFACE (21 Sep 2026).

   The old ring was a single ink outline with an .on-dark opt-in, and NOTHING
   in the build ever carried .on-dark, so every control on a dark surface drew
   a #121212 ring on a #121212 background. An audit of 364 focusable elements
   found five live cases at contrast 1.0 — invisible: .skip-link, the
   editorial band's .btn, .agegate__select on submit, .cta-retail, and the dev
   dock toggle. The sign-up pop-up's close X was a sixth.

   Two rings instead of one: an ink ring hugging the control and a white ring
   just outside it. On a light surface the ink reads; on a dark surface the
   white reads. No per-surface list to keep in sync, so a new dark component
   cannot quietly lose its focus indicator again. */
:focus-visible {
  outline: 2px solid var(--dh-invert);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--dh-ink);
}

/* The panel takes focus when the dialog opens (tabindex="-1"); it must never
   paint a ring of its own. */
.signup-pop__panel:focus,
.signup-pop__panel:focus-visible { outline: none; box-shadow: none; }

.skip-link {
  position: absolute;
  left: var(--s-4);
  top: -100px;
  z-index: 2000;
  padding: var(--s-3) var(--s-4);
  background: var(--dh-ink);
  color: var(--dh-invert);
  font: var(--t-label-w) var(--t-label)/1 var(--f-body);
  letter-spacing: var(--t-label-ls);
  text-transform: uppercase;
  transition: top var(--dur) var(--ease);
}
.skip-link:focus { top: var(--s-4); }


/* --------------------------------------------------------------------------
   4. TYPE PRIMITIVES
   Chocogom classes NEVER carry font-weight. Hierarchy is size + tracking
   + case + colour only.
   -------------------------------------------------------------------------- */

.t-display,
.t-h1,
.t-h2,
.t-h3,
.t-nav {
  font-family: var(--f-display);
  text-transform: uppercase;
}

.t-display { font-size: var(--t-display); line-height: var(--t-display-lh); letter-spacing: var(--t-display-ls); }
.t-h1      { font-size: var(--t-h1);      line-height: var(--t-h1-lh);      letter-spacing: var(--t-h1-ls); }
.t-h2      { font-size: var(--t-h2);      line-height: var(--t-h2-lh);      letter-spacing: var(--t-h2-ls); }
.t-h3      { font-size: var(--t-h3);      line-height: var(--t-h3-lh);      letter-spacing: var(--t-h3-ls); }
.t-nav     { font-size: var(--t-nav);     line-height: var(--t-nav-lh);     letter-spacing: var(--t-nav-ls); }

.t-body-lg { font-family: var(--f-body); font-weight: var(--t-body-lg-w); font-size: var(--t-body-lg); line-height: var(--t-body-lg-lh); }
.t-body    { font-family: var(--f-body); font-weight: var(--t-body-w);    font-size: var(--t-body);    line-height: var(--t-body-lh); }

.t-label {
  font-family: var(--f-body);
  font-weight: var(--t-label-w);
  font-size: var(--t-label);
  line-height: var(--t-label-lh);
  letter-spacing: var(--t-label-ls);
  text-transform: uppercase;
}

.t-meta {
  font-family: var(--f-body);
  font-weight: var(--t-meta-w);
  font-size: var(--t-meta);
  line-height: var(--t-meta-lh);
  letter-spacing: var(--t-meta-ls);
}

.ink-70 { color: var(--dh-ink-70); }
.ink-45 { color: var(--dh-ink-45); }
.accent { color: var(--dh-accent); }

/* The positioning mark. Always Poppins, never Chocogom. Periods intact. */
.mark-iconography {
  font-family: var(--f-body);
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: 0.22em;
  line-height: 1;
  text-transform: uppercase;
}

.measure { max-width: 68ch; }


/* --------------------------------------------------------------------------
   5. LAYOUT PRIMITIVES
   -------------------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--max-w);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.wrap--wide  { max-width: none; padding-inline: var(--gutter); }
.wrap--bleed { max-width: none; padding-inline: 0; }
/* Near-full-width: no max-width cap, and a much tighter gutter than the
   editorial default. Used where the content should run to the edges. */
.wrap--flush { max-width: none; padding-inline: var(--s-4); }

.section { padding-block: var(--section-y); }
.section--deep { background: var(--dh-paper-deep); }
.section--sweep { background: var(--dh-sweep); }

.grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--col-gap);
}

.stack > * + * { margin-top: var(--s-4); }
.stack-lg > * + * { margin-top: var(--s-5); }

.rule { border: 0; border-top: var(--hair); }

.center { text-align: center; }
.center-x { margin-inline: auto; }

.vh {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}


/* --------------------------------------------------------------------------
   6. PLACEHOLDER SYSTEM
   Every image on this site is a labeled block. It must read as
   "designed placeholder," never "missing image."
   Holds exact aspect ratio so swapping in a real image causes zero shift.
   -------------------------------------------------------------------------- */

.ph {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: 100%;
  aspect-ratio: var(--ph-ratio, 4 / 5);
  padding: var(--s-4);
  text-align: center;
  background-color: var(--dh-paper-deep);
  overflow: hidden;
  /* No border. No hatch. A flat field with a quiet callout — the layout
     should read as the design, not as a box-drawing exercise. */
}

.ph__label {
  font-family: var(--f-body);
  font-weight: 500;
  font-size: 11px;
  line-height: 1.4;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dh-ink-45);
  max-width: 100%;
  /* Slot labels are identifiers, not copy — a two-line label reads as a
     layout accident. They shrink to fit rather than breaking. */
  white-space: nowrap;
  font-size: clamp(8px, 1.1vw, 11px);
}

.ph__meta {
  font-family: var(--f-body);
  font-weight: 400;
  font-size: 10px;
  line-height: 1.4;
  letter-spacing: 0.06em;
  color: var(--dh-ink-45);
  opacity: 0.62;
}

/* A filled slot holds the identical box, so swapping a real image in for a
   placeholder causes zero layout shift. */
.ph--filled { padding: 0; background-color: var(--dh-sweep); }
.ph--filled img { width: 100%; height: 100%; object-fit: contain; }
/* <picture> is inline by default, so without this an art-directed slot
   collapses to the intrinsic image size instead of filling its box. */
.ph--filled picture { display: block; width: 100%; height: 100%; }

/* Ratio helpers */
.ph--4-5  { --ph-ratio: 4 / 5; }
.ph--16-9 { --ph-ratio: 16 / 9; }
.ph--21-9 { --ph-ratio: 21 / 9; }
.ph--3-1  { --ph-ratio: 3 / 1; }
.ph--4-1  { --ph-ratio: 4 / 1; }
.ph--1-1  { --ph-ratio: 1 / 1; }

/* A placeholder must always be distinguishable from the surface behind it.
   The default fill is --dh-paper-deep, which is IDENTICAL to the background
   of a .section--deep band — so on those sections the slots disappeared
   entirely. Each surface therefore gets its own placeholder fill. */
/* Fills are chosen for clear separation from the surface behind them, not
   just technical difference. On paper-deep (#E6E6E6) a #D4D4D4 slot was only
   18 steps away and read as flat — it now drops to #C2C2C2, ~36 steps, which
   is legible at a glance without turning the layout into a grid of boxes. */
.section--sweep .ph,
.ph--on-sweep   { background-color: #EDEDED; }   /* on white sweep  #FFFFFF */
.section--deep .ph,
.ph--on-deep    { background-color: #C2C2C2; }   /* on paper-deep   #E6E6E6 */
.footer .ph     { background-color: #C2C2C2; }

/* Labels need to hold up on the darker fill. */
.ph--on-deep .ph__label,
.section--deep .ph .ph__label { color: #4A4A4A; }
.ph--on-deep .ph__meta,
.section--deep .ph .ph__meta  { color: #4A4A4A; opacity: 0.7; }

/* Property logo slot — typographic identity + named client asset slot.
   No licensed marks are sourced, drawn or approximated anywhere. */
.ph--logo-slot {
  gap: var(--s-3);
  background-color: var(--dh-paper-deep);
}
.ph--logo-slot .ph__name {
  font-family: var(--f-display);
  text-transform: uppercase;
  font-size: clamp(20px, 3vw, 40px);
  line-height: 1.15;
  letter-spacing: 0.06em;
  color: var(--dh-ink);
}

/* Wordmark placeholder */
.wordmark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: 10px var(--s-4);
  border: var(--hair);
  font-family: var(--f-display);
  text-transform: uppercase;
  letter-spacing: 0.26em;
  line-height: 1;
  white-space: nowrap;
}
.wordmark__tag {
  font-family: var(--f-body);
  font-weight: 400;
  font-size: 9px;
  letter-spacing: 0.1em;
  color: var(--dh-ink-45);
}


/* --------------------------------------------------------------------------
   7. ICONS — fat, chunky, oversized. A signature element, not furniture.
   -------------------------------------------------------------------------- */

.icon {
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon--sm  { width: 20px; height: 20px; stroke-width: 3; }
.icon--nav { width: 24px; height: 24px; stroke-width: 2.75; }
.icon--soc { width: 28px; height: 28px; }
.icon--lg  { width: 40px; height: 40px; stroke-width: 2.25; }

/* Social glyphs are solid marks, not strokes */
.icon--fill { fill: currentColor; stroke: none; }


/* --------------------------------------------------------------------------
   8. SCROLL REVEAL — 24px rise + fade, fires once, never re-animates.
   -------------------------------------------------------------------------- */

/* GATED ON JS. The hidden state applies ONLY when js-reveal is on <html>,
   which ui.js sets the moment it is ready to run the observer. Without that
   gate, a JS error, a blocked script or a slow connection leaves the entire
   page body permanently invisible — the content is there, it just never gets
   switched on. Progressive enhancement: visible is the default, hiding is
   the enhancement. */
html.js-reveal .reveal {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity var(--dur-reveal) var(--ease),
    transform var(--dur-reveal) var(--ease);
}
html.js-reveal .reveal.is-in { opacity: 1; transform: none; }

/* PRINT / PDF / SCREENSHOT.
   A one-shot full-page capture does not scroll, so the IntersectionObserver
   never fires and every section below the fold captures at opacity 0 — the
   page looks empty. It is not, but a screenshot is often the first thing a
   client sees, so print always shows everything. */
@media print {
  html.js-reveal .reveal { opacity: 1 !important; transform: none !important; }
}


/* --------------------------------------------------------------------------
   9. BREAKPOINTS — 1280 / 1024 / 768 / 480
   -------------------------------------------------------------------------- */

@media (max-width: 1280px) {
  :root {
    --t-display: 76px;
    --t-h1:      48px;
  }
}

@media (max-width: 1024px) {
  :root {
    --gutter:    24px;
    --section-y: var(--s-7);
    --t-display: 64px;
    --t-h1:      42px;
    --t-h2:      32px;
  }
}

/* Scale display type down ~20% at <=768px */
@media (max-width: 768px) {
  :root {
    --gutter:     20px;
    --section-y:  80px;
    --t-display:  48px;
    --t-h1:       34px;
    --t-h2:       28px;
    --t-h3:       19px;
    --t-nav:      16px;
    --t-body-lg:  17px;
    --t-body:     15px;
    --t-label:    12px;
    --header-pad:   20px;
    --header-bar-h: 50px;
  }
  .icon     { width: 28px; height: 28px; }
  .icon--soc{ width: 24px; height: 24px; }
}

@media (max-width: 480px) {
  :root {
    --t-display: 40px;
    --t-h1:      30px;
  }

  /* SLOT LABELS WRAP ON THE SMALLEST PHONES (16 Sep 2026).
     .ph__label is nowrap and shrinks to fit, which is right for a slot
     identifier — a two-line label reads as a layout accident. But shrinking
     has a floor (clamp's 8px minimum), and below about 375px it stops being
     enough: measured at 320px, "INSTAGRAM POST 01" needed 104px in a 100px
     tile and "GHOST FACE — PRODUCT SHOT" needed 159px in 153px. Clipped mid
     word is worse than wrapped, so below 480 they wrap. */
  .ph__label { white-space: normal; }
}


/* --------------------------------------------------------------------------
   10. REDUCED MOTION
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
}


/* --------------------------------------------------------------------------
   11. PRINT — minimal, but stops the tab and tray printing.
   -------------------------------------------------------------------------- */

@media print {
  .shop-tab, .tray, .scrim, .anno { display: none !important; }
}
