/* 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], [data-ewb-reveal], [data-ewb-stagger] > *) {
  opacity: 1 !important;
  transform: none !important;
  clip-path: none !important;
  visibility: visible !important;
}
/* ---- entry reveals, owned by the pipeline ---- */
/* data-ewb-reveal on an element (up, fade, scale, clip) and data-ewb-stagger on
   a parent: motion.js adds .ewb-in when the element enters the viewport. The
   hidden state and the reveal live on the same element, under motion-on, so the
   shape that hid rca-8791's twelve photographs -- a hidden <img> inside a
   wrapper something else animated -- cannot be written with these. */
html.motion-on [data-ewb-reveal]:not(.ewb-in) { opacity: 0; transform: translateY(24px); }
html.motion-on [data-ewb-reveal="fade"]:not(.ewb-in) { transform: none; }
html.motion-on [data-ewb-reveal="scale"]:not(.ewb-in) { transform: scale(.96); }
html.motion-on [data-ewb-reveal="clip"]:not(.ewb-in) { opacity: 1; transform: none; clip-path: inset(0 0 calc(100% - 1px) 0); }
[data-ewb-reveal] {
  transition: opacity .8s cubic-bezier(.2, .7, .2, 1) var(--ewb-delay, 0s),
    transform .8s cubic-bezier(.2, .7, .2, 1) var(--ewb-delay, 0s),
    clip-path 1.1s cubic-bezier(.65, 0, .35, 1) var(--ewb-delay, 0s);
}
/* ---- 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 the card's width and crops to whatever aspect the
   card gives it. An Instagram post is not reliably square. No height: 100% --
   inside a card with a height of its own (a grid row, an aspect-ratio) that
   made the photograph the whole card and pushed caption, date and link out
   over the next row (local-svc-2, every card at 834 and 1440). */
.social-post img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
}

/* ---- the posts section the pipeline injects, when the builder wrote none ---- */
/* applySourceOfTruth puts a <section id="sns-posts" class="social-posts"> on
   the homepage when the page has no post cards, because ten posts in
   data.json and nothing on the page is the worse failure. This is its floor,
   and only for a build that did not style it: the section is marked
   data-ewb-styled when the site's CSS styles .social-post(s), and nothing
   here reaches a marked one.

   Two layouts measured bad on dev builds (2026-10-17) and replaced: grey
   boxes in a 4-column grid that left one card alone on the last row (nine
   posts), and nine full-width cards stacked on a phone, 4,433px of scrolling.
   The column count now follows the number of posts so a row is never left
   with one card, and a phone gets one swipeable row. */
/* Its own column only when it could not borrow the page's (data-ewb-frame:
   built inside the page's section and wrapper classes). */
:where(.social-posts:not([data-ewb-styled]):not([data-ewb-frame])) {
  max-width: 72rem; margin-inline: auto;
  padding: var(--s6, 4rem) var(--s4, 2rem); color: var(--ink);
}
/* The gap under the heading lives on the grid: a site's own h2 { margin: 0 }
   outranks anything here, and it never styles the grid. */
