/* Pipeline-owned. Written before the build; do not edit or overwrite. */

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

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

body { margin: 0; min-height: 100vh; }

/* Hours, prices and phone numbers line up only with tabular figures, and a
   misaligned hours table is a defect no check in this repo can see. Scoped to
   the elements that are numeric by semantics rather than by guess -- the rest
   is one line in the grammar, because CSS cannot tell a price from prose. */
:where(time, td, th) { font-variant-numeric: tabular-nums; }

/* Anchored headings must not land under a sticky header. */
:where(h1, h2, h3, h4, [id]) { scroll-margin-top: 6rem; }

:where(figure, blockquote, dl, dd) { margin: 0; }
:where(ul, ol):where([class]) { margin: 0; padding: 0; list-style: none; }
:where(h1, h2, h3, h4, p) { overflow-wrap: break-word; }

/* ---- Japanese typesetting the browser does not do by default ---- */
/* line-break: strict keeps small kana and the long-vowel mark off the start of
   a line. text-autospace adds the 1/8em between Japanese and Latin or digits
   that every engine ships switched off, so a writer never types that space.
   Headings break between phrases, not inside a word -- test-bbs-deespot-024
   shipped ラインナッ/プ and 質/問 -- and balance the lines they do break into;
   auto-phrase is Chromium-only, and floor.js loads phrase.js for the engines
   without it. Body copy avoids a one-character last line. :where() keeps all
   of it at zero specificity: a designed page still decides. */
:where(html) { line-break: strict; text-autospace: normal; }
:where(h1, h2, h3, h4) { word-break: auto-phrase; text-wrap: balance; }
:where(p, li, dd, blockquote, figcaption) { text-wrap: pretty; }

:where(img, svg, video, iframe) { max-width: 100%; display: block; }
:where(img, video) { height: auto; }

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

/* Every interactive thing is obviously interactive, and tapping it is instant. */
:where(a[href], button, summary, label, [role="button"]) {
  cursor: pointer;
  touch-action: manipulation;
}
:where(summary) { list-style: none; }
:where(summary)::-webkit-details-marker { display: none; }

/* Keyboard users can always see where they are. */
:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
  border-radius: 2px;
}

/* The floor of side padding. A section may have more; none may have none. */
body > :where(header, main, footer) > :where(section, div) {
  padding-left: max(1.25rem, env(safe-area-inset-left));
  padding-right: max(1.25rem, env(safe-area-inset-right));
}

/* Motion is decoration. Content never depends on it. */
@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;
  }
}

/* ---- the motion runtime, loaded by motion.js ---- */
/* GSAP, ScrollTrigger and Lenis come off a CDN, and a CDN has bad minutes. The
   contract is that a reveal's *resting* state is visible and the hidden state is
   scoped under html.motion-on, which motion.js sets synchronously in the head
   and removes again if the libraries do not arrive. This rule is the net under
   the contract: a builder that forgets to scope its hidden state still ships a
   readable page when the libraries fail, rather than a blank one. */
html:not(.motion-on) :where([data-reveal], [data-animate], [data-split]) {
  opacity: 1 !important;
  transform: none !important;
  clip-path: none !important;
  visibility: visible !important;
}
/* ---- the loading curtain, when a design uses one ---- */
/* Two independent nets, because this is the one device that can leave a shop's
   site permanently blank.

   One: the curtain does not exist unless motion.js is alive to remove it —
   html.motion-on is set by that script and by nothing else, so a blocked CDN, a
   parse error and prefers-reduced-motion all mean the visitor never sees a
   curtain at all rather than seeing one forever.

   Two: motion.js adds html.preload-done on an unconditional timer, so the
   curtain lifts on the passage of time rather than on an event. The builder
   styles [data-preload] and gets the reveal for free; it cannot get this wrong,
   because it does not write it. */
html:not(.motion-on) [data-preload] { display: none !important; }
[data-preload] {
  position: fixed;
  inset: 0;
  z-index: 90;
  transition: opacity .45s ease, visibility .45s ease;
}
html.preload-done [data-preload] {
  opacity: 0 !important;
  visibility: hidden !important;
  pointer-events: none !important;
}

/* ---- social post cards ---- */
/* The caption is clamped here rather than in the builder's stylesheet, because
   "long captions get three dots" is the whole reason these cards are ours and
   not Instagram's iframe. A post caption is arbitrary length — some are a
   sentence, some are twenty hashtags — and one long one otherwise stretches its
   card and breaks the row. The builder may override the line-clamp count to
   two or four; it should not have to remember the rest. */
.social-post p {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* The photograph fills its frame at whatever aspect the card asks for. An
   Instagram post is not reliably square. */
.social-post img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---- the posts section the pipeline injects, when the builder wrote none ---- */
/* applySourceOfTruth puts a plain <section id="sns-posts" class="social-posts">
   on the homepage when the page has no post cards at all, because ten posts in
   data.json and nothing on the page is the worse failure. jdm-test-008 shipped
   exactly that: eight cards, our class names, and not one rule matching
   .social-post anywhere in the build's own <style>. The owner's report was
   "design is not align with the page aesthetic" -- every other section carried
   the art direction and this one carried nothing at all.

   This cannot make an injected block match a bespoke section; nothing can. It
   makes it stop looking unstyled: the palette tokens, the page's own spacing
   ladder where it defined one, and a grid instead of eight full-width blocks.

   Everything here is deliberately weak. reset.css loads *before* the builder's
   own <style>, so a section the builder designs itself wins on equal
   specificity and none of this reaches it -- which is the outcome we want, and
   the reason grammar.md now asks for that section by name.

   Scoped under .social-posts rather than applied to .social-post, so a card
   the builder wrote inside its own section keeps only the two rules above. */
.social-posts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: var(--s3, 1.25rem);
  max-width: 72rem;
  margin-inline: auto;
  padding: var(--s6, 4rem) var(--s4, 2rem);
  color: var(--ink);
}
.social-posts > h2, .social-posts > h3 { grid-column: 1 / -1; margin: 0; }
.social-posts .social-post {
  display: flex;
  flex-direction: column;
  gap: var(--s1, .5rem);
  margin: 0;
  padding: var(--s2, .75rem);
  background: var(--surface);
  border-radius: var(--radius, .5rem);
  overflow: hidden;
}
/* Only inside the injected block: a card with no height of its own leaves the
   object-fit rule above with nothing to crop against, so every photograph
   lands at its own aspect and the row goes ragged. */