:where(.social-posts:not([data-ewb-styled]) .social-posts-grid) {
  --ig-cols: 4; margin-top: var(--s4, 2rem);
  display: grid; grid-template-columns: repeat(var(--ig-cols), minmax(0, 1fr));
  gap: var(--s4, 2rem) var(--s3, 1.25rem);
}
/* Columns by count, so the last row is full: 9 is 3x3, 10 is 5x2, 6 is 3x2. */
:where(.social-posts:not([data-ewb-styled]) .social-posts-grid:has(> .social-post:nth-child(3):last-child)),
:where(.social-posts:not([data-ewb-styled]) .social-posts-grid:has(> .social-post:nth-child(5):last-child)),
:where(.social-posts:not([data-ewb-styled]) .social-posts-grid:has(> .social-post:nth-child(6):last-child)),
:where(.social-posts:not([data-ewb-styled]) .social-posts-grid:has(> .social-post:nth-child(9):last-child)) { --ig-cols: 3; }
:where(.social-posts:not([data-ewb-styled]) .social-posts-grid:has(> .social-post:nth-child(2):last-child)) { --ig-cols: 2; }
:where(.social-posts:not([data-ewb-styled]) .social-posts-grid:has(> .social-post:nth-child(10):last-child)) { --ig-cols: 5; }
:where(.social-posts:not([data-ewb-styled]) .social-post) {
  display: flex; flex-direction: column; gap: var(--s1, .5rem); margin: 0; min-width: 0;
}
:where(.social-posts:not([data-ewb-styled]) .social-post img) { aspect-ratio: 1; border-radius: var(--radius, .25rem); }
:where(.social-posts:not([data-ewb-styled]) .social-post [data-ig-fallback]) {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .25rem .75rem;
}
:where(.social-posts:not([data-ewb-styled]) .social-post p) {
  flex: 1 0 100%; margin: 0; font-size: .9375em; line-height: 1.7; -webkit-line-clamp: 2;
}
:where(.social-posts:not([data-ewb-styled]) .social-post time) { color: var(--muted); font-size: .8125em; }
:where(.social-posts:not([data-ewb-styled]) .social-post a) {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--accent-ink); font-size: .8125em;
}
/* A phone: one row that swipes, each card most of the screen wide. */
@media (max-width: 719px) {
  :where(.social-posts:not([data-ewb-styled]) .social-posts-grid) {
    grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 78%;
    overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none;
    padding-bottom: var(--s1, .5rem);
  }
  :where(.social-posts:not([data-ewb-styled]) .social-post) { scroll-snap-align: start; }
}

/* ---- 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; }

/* ---- the menu / service list, written by offerings.js ---- */
/* The markup is the pipeline's and the look is the builder's: studio.md asks
   for every ewb-offer-* class in styles.css. This is the floor for a build
   that did not, and only for that build: offerings.js marks the block
   data-ewb-styled when the site's own CSS styles it, and nothing here
   reaches a marked block -- zero specificity alone still filled in every
   property the author left unset. */
:where(.ewb-offerings:not([data-ewb-styled])) { max-width: 72rem; margin-inline: auto; color: var(--ink); }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-nav) {
  position: sticky; top: var(--chrome-h, 0px); z-index: 2;
  display: flex; gap: var(--s1, .5rem); overflow-x: auto; scrollbar-width: none;
  margin: 0 0 var(--s4, 2rem); padding: var(--s1, .5rem) 0; background: var(--bg);
}
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-nav a) {
  flex: none; display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 var(--s2, .75rem); border: 1px solid var(--surface-2);
  border-radius: 999px; color: var(--ink); text-decoration: none;
}
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-section) { margin: 0 0 var(--s5, 3rem); scroll-margin-top: calc(var(--chrome-h, 0px) + 4rem); }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-heading) { margin: 0 0 var(--s3, 1.25rem); }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-section-desc) { margin: 0 0 var(--s3, 1.25rem); color: var(--muted); }
/* Photographed items lead each category, one under another at full width;
   the plain rows below them are the dense list. */
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-features) {
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--s3, 1.25rem); list-style: none; margin: 0 0 var(--s3, 1.25rem); padding: 0;
}
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-features .ewb-offer-item) {
  display: grid; grid-template-columns: minmax(0, 1fr); background: var(--surface);
  border-radius: var(--radius, .5rem); overflow: hidden;
}
@media (min-width: 720px) {
  :where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-features .ewb-offer-item) { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); }
}
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-list) {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
  column-gap: var(--s4, 2rem); list-style: none; margin: 0; padding: 0;
}
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-list .ewb-offer-item) { border-bottom: 1px solid var(--surface-2); }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-img) { display: block; width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-body) { display: flex; flex-direction: column; gap: var(--s1, .5rem); padding: var(--s2, .75rem) 0; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-features .ewb-offer-body) { padding: var(--s3, 1.25rem); }
/* A plain row reads like a printed menu: name left, price right on the same
   line, anything else (description, variants) below across the full width. */
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-list .ewb-offer-body) {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--s2, .75rem); row-gap: .25rem;
}
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-list .ewb-offer-body > *) { grid-column: 1 / -1; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-list .ewb-offer-body > .ewb-offer-name) { grid-column: 1; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-list .ewb-offer-body > .ewb-offer-price) { grid-column: 2; grid-row: 1; white-space: nowrap; text-align: right; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-body p) { margin: 0; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-name) { font-weight: 700; overflow-wrap: anywhere; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-price) { color: var(--accent-ink); font-weight: 700; font-feature-settings: "tnum"; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-price-note) { margin-left: .5em; color: var(--muted); font-size: .875em; font-weight: 400; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-desc), :where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-duration) { color: var(--muted); font-size: .9375em; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-contents), :where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-variants), :where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-tags) { list-style: none; margin: 0; padding: 0; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-contents li) { padding: .25em 0; border-top: 1px solid var(--surface-2); font-size: .9375em; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-contents-label) { display: inline-block; min-width: 4.5em; color: var(--muted); }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-variants li) { display: flex; justify-content: space-between; gap: 1em; font-size: .9375em; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-tags) { display: flex; flex-wrap: wrap; gap: .375em; }
:where(.ewb-offerings:not([data-ewb-styled]) .ewb-offer-tags li) { padding: .125em .625em; border-radius: 999px; background: var(--surface-2); font-size: .8125em; }

/* ---- the layout kit ---- */
/* Six layouts a page is built from, each already correct at 390, 834 and
   1440: the collapsed grid, the column that runs off the phone, the text
   column at eight characters a line and the block that leaves the page's
   column were the most common geometry defects the gate measured, and each is
   a layout written from scratch missing its phone rule. Zero specificity, so
   any rule the site writes wins; tuned through custom properties rather than
   overridden. The look -- type, colour, borders, motion -- is the site's. */
:where(.ewb-wrap) {
  width: min(100% - 2 * var(--gutter, clamp(1.25rem, 5vw, 3rem)), var(--wrap, 72rem));
  margin-inline: auto;
}
:where(.ewb-stack) { display: flex; flex-direction: column; gap: var(--gap, 1.25rem); }
:where(.ewb-stack, .ewb-grid, .ewb-split, .ewb-media, .ewb-row) > * { min-width: 0; }
:where(.ewb-grid) {
  display: grid; gap: var(--gap, 1.25rem);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--grid-min, 16rem)), 1fr));
}
:where(.ewb-split) { display: grid; gap: var(--gap, clamp(1.5rem, 4vw, 4rem)); grid-template-columns: minmax(0, 1fr); align-items: var(--split-align, center); }
@media (min-width: 900px) {
  :where(.ewb-split) { grid-template-columns: minmax(0, var(--split-a, 1fr)) minmax(0, var(--split-b, 1fr)); }
  :where(.ewb-split[data-flip]) > :first-child { order: 2; }
}
:where(.ewb-media) { display: grid; gap: var(--gap, 1rem); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 600px) {
  :where(.ewb-media) { grid-template-columns: minmax(0, var(--media-w, 40%)) minmax(0, 1fr); align-items: start; }
}
:where(.ewb-row) {
  display: grid; grid-auto-flow: column; grid-auto-columns: var(--row-item, min(78%, 20rem));
  gap: var(--gap, 1rem); overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scrollbar-width: thin; padding-bottom: .5rem;
}
:where(.ewb-row) > * { scroll-snap-align: start; }
:where(.ewb-measure) { max-inline-size: var(--measure, 36em); }
:where(.ewb-wrap, .ewb-stack, .ewb-grid, .ewb-split, .ewb-media, .ewb-row) img { max-width: 100%; height: auto; }