.social-posts .social-post img { aspect-ratio: 1; }
.social-posts .social-post p { margin: 0; }
.social-posts .social-post time { color: var(--muted); font-size: .875em; }
.social-posts .social-post a { color: var(--accent-ink); }

/* ---- the FAQ and review sections the pipeline injects, same reasoning ---- */
/* rewriteFaqHtml and rewriteReviewsHtml inject a plain
   <section id="faq" class="faq"> / <section id="reviews" class="reviews"> when
   the page has no block of that kind to re-fill -- the shop's ten questions in
   data.json and nothing on the page is the worse failure, exactly as with the
   posts above. The posts section got this treatment after jdm-test-008; these
   two were the same hole one section over and shipped without it.

   Measured on loc_rand_komuten_20260906213639_01, live: every heading the
   builder wrote sits at left 152px in a 1136px column, and the injected
   -- and only the injected -- 「よくあるご質問」 h2 rendered at left 20px, 1400px
   wide, full-bleed against a page that has a content column. It was the
   build's single gate error (container-drift), and it is not a model defect:
   nothing in the page could style a section the page never wrote.

   Same weakness on purpose. reset.css loads before the builder's own
   stylesheet, so a section it designed itself wins on equal specificity and
   none of this reaches it. All this does is put an injected block on the
   page's own column instead of the viewport edge. */
/* .ewb-injected, not .faq -- and the difference was found by building a page
   by hand and running the gate over it. "faq" and "reviews" are names an
   author reaches for: a hand-built page with its own div.faq {max-width:52rem}
   inherited margin-inline:auto from a bare .faq rule here, got centred inside
   a column it was already inside, and came out 160px off it. The gate called
   it container-drift, correctly. A fallback for markup the pipeline injected
   has to aim at markup the pipeline injected.

   (And the comment that first said this used backticks, inside a template
   literal, which is the sixth time that has happened in this file. There is a
   test asserting every asset string is backtick-free; it is the only reason
   this was a syntax error and not a boot failure.) */
.ewb-injected {
  max-width: 72rem;
  margin-inline: auto;
  padding: var(--s6, 4rem) var(--s4, 2rem);
  color: var(--ink);
}
.ewb-injected > h2, .ewb-injected > h3 { margin: 0 0 var(--s3, 1.25rem); }
/* Only the accordion the fallback template writes: a bare <details> has no
   affordance and no spacing, and a stack of them reads as one grey wall. */
.ewb-injected > details {
  border-top: 1px solid var(--surface-2);
  padding: var(--s2, .75rem) 0;
}
.ewb-injected > details > summary { cursor: pointer; padding: var(--s1, .5rem) 0; }
.ewb-injected > details > p { margin: var(--s1, .5rem) 0 0; color: var(--muted); }
.ewb-injected > .review { margin: 0 0 var(--s3, 1.25rem); padding: var(--s2, .75rem) 0; }

/* ---- Instagram embeds, wired by instagram.js ---- */
/* Structure only. The card frame around the embed belongs to the site's own
   stylesheet — this is just the part that must not be got wrong: a frame that
   fills its card, and the text fallback stepping aside once the real post has
   rendered. The fallback stays in the DOM rather than being removed, so the
   post's words are still there for anything that reads the markup. */
.ig-embed { width: 100%; }
.ig-embed > iframe { display: block; width: 100%; border: 0; max-width: 100%; }
.social-post.ig-live [data-ig-fallback] { display: none; }

/* A canvas is decoration: it never carries content, and it never takes clicks. */
canvas[data-webgl] { pointer-events: none; }
html:not(.motion-ready) canvas[data-webgl] { display: none; }

/* Lenis's own three rules, so no site needs a second stylesheet request. */
html.lenis, html.lenis body { height: auto; }
html.lenis { scroll-behavior: auto; }
.lenis.lenis-smooth iframe { pointer-events: none; }
.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }
.lenis :where([data-lenis-prevent], [data-lenis-prevent-wheel], [data-lenis-prevent-touch]) {
  overscroll-behavior: contain;
}

/* ---- the gallery preview overlay, built by lightbox.js ---- */
/* Positioned here because a stylesheet the builder rewrites cannot be trusted
   to keep it fixed, and an overlay in normal flow renders below the footer. */
.ewb-lightbox {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: max(1.5rem, env(safe-area-inset-top)) 1.5rem;
  background: rgba(0, 0, 0, 0.88);
  overscroll-behavior: contain;
  border: 0;
}
.ewb-lightbox[hidden] { display: none; }
.ewb-lightbox img {
  max-width: min(100%, 1200px);
  max-height: 85vh;
  width: auto;
  height: auto;
  object-fit: contain;
}
.ewb-lightbox__close {
  position: absolute;
  top: max(1rem, env(safe-area-inset-top));
  right: 1rem;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
}