/* Structure, not look, so it applies to a styled menu too: the body fills
   its row. A build that set display:flex on the li left the body shrunk to
   the name's width, so every price right-aligned to a different edge. */
:where(.ewb-offer-item > .ewb-offer-body) { flex: 1 1 auto; min-width: 0; }

/* ---- the FAQ, the reviews, the hours (shopBlocks.js) and the social links (socialLinks.js) ---- */
/* Same rule as the menu: the floor for a build that did not style the
   ewb-faq / ewb-review / ewb-hours classes, and nothing for one that did. */
:where(.ewb-faq:not([data-ewb-styled])) { display: grid; gap: 0; border-top: 1px solid var(--surface-2); }
:where(.ewb-faq:not([data-ewb-styled]) .ewb-faq-item) { border-bottom: 1px solid var(--surface-2); }
:where(.ewb-faq:not([data-ewb-styled]) .ewb-faq-q) {
  display: flex; align-items: center; justify-content: space-between; gap: 1em;
  min-height: 44px; padding: var(--s2, .75rem) 0; cursor: pointer; font-weight: 700; list-style: none;
}
:where(.ewb-faq:not([data-ewb-styled]) .ewb-faq-q)::-webkit-details-marker { display: none; }
:where(.ewb-faq:not([data-ewb-styled]) .ewb-faq-q)::after { content: "+"; flex: none; color: var(--accent-ink); font-weight: 400; }
:where(.ewb-faq:not([data-ewb-styled]) .ewb-faq-item[open] .ewb-faq-q)::after { content: "−"; }
:where(.ewb-faq:not([data-ewb-styled]) .ewb-faq-a) { padding: 0 0 var(--s3, 1.25rem); color: var(--muted); }
:where(.ewb-faq:not([data-ewb-styled]) .ewb-faq-a p) { margin: 0 0 .5em; }
:where(.ewb-reviews:not([data-ewb-styled])) {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr)); gap: var(--s3, 1.25rem);
}
:where(.ewb-reviews:not([data-ewb-styled]) .ewb-review) {
  display: flex; flex-direction: column; gap: var(--s1, .5rem); margin: 0;
  padding: var(--s3, 1.25rem) 0; border-top: 1px solid var(--surface-2);
}
:where(.ewb-reviews:not([data-ewb-styled]) .ewb-review-stars) { margin: 0; color: var(--accent-ink); letter-spacing: .1em; }
:where(.ewb-reviews:not([data-ewb-styled]) .ewb-review-text) { margin: 0; }
:where(.ewb-reviews:not([data-ewb-styled]) .ewb-review-text p) { margin: 0 0 .5em; }
:where(.ewb-reviews:not([data-ewb-styled]) .ewb-review-meta) { display: flex; flex-wrap: wrap; gap: .75em; margin: auto 0 0; color: var(--muted); font-size: .875em; }
:where(.ewb-hours:not([data-ewb-styled])) { display: grid; gap: 0; margin: 0; font-feature-settings: "tnum"; }
:where(.ewb-hours:not([data-ewb-styled]) .ewb-hours-row) {
  display: grid; grid-template-columns: minmax(4em, auto) minmax(0, 1fr); gap: 1em;
  padding: .375em 0; border-bottom: 1px solid var(--surface-2);
}
:where(.ewb-hours:not([data-ewb-styled]) dt) { font-weight: 700; }
:where(.ewb-hours:not([data-ewb-styled]) dd) { margin: 0; }
:where(.ewb-hours:not([data-ewb-styled]) .is-closed dd) { color: var(--muted); }
:where(.ewb-social-links:not([data-ewb-styled])) {
  display: flex; flex-wrap: wrap; gap: .5em 1.25em; margin: var(--s2, .75rem) 0 0; padding: 0; list-style: none;
}
:where(.ewb-social-links:not([data-ewb-styled]) .ewb-social-link) {
  display: inline-flex; align-items: center; min-height: 44px; color: inherit;
  text-decoration: underline; text-underline-offset: .2em;
}

/* ---- 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;
}
