/* ==================================================================
   GCH - GANGNAM CHIMAEK HOUSE
   Casual restaurant example, example site number 2.
   First build - wd-s14, Aug 20 2026.

   >> EVERY VALUE IN THE RAIL BLOCK IS COPIED FROM `_lab/rail-3d.html`,
      WHICH IS THE LOCKED FILE. Do not re-derive any of it - the
      palette, the wash, the logo treatment and all six depth
      treatments cost a whole session to settle. If a rail value here
      disagrees with that file, that file is right.

   Palette   A2 #DC2E14 on every coloured element, white ground,
             ink #141210.  NO YELLOW.
   Rail      light honey oak bar top, wash A, anchored left.
   Logo      ghc-logo.png - carries the name, so no separate wordmark.
   Depth     the 7 Star inset pair on every lifted element: a lit line
             on the top edge, a dark line on the bottom.
================================================================== */

/* ---------- tokens ---------- */
:root{
  --a:#DC2E14;              /* accent A2 - white text on it is 4.73:1, just over AA */
  --a-dark:#B8210C;
  --a-lift:#EE4526;
  --ground:#FFFFFF;
  --ink:#141210;
  --ink-soft:#5E5952;
  --line:#EAE6E0;
  --line-strong:#CFC9C0;
  --rail-w:232px;

  /* >> DISPLAY FACE IS A SINGLE VARIABLE ON PURPOSE (wd-s14).
     The owner is not sold on Archivo Black and the untested direction is a heavy
     CONDENSED face. Swapping the whole page is one line here. Specimens of the four
     candidates against real page furniture are in `_lab/type-options.html`. */
  --font-display:'Anton', 'Archivo Black', Impact, sans-serif;
  --font-body:'Archivo', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
}

*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
@media (prefers-reduced-motion: reduce){ html{scroll-behavior:auto} }

body{
  font-family:var(--font-body);
  background:var(--ground);color:var(--ink);
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{max-width:100%;display:block}
a{color:inherit}

/* the anchor targets sit under a fixed rail, not a top bar, so no scroll-margin is needed
   on desktop. On mobile the rail IS a top bar, so the offset is applied in the media query. */

.wrap{max-width:1040px;margin:0 auto;padding:0 clamp(1.2rem,4vw,2.6rem)}

/* ============================================================
   1. THE RAIL
   Values copied verbatim from _lab/rail-3d.html.
   ============================================================ */
.rail{
  position:fixed;top:0;left:0;bottom:0;width:var(--rail-w);z-index:60;
  color:var(--ink);
  padding:1.4rem 1.15rem 1.5rem;display:flex;flex-direction:column;gap:1.05rem;
  background-image:url('https://media.7starwebstudio.com/example/gch/images/hp-rail-bg.jpg');
  /* >> ANCHORED LEFT. The source has one small knot on its right edge about 60% down;
     the rail is far narrower than the source, so anchoring left crops the knot away
     instead of parking it behind the hours block. */
  background-size:cover;background-position:left center;
  box-shadow:
    inset -21px 0 30px -19px rgba(60,34,10,.62),
    inset 0 16px 23px -15px rgba(255,250,240,.62),
    13px 0 32px rgba(0,0,0,.39),
    4px 0 7px rgba(0,0,0,.26);
  border-right:1px solid rgba(255,246,232,.34);
}
/* wash A - a light warm film so type sits on the grain cleanly without flattening it */
.rail::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:
    linear-gradient(180deg, rgba(255,250,242,.30), rgba(255,248,236,.16)),
    linear-gradient(90deg, rgba(255,252,246,.16) 0%, rgba(120,74,28,.10) 100%);
}
.rail > *{position:relative;z-index:1}

.rail .logo{display:block;width:100%;margin:-.15rem 0 .1rem}
.rail .logo img{
  width:100%;height:auto;
  /* ONE shadow. Chained drop-shadow filters compound - each operates on the previous
     one's OUTPUT, shadow included, which packs grey haze into the letter counters. */
  filter:drop-shadow(0 7px 11px rgba(50,28,8,.42));
}

.rail nav{display:flex;flex-direction:column;gap:.4rem}
.rail nav a{
  font-size:.85rem;font-weight:700;text-decoration:none;color:var(--ink);
  text-transform:uppercase;letter-spacing:.02em;width:fit-content;
  text-shadow:
    0 1px 0 rgba(255,252,244,.81),
    0 -1px 0 rgba(90,56,20,.24),
    0 3px 4px rgba(60,34,10,.36);
  transition:transform .16s, text-shadow .16s, color .16s;
}
.rail nav a.on{color:var(--a)}
@media (hover:hover) and (pointer:fine){
  .rail nav a:hover{transform:translateY(-1px);
    text-shadow:0 1px 0 rgba(255,252,244,.9),0 -1px 0 rgba(90,56,20,.26),0 5px 7px rgba(60,34,10,.42)}
}

/* >> THE CTA IS THE PHONE NUMBER ITSELF, tappable. In the base build the conversion
   action is a call or directions - both real, both need no backend. */
.rail .cta{
  display:block;text-align:center;text-decoration:none;color:#fff;
  font-family:var(--font-display);font-size:1.02rem;letter-spacing:.01em;
  padding:.72rem .5rem;border-radius:6px;
  background:linear-gradient(180deg,var(--a-lift) 0%, var(--a) 55%, var(--a-dark) 100%);
  box-shadow:
    0 13px 26px rgba(120,20,4,.47),
    0 4px 8px rgba(60,20,6,.37),
    inset 0 2px 0 rgba(255,255,255,.46),
    inset 0 -4px 0 rgba(0,0,0,.30);
  text-shadow:0 1px 2px rgba(0,0,0,.34);
  transition:transform .16s, box-shadow .16s;
}
@media (hover:hover) and (pointer:fine){
  .rail .cta:hover{transform:translateY(-2px);
    box-shadow:0 18px 34px rgba(120,20,4,.54),0 5px 9px rgba(60,20,6,.4),inset 0 2px 0 rgba(255,255,255,.52),inset 0 -4px 0 rgba(0,0,0,.30)}
}
.rail .cta:active{transform:translateY(1px);
  box-shadow:0 4px 10px rgba(120,20,4,.4),inset 0 2px 4px rgba(0,0,0,.32),inset 0 -1px 0 rgba(255,255,255,.18)}

/* >> HOURS SIT DIRECTLY UNDER THE PHONE BUTTON ON PURPOSE (wd-s14 research).
   "They do not answer the phone" is a named complaint at the chain on this block, and our
   CTA is a phone number - so the hours have to be next to it, not in the footer. */
.rail .meta{
  font-size:.69rem;line-height:1.55;font-weight:600;color:var(--ink);
  padding:.58rem .68rem .62rem;border-radius:8px;
  background:linear-gradient(180deg, rgba(255,253,248,.56), rgba(252,242,226,.30));
  border:1px solid rgba(255,252,246,.5);
  box-shadow:
    0 8px 17px rgba(60,34,10,.26),
    0 2px 4px rgba(60,34,10,.20),
    inset 0 2px 0 rgba(255,255,255,.80),
    inset 0 -3px 0 rgba(90,56,20,.155);
}
.rail .meta .k{
  display:block;font-size:.55rem;letter-spacing:.11em;text-transform:uppercase;
  font-weight:800;margin-bottom:.16rem;color:var(--a);
  text-shadow:0 1px 0 rgba(255,252,244,.7);
}
.rail .meta a{text-decoration:none}

/* >> THE RAIL'S ICONS SPAN THE FULL COLUMN, so their row lines up with the left and right
   edges of the hours and find-us plaques above them. Bunched at one end with a fixed gap
   they read as a leftover group rather than the last item in the same stack. `space-between`
   distributes them instead of sizing them, so the circles stay 24px.
   The footer keeps a plain gap - its icons sit in a grid track, not in a fixed-width column,
   so there is no edge for them to line up with. */
.socials{display:flex;gap:.45rem;margin-top:auto;padding-top:1rem}
.rail .socials{justify-content:space-between;gap:.3rem}
.socials a{
  width:24px;height:24px;border-radius:50%;display:grid;place-items:center;
  background:linear-gradient(180deg,#FFFBF4,#E4D4BB);
  border:1px solid rgba(90,56,20,.22);
  box-shadow:
    0 6px 12px rgba(60,34,10,.34),
    0 2px 3px rgba(60,34,10,.24),
    inset 0 2px 0 rgba(255,255,255,.97),
    inset 0 -3px 0 rgba(90,56,20,.19);
  transition:transform .16s;
}
.socials svg{width:12px;height:12px;fill:#3A2410}
@media (hover:hover) and (pointer:fine){ .socials a:hover{transform:translateY(-2px)} }

/* the mobile bar controls - hidden on desktop */
.bar-tools{display:none}

/* ============================================================
   2. PAGE SHELL
   ============================================================ */
.main{margin-left:var(--rail-w)}

.section{padding:clamp(3.4rem,7vw,5.6rem) 0;border-top:1px solid var(--line)}
.section:first-of-type{border-top:0}

.kicker{
  font-family:var(--font-display);font-size:.8rem;letter-spacing:.11em;
  text-transform:uppercase;color:var(--a);margin-bottom:.6rem;
}
.sec-head h2{
  font-family:var(--font-display);font-weight:400;
  font-size:clamp(2rem,4.6vw,3.2rem);line-height:.98;letter-spacing:-.01em;
  text-transform:uppercase;
}
.sec-head p{
  margin-top:.9rem;max-width:56ch;color:var(--ink-soft);
  font-size:1.02rem;line-height:1.62;
}
.sec-head{margin-bottom:clamp(1.8rem,4vw,2.8rem)}

/* buttons - the house treatment, solid accent and outline */
.btn{
  display:inline-block;font-family:var(--font-display);font-size:.92rem;letter-spacing:.02em;
  text-transform:uppercase;text-decoration:none;padding:.72rem 1.3rem;border-radius:4px;
  border:0;cursor:pointer;transition:transform .16s, box-shadow .16s;
}
.btn-solid{
  background:var(--a);color:#fff;
  box-shadow:0 10px 22px rgba(120,20,4,.44),
             inset 0 2px 0 rgba(255,255,255,.40),
             inset 0 -4px 0 rgba(0,0,0,.28);
}
.btn-out{background:transparent;color:var(--ink);border:2px solid var(--ink);padding:.7rem 1.3rem}

/* >> `.btn-glass` IS THE SECONDARY BUTTON ON A DARK OR PHOTOGRAPHIC GROUND (owner, wd-s14).
   The hero previously ran `.btn-out` - a black outline with black text - which on near-black
   amber read as a hole cut in the picture. This is the 7 Star glass button: translucent fill,
   backdrop blur, and the same inset pair every lifted element on this page carries.

   >> IT NEEDS NO PILL UNDER IT, AND THAT IS THE RULE, NOT AN OVERSIGHT. A frosted button is
   see-through, so it needs something behind it to catch - on a photographic hero the
   photograph IS that surface. The hugging pill exists for flat near-black only.

   >> VISIT'S "DIRECTIONS" NOW CARRIES IT TOO (owner, wd-s16). It ran `.btn-out` while that
   column sat on white; the column has a photograph behind it now, so the same reasoning
   that put glass in the hero applies here - and the two "Call to order / secondary" pairs
   on the page finally match. `.btn-out` is unused as a result; left defined, since a future
   secondary on a plain white ground is exactly what it is for. */
/* >> IT IS FROSTED ICE, NOT SMOKED GLASS (owner, wd-s14). The first version was a dark
   translucent panel, which on a dark hero is quiet by definition - it borrows whatever is
   behind it and that is already dark. Flipping the fill to WHITE makes the button the
   lightest object in the frame, which is what makes it pop next to the red one.

   Three things create the ice, and all three are needed:
     1. a white fill that is genuinely translucent (.62 to .44 top to bottom) so the amber
        hero still reads through it - a solid white button is just a white button;
     2. a cool blue-grey in the lower stop and in the bottom inset. **The chill is in the
        SHADOW, not in the fill** - tinting the whole button blue makes it look like a
        colour we do not own;
     3. a near-white top inset at .95, which is the lit edge of a block of ice.
   >> THE TEXT GOES DARK. White text on a white frost is unreadable at any blur, and the
   page's own ink keeps it inside the one-look-per-element rule. */
.btn-glass{
  /* >> THIS IS THE HOUSE LIQUID-GLASS BUTTON, `.btn-liquid`, COPIED FROM
     `7star/site/assets/css/style.css` - the "See the numbers" button.
     Same eight-layer shadow stack, same transparent fill, same blurred backdrop layer.
     Only two values are swapped: the outer glow takes GCH's accent rather than the
     agency red, and the radius stays 4px so it matches "See the menu" beside it.
     Size and padding come from `.btn` and are untouched.

     >> AND IT RUNS WITHOUT THE PILL UNDER IT, WHICH IS THE STANDARD, NOT A SHORTCUT.
     The shared rule: a frosted button needs a surface behind it to catch, and on FLAT
     NEAR-BLACK there is nothing there - which is why the studio site's version sits on
     a hugging glass bar. On a LIGHT OR PHOTOGRAPHIC background the button needs no
     surface, because the picture already is one. This hero is photographic, so the
     bar would be adding a layer to solve a problem this ground does not have -
     and it would make the two hero buttons structurally different for no reason. */
  position:relative;isolation:isolate;
  color:#fff;background:transparent;border:0;
  text-shadow:0 1px 3px rgba(0,0,0,.55);
  box-shadow:
    0 0 8px rgba(0,0,0,0.10),
    0 3px 10px rgba(0,0,0,0.35),
    inset 3px 3px .5px -3.5px rgba(255,255,255,0.55),
    inset -3px -3px .5px -3.5px rgba(255,255,255,0.55),
    inset 1px 1px 1px -.5px rgba(255,255,255,0.35),
    inset -1px -1px 1px -.5px rgba(255,255,255,0.35),
    inset 0 0 8px 7px rgba(255,255,255,0.06),
    0 0 14px rgba(220,46,20,0.22);
}
/* the frosted pane itself - a separate layer so the blur sits BEHIND the text */
.btn-glass::before{
  content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;
  background:rgba(255,255,255,0.05);
  backdrop-filter:blur(8px) saturate(150%);
  -webkit-backdrop-filter:blur(8px) saturate(150%);
}
@media (hover:hover) and (pointer:fine){
  .btn-solid:hover{transform:translateY(-2px);box-shadow:0 16px 30px rgba(120,20,4,.5),inset 0 2px 0 rgba(255,255,255,.46),inset 0 -4px 0 rgba(0,0,0,.28)}
  .btn-out:hover{transform:translateY(-2px)}
  .btn-glass:hover{transform:translateY(-2px);filter:brightness(1.14)}
}
/* touch gets the same treatment on press, since it has no hover */
.btn-glass:active{transform:translateY(1px);filter:brightness(.94)}
.btn-row{display:flex;gap:.7rem;flex-wrap:wrap;align-items:center;margin-top:1.4rem}

/* ============================================================
   3. HERO  -  still + Ken Burns push
   >> THE SLOT IS BUILT SO A VIDEO DROPS IN LATER WITHOUT A LAYOUT CHANGE.
      `.hero-media` is the only thing that would be replaced.
   ============================================================ */
/* >> NO `.section` PADDING ON THE HERO. It carries the class for the border reset, but its
   own inner padding places the copy - the two stacked left about 150px of dead amber below
   the buttons, which read as the hero being unfinished rather than as breathing room. */
/* >> THE HERO FILLS THE SCREEN. IT WAS `clamp(460px,78vh,760px)` AND THE CAP WAS THE BUG
   (owner, wd-s24): "we need to lengthen section so that image fills landing zone, and sec2
   doesn't come into view."
   Measured before the change: at 1440x900 the hero was 702px and SIGNATURE was already
   198px up the screen; at 1600x1000 the 760px ceiling bit and it was 240px. The taller the
   window, the worse it got - which is why it reads as a short hero rather than as a cap.
   >> `100vh` FIRST, `100dvh` SECOND, AND THE ORDER IS THE FALLBACK. A browser that does not
   know `dvh` keeps the `vh` line; one that does overwrites it. On a laptop the two are the
   same number - there is no address bar to move - so this only does work on a phone.
   >> THE COPY IS BOTTOM-ANCHORED (`align-items:flex-end`), so it moves DOWN with the taller
   frame on its own and its distance from the rail never changes. That is the owner's ask
   met by the existing structure - do not add a vertical offset on top of it. */
.hero{position:relative;min-height:100vh;min-height:100dvh;display:flex;align-items:flex-end;
  overflow:hidden;border-top:0;padding:0}
.hero-media{
  position:absolute;inset:0;
  /* >> THE HERO PHOTOGRAPH, wd-s15. 2560x1440, generated on Lucid Origin and cleaned up in
     two FLUX.1 Kontext passes (writing off the carton, labels off the bottle necks), then
     upscaled. Composed so the LEFT of the frame is empty dark table - that is where the copy
     sits, under `.hero::after`. Do not swap in a centred image without re-checking the scrim.
     `#56341a` is the fallback while the file loads, sampled from the picture's mid-brown. */
  overflow:hidden;background:#56341a;
}
/* >> THE KEN BURNS PUSH - VALUES COPIED VERBATIM FROM THE DIGITAL BROCHURE, NOT DERIVED.
   Source: `7Star-RealEstate-VideoMarketing/html-live-prod/brochures/brochure-example-1/
   brochure-example-1.html`, the `.kb` rule. That is a signed-off product running this on
   seven live pages, so the technique and the numbers are both already QC'd:
   a real <img>, object-fit:cover, scale(1.02) -> scale(1.12), transition 6s linear,
   fired ONCE by a class rather than looping.

   >> IT MUST BE AN <img>. Doing the same scale on a background-image made the photograph
   VIBRATE in place - Chrome re-rasterises that layer every frame, and a picture full of
   condensation beads shows every re-raster. `translateZ(0)` did not fix it. The brochure's
   own CSS carries the matching note that a per-element zoom on a <video> janks too.

   >> AND THE PROCESS LESSON, WHICH COST A ROUND TRIP: a Ken Burns push was written from
   scratch here (1.13, then 1.07, on a technique nothing else in the company uses) while two
   of our own builds already said 1.12. The brochure is the web SST for this effect. Monte
   Vista's hero prototype independently lands on 1.12 as well, but needs GSAP - do not copy
   that one. Check for an existing implementation BEFORE writing an effect.

   The subject is moved right by padding the SOURCE IMAGE on the left, not by object-position
   - `cover` leaves too little overhang to shift meaningfully. See `tools/hero-extend-canvas.py`. */
/* >> `<picture>` IS INLINE BY DEFAULT, so the img's `height:100%` has nothing to resolve
   against and the photo collapses. The wrapper has to be a block at full size. */
.hero-media picture{display:block;width:100%;height:100%}
.hero-media img{
  /* 64% not centre: puts the frame's right edge on the bottle's edge once the Ken Burns
     push settles. Owner's pick, wd-s24. Mobile overrides this below. */
  width:100%;height:100%;object-fit:cover;object-position:64% center;display:block;
  transform:scale(1.02);
  /* >> THE ONE ADDITION TO THE BROCHURE RECIPE, AND IT IS A SMOOTHNESS FIX, NOT A VALUE CHANGE.
     Without layer promotion Chrome repaints this image as it scales instead of compositing it,
     which reads as slight pauses between pushes - a 6s linear ramp moves sub-pixel per frame,
     so every skipped composite is visible. The brochure does not need this because its images
     are a fraction of the size and only animate for a moment. `will-change` is safe on exactly
     one element; do not sprinkle it on others - a promoted layer costs memory. */
  will-change:transform;backface-visibility:hidden;-webkit-backface-visibility:hidden;
}
@media (prefers-reduced-motion: no-preference){
  /* >> LINEAR IS LOCKED (owner, wd-s15). `ease-out` was built and compared side by side and
     REJECTED - it front-loads the movement, which renders marginally smoother but reads as a
     different move: a lunge that settles, rather than a steady drift. On a hero the drift is
     the point. This also keeps the value identical to the brochure's, so the two cannot drift
     apart. Do not re-try easing here; it has been looked at. */
  .hero-media img{transition:transform 6s linear}
  .hero-media.kb-go img{transform:scale(1.12)}
}

/* >> TWO SCRIM LAYERS, AND THE RADIAL IS THE ONE THAT MATTERS. A single left-to-right ramp
   darkens by POSITION, so pushing it hard enough to carry the accent-red kicker also drags
   the whole left third of the picture toward mud. The radial is anchored on the COPY BLOCK
   instead, so the darkening lands exactly where type sits and the top right of the frame
   stays bright amber. Same approach the studio site's hero landed on in wd-s3.
   This is what lets the kicker run at full #DC2E14 rather than a lifted salmon. */
.hero::after{
  content:"";position:absolute;inset:0;
  background:
    radial-gradient(115% 88% at 6% 74%, rgba(10,6,2,.88) 0%, rgba(10,6,2,.58) 40%, rgba(10,6,2,0) 76%),
    linear-gradient(90deg, rgba(14,8,3,.72) 0%, rgba(14,8,3,.44) 50%, rgba(14,8,3,.06) 100%);
}
/* >> `width:100%` IS A BUG FIX, NOT A NUDGE (owner asked for the block "up and to the left").
   `.hero` is a FLEX container, so `.wrap` became a flex ITEM and shrank to fit its own
   content - then its `margin:0 auto` centred that narrow box instead of the 1040px column.
   The result: the hero copy started ~180px right of "SIGNATURE" in the section below it, on
   a page where every other block shares one left edge. Giving it full width restores the
   normal `max-width` + auto-margin behaviour and the two line up exactly.
   The extra bottom padding is the "up" half of the same ask. */
.hero-inner{position:relative;z-index:2;width:100%;padding-bottom:clamp(4rem,8vw,6.5rem)}
/* >> PADDING-BOTTOM ONLY, NEVER THE SHORTHAND. `padding:0 0 <x>` here zeroed the LEFT and
   RIGHT padding `.wrap` supplies, so the hero copy sat 40px left of every other section
   on the page - a misalignment that looks like a nudge and is actually a wiped value. */
/* >> THE HERO KICKER USES THE BRAND ACCENT, NOT A LIGHTER TINT OF IT (owner, wd-s14).
   It was #FF8A5C, a salmon lifted off A2 for legibility on the dark hero. The owner wants
   the one colour - the logo, the buttons and this line all reading as the same red-orange.
   The drop shadow underneath is what buys the legibility instead. */
.hero .kicker{color:var(--a);text-shadow:0 2px 10px rgba(0,0,0,.72),0 1px 2px rgba(0,0,0,.6)}
.hero h1{
  font-family:var(--font-display);font-weight:400;color:#fff;
  font-size:clamp(2.7rem,7.4vw,5.4rem);line-height:.92;letter-spacing:-.02em;
  text-transform:uppercase;max-width:16ch;
  text-shadow:0 3px 18px rgba(0,0,0,.6);
}
.hero p{
  margin-top:1rem;max-width:46ch;color:#F6EEE4;font-size:1.06rem;line-height:1.6;
  text-shadow:0 1px 8px rgba(0,0,0,.75);
}
/* >> THE HERO REVEAL IS PORTED FROM `the7staragency.com` (owner, wd-s14) - the same
   effect its hero tagline uses. Source: `7Star-RealEstate-VideoMarketing/html-live-prod/
   homepage-index.html`, `.hero-h1 .line` / `.char` plus the timed sequence at the bottom
   of that file. Values copied, not re-derived: 30px of rise, .6s ease, 28ms per character.

   >> THE MASK IS THE HALF THAT MATTERS. Each LINE is `overflow:hidden`, so the characters
   rise from behind its own bottom edge rather than fading in mid-air. Take the mask off and
   the whole thing reads as a plain fade - which is the version everyone builds by accident.

   The padding/negative-margin pair on `.line` protects descenders and the text shadow from
   being clipped by that same mask. */
#headline .line{display:block;overflow:hidden;padding-bottom:.1em;margin-bottom:-.1em}
#headline .char{
  display:inline-block;opacity:0;transform:translateY(30px);
  transition:opacity .6s ease, transform .6s ease;
}
#headline .char.visible{opacity:1;transform:none}

/* the four blocks around the headline use the same rise, one delay each, set in main.js */
.reveal{opacity:0;transform:translateY(18px);transition:opacity .6s ease, transform .6s ease}
.reveal.visible{opacity:1;transform:none}

/* >> REDUCED MOTION SHOWS EVERYTHING AT ONCE, IT DOES NOT SHOW NOTHING. A visitor who has
   asked for less motion must still get the headline - the script sets `.visible` on
   everything immediately in that case, and these rules make that instant. */
@media (prefers-reduced-motion: reduce){
  #headline .char, .reveal{transition:none}
}

/* The fry-to-order line - a quality claim, sitting on the hero where it is unmissable.
   >> LIFTED PER THE 7 STAR PANEL STANDARD: the INSET PAIR is what makes it an object with
   thickness - a lit line on the top edge, a shaded line on the bottom. The two outer
   shadows only say how far it floats. It was a flat translucent lozenge before. */
.hero-note{
  display:inline-flex;align-items:center;gap:.42rem;margin-top:1.3rem;
  background:linear-gradient(180deg, rgba(38,22,10,.60), rgba(10,6,2,.74));
  border:1px solid rgba(255,238,218,.44);
  border-radius:999px;padding:.55rem 1.15rem;
  font-size:.85rem;font-weight:600;color:#FFEBDC;letter-spacing:.01em;
  backdrop-filter:blur(10px) saturate(130%);-webkit-backdrop-filter:blur(10px) saturate(130%);
  box-shadow:
    0 14px 30px rgba(0,0,0,.48),
    0 4px 8px rgba(0,0,0,.38),
    inset 0 1px 0 rgba(255,255,255,.40),
    inset 0 -3px 0 rgba(0,0,0,.40);
  text-shadow:0 1px 2px rgba(0,0,0,.55);
}
.hero-note b{color:#fff;font-weight:800}

/* ============================================================
   4. SIGNATURE DISHES  -  VERTICAL ROLODEX
   Donor: the studio site's stats rolodex (`OPTION 2 : ROLODEX`, wd-s6) plus its depth
   handler. Sticky does the stacking; the script writes scale and blur, because
   `animation-timeline` is Chrome-only and this project is checked in four browsers.

   >> RE-SKINNED, NOT COPIED. The donor cards are dark glass on near-black. This page is
   white ground, so the depth comes from shadow and edge instead of a translucent fill.
   ============================================================ */
.dish-list{list-style:none;display:block;margin:0;padding:0}
.dish{
  position:sticky;top:2.2rem;margin-bottom:2rem;
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);
  border-radius:16px;overflow:hidden;background:#fff;
  border:1px solid var(--line);
  box-shadow:
    0 -14px 30px rgba(60,34,10,.10),   /* shades the card this one is covering */
    0 22px 46px rgba(60,34,10,.20),    /* lifts this one off the page */
    inset 0 1px 0 rgba(255,255,255,.9),
    inset 0 -2px 0 rgba(90,56,20,.10);
  /* the handler writes these two - declared so the first paint is not unstyled */
  transform:scale(1);filter:blur(0px);
  transform-origin:50% 0;
  will-change:transform,filter;
  z-index:1;
}
.dish-media{
  position:relative;overflow:hidden;
  min-height:300px;
  /* stand-in - one gradient per dish, replaced by a real photograph */
  background:linear-gradient(150deg,#e0a044,#a3591f 58%,#5a3013);
}
/* A dish that has its photograph carries a real <img>. Absolute rather than
   height:100% because a percentage height inside a stretched grid item is
   not reliable; the gradient stays underneath as the fallback. */
.dish-media img{
  position:absolute;inset:0;
  width:100%;height:100%;object-fit:cover;object-position:center;
  display:block;
}
.dish:nth-child(2) .dish-media{background:linear-gradient(150deg,#e9a949,#bb4527 62%,#6d2415)}
.dish:nth-child(3) .dish-media{background:linear-gradient(150deg,#f0c268,#95591f 60%,#4b2913)}
.dish:nth-child(4) .dish-media{background:linear-gradient(150deg,#d99a3c,#8f4a1c 58%,#43220f)}
.dish:nth-child(5) .dish-media{background:linear-gradient(150deg,#f2cf86,#c07a2a 60%,#5c3314)}
.dish-body{padding:clamp(1.6rem,3.4vw,2.6rem);display:flex;flex-direction:column;justify-content:center}
.dish-body .num{
  font-family:var(--font-display);font-size:.78rem;letter-spacing:.14em;color:var(--a);
  text-transform:uppercase;margin-bottom:.5rem;
}
.dish-body h3{
  font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(1.7rem,3.4vw,2.5rem);line-height:1;letter-spacing:-.01em;
}
.dish-body p{margin-top:.8rem;color:var(--ink-soft);line-height:1.62;max-width:40ch}
.dish-meta{display:flex;align-items:baseline;gap:.8rem;margin-top:1.2rem;flex-wrap:wrap}
.dish-price{font-family:var(--font-display);font-size:1.7rem;color:var(--a)}
.dish-serves{font-size:.82rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-soft)}
/* the tail the last card needs to reach its pin - sized by the script, never guessed */
.dish-tail{list-style:none;height:0}

/* ============================================================
   5. MENU
   Cut-first, not flavour-first: pick the cut, then the sauce. Flavour-first would be
   five sauces across three cuts = fifteen rows before a single side.
   ============================================================ */

/* >> THE MENU SITS ON PAPER (wd-s22, owner's ask for section backgrounds).
   Warm kraft, tying to the takeout carton in the logo and sitting between the white page
   above it and the wood at the foot of the page.

   The image is already flattened and lifted - `tools/flatten-texture.py` divided out its
   lighting and blended it toward warm white until the page's own inks cleared AA on it.
   Nothing is washed here at scroll time. Read that script's header before regenerating,
   and NOT the rail's CSS wash: the rail's wash is directional because a plank is lit from
   one side, and the same values across a full-width section darken the right-hand third. */
/* >> IT TILES, IT DOES NOT STRETCH, AND THE SECTION'S HEIGHT IS WHY. The menu measures
   four screens on a laptop and five and a quarter on a phone. `cover` on a box that tall
   scales the image by its HEIGHT - about 3.3x - and every fleck becomes a blob. The file
   is a mirrored block built by the same script, so it repeats with no seam by
   construction, and 1920px is the size the texture was judged at. */
#menu{
  background:url("https://media.7starwebstudio.com/example/gch/images/menu-kraft-bg.jpg") 0 0/1920px auto repeat;

  /* >> THREE TOKENS ARE REDEFINED FOR PAPER, AT THE SECTION, WHICH IS WHY EVERY ROW INSIDE
     PICKS THEM UP FOR FREE. Scoped to `#menu`, so nothing anywhere else on the site moves.

     >> AND THE RULE THEY COME FROM, WHICH COST A ROUND TRIP TO LEARN (wd-s22): DARKEN THE
     INK FOR THE SURFACE, DO NOT BLEACH THE SURFACE FOR THE INK. The first build lifted the
     paper 50% toward white so the site's own grey secondary text would clear AA on it. It
     passed, and the owner's verdict was "really light and washed out on both laptop and
     phone" - correct, because the paper is the idea and the text is only ours. The paper is
     now near full strength and the two inks that could not cope were changed instead.

     Measured on this ground, #DDBF9B: body ink 10.68, secondary 5.63, kicker 4.61. */
  --line:rgba(90,56,20,.22);
  --line-strong:rgba(90,56,20,.38);

  /* the site's #5E5952 is a cool grey for a white page - 3.96 here, a fail. Warm and
     darker is the same role drawn for this surface */
  --ink-soft:#4A423A;
}

/* >> THE KICKER'S RED IS A THIRD RED AND IT LIVES ONLY HERE. Brand red is 4.73:1 on white,
   already only just over AA, and 2.70:1 on this paper. `--a-dark` was tried and is 3.68:1 -
   it only clears AA on the bleached ground the owner rejected. So the choice was a deeper
   red in one place or a washed-out section, and the section is worth more.
   NOT promoted to a token on purpose: a third `--a-*` in the palette invites its use
   elsewhere, and there is nowhere else on this site that needs it. */
#menu .kicker{color:#9E1A08}

/* >> THE SAUCE STRIP IS A LIVE SELECTOR, NOT A LEGEND. Picking a sauce rewrites the
   line under the strip and the sauce shown on every chicken row - it makes the
   cut-first structure visible instead of implied. No backend, no state to persist. */
.sauces{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem;position:relative}

/* >> MOLDED KEYS, NOT FLAT PILLS (owner, wd-s16). Each button is a solid key with a
   hard bottom edge and a soft shadow under it, and it presses DOWN onto that shadow
   when tapped. The house glass button is transparent by design, and transparency has
   nothing to do on a flat cream page - there is no picture behind it to blur, so it
   composites to the page color and goes dead. Depth on a light ground has to come
   from the edge and the shadow instead. Options were built at `_lab/sauce-options.html`;
   a frosted version over a tinted tray and one over the rail's wood were both shown. */
.sauce{
  font-family:var(--font-display);font-size:.88rem;letter-spacing:.03em;text-transform:uppercase;
  color:var(--ink);border-radius:999px;padding:.5rem 1.05rem;cursor:pointer;
  /* >> RETONED FOR THE PAPER, wd-s22. The key is unchanged as an OBJECT - same shape,
     same edge, same press. Only its colour moved: the fill was white-to-cream and the
     edge a cool grey-cream, both of which existed to hold the button apart from a white
     page. On kraft they have nothing to hold apart from and read as off-white blobs
     sitting on top of the menu. These are the paper's own tones, one shade lighter than
     the ground, so the keys read as cut from the same sheet. Owner picked this over an
     outline-only version and a dark stamped-tag version; `_lab/sauce-options.html` holds
     the original wd-s16 round. */
  background:linear-gradient(#F3E2CB,#E8D2B4);
  border:1px solid #C9A87F;
  box-shadow:0 3px 0 #C09A6E,                 /* the key's own edge */
             0 7px 14px rgba(90,56,20,.22),   /* what it casts on the page */
             inset 0 1px 0 rgba(255,255,255,.55);   /* lit top face */
  transition:transform .12s, box-shadow .12s, color .14s;
  position:relative;z-index:1;
}
@media (hover:hover) and (pointer:fine){
  .sauce:hover{transform:translateY(-1px);box-shadow:0 4px 0 #C09A6E,0 9px 16px rgba(90,56,20,.24),inset 0 1px 0 rgba(255,255,255,.55)}
}
/* the press moves the key down onto its own shadow - a finger covers what it touches,
   so a dip reads as contact where a lift reads as the thing running away */
.sauce:active{transform:translateY(3px);box-shadow:0 0 0 #C09A6E,0 2px 6px rgba(90,56,20,.22),inset 0 1px 0 rgba(255,255,255,.55)}

/* >> THE SELECTED BUTTON PAINTS NOTHING. The red fill is a single marker element that
   SLIDES from the old button to the new one, so the strip reads as one control rather
   than five independent switches. The button only changes its text color. */
.sauce[aria-pressed="true"]{color:#fff;border-color:transparent;background:transparent;box-shadow:none}

.sauce-marker{
  position:absolute;left:0;top:0;
  border-radius:999px;background:var(--a);
  box-shadow:0 3px 0 #8E1C06,
             0 8px 16px rgba(120,20,4,.34),
             inset 0 2px 0 rgba(255,255,255,.40),
             inset 0 -2px 0 rgba(0,0,0,.22);
  transition:transform .34s cubic-bezier(.22,.61,.36,1),
             width .34s cubic-bezier(.22,.61,.36,1),
             top .34s cubic-bezier(.22,.61,.36,1);
  pointer-events:none;z-index:0;
}
/* the strip wraps onto a second line on a phone, so the marker animates its top as
   well as its left - without that it slides diagonally across the gap between rows */

/* the sauce's own color, carried on every button so the row reads as five different
   sauces rather than five identical pills */
.sauce .chip{
  width:11px;height:11px;border-radius:50%;
  display:inline-block;vertical-align:middle;
  margin-right:.5rem;margin-top:-2px;
  border:1px solid rgba(0,0,0,.18);
}
/* a white ring once selected - the dark brown chip goes muddy against the red */
.sauce[aria-pressed="true"] .chip{border-color:rgba(255,255,255,.85)}
.chip-soy{background:#6B3F1D}
.chip-yang{background:#B8341E}
.chip-hot{background:#E8461C}
.chip-honey{background:#E0A73A}
.chip-snow{background:#F2E7D6}
.sauce-note{
  display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;
  font-size:.95rem;color:var(--ink-soft);line-height:1.55;margin-bottom:2rem;
  /* >> THE PANEL BEHIND THIS LINE IS GONE, wd-s22. It was a near-white card whose only
     job was to hold the description apart from a white page. On paper it read as a second
     off-white surface for no gain - the red rule already marks the line, and the paper is
     a better ground for it than a card is. One less surface. */
  padding:.75rem 0 .75rem .95rem;border-left:3px solid var(--a);
}
.sauce-note b{color:var(--ink)}
/* the .heat dot scale was removed wd-s16 (owner) - the sauce lines say how hot each
   one is in words, so the dots were a second way of saying the same thing */

.menu-group{margin-top:2.6rem}
.menu-group:first-of-type{margin-top:0}
.menu-group > h3{
  font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:1.35rem;letter-spacing:.04em;color:var(--ink);
  padding-bottom:.5rem;border-bottom:2px solid var(--ink);margin-bottom:.3rem;
}
.menu-group > .note{font-size:.86rem;color:var(--ink-soft);margin:.55rem 0 .2rem;font-style:italic}

.rows{list-style:none;display:flex;flex-direction:column}
/* >> THE DOTTED LEADER IS FROM THE LOCKED LAB FILE AND IS NOT DECORATION.
   Without it the price sits at the far right of a 1040px column and the eye has to
   travel the whole way with nothing to follow - which is exactly the problem printed
   menus solved with leader dots a century ago. Built as a three-column grid so the
   dots take whatever space is left over at any name or price length. */
.row{display:grid;grid-template-columns:auto minmax(1.5rem,1fr) auto;gap:.35rem .6rem;
  align-items:baseline;padding:.85rem 0;border-bottom:1px solid var(--line)}
.row:last-child{border-bottom:0}
.row .nm{grid-column:1;font-size:1.02rem;font-weight:700;text-transform:uppercase;letter-spacing:.01em}
.row .dots{grid-column:2;border-bottom:1px dotted var(--line-strong);transform:translateY(-.26em)}
.row .pr{grid-column:3;font-family:var(--font-display);font-size:1.15rem;color:var(--ink);white-space:nowrap}
.row .pr.free{color:var(--a);font-size:.95rem;letter-spacing:.04em;white-space:normal;text-align:right}
/* >> EVERY CHICKEN ROW CARRIES A PIECE COUNT AND A FEEDS-HOW-MANY. This is the answer to
   the portion-versus-price complaint that runs through every review set read in wd-s14,
   and most real menus fail it. It is not optional on this page. */
.row .sub{grid-column:1 / -1;font-size:.86rem;color:var(--ink-soft);line-height:1.5}
.row .sub .cut{color:var(--ink);font-weight:700}
.row .sub .with-sauce{color:var(--a);font-weight:700}
.tags{display:inline-flex;gap:.3rem;margin-left:.5rem;vertical-align:middle}
.tag{
  font-size:.58rem;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
  color:#fff;background:var(--ink);padding:.16rem .38rem;border-radius:3px;white-space:nowrap;
}
.tag.v{background:#3F6B2E}
.tag.n{background:#8A5A16}
.menu-legend{margin-top:2.4rem;font-size:.82rem;color:var(--ink-soft);line-height:1.7}

/* ============================================================
   6. BEER RAIL  -  OPTION (a), THE PINNED DECK
   Donor: the studio site's examples deck (`.work-track` / `.work-rail` / `.work-item`).

   >> THE FIVE NUMBERS ARE TUNED TIGHTER THAN THE DONOR (owner, wd-s14: "cards would want
   to be closer together"). The donor is spaced for one browser frame at a time; a beer
   list has to read as a LINEUP, so neighbours stay legible at the edges of frame.
   Donor:  SPREAD .66  SHRINK .15  BLUR 2.2  FADE .22  DEPTH 2
   Here:   SPREAD .52  SHRINK .08  BLUR 1.0  FADE .12  DEPTH 3     (set in main.js)
   ============================================================ */
/* >> THE BEER DECK SITS ON A ZINC COUNTER (wd-s22). Bottles on a counter reads correctly,
   and it is the one section where a harder material beats paper. Built by the same script
   as the menu paper - see `DESIGN.md`, SECTION BACKGROUNDS.

   >> THE ONE THING THAT IS DIFFERENT HERE, AND IT DECIDED THE TONE: MID GREY IS THE WORST
   POSSIBLE GROUND FOR TEXT. Raw zinc came out at #858585, where near-black scores 5.08 and
   white scores 3.0 - nothing reads well on it in either direction. The counter is therefore
   lifted to #ABAAA7 and the section's secondary ink darkened to meet it, the same move the
   menu paper needed. Going the other way - a dark counter with cream type - was the
   alternative and it is a bigger change than it looks: every card in this deck is white
   with a light border and a warm shadow, so all of that would be rebuilt too. */
.beer{
  padding:0;
  /* a flat stand-in in the counter's own mid tone, so nothing white can flash through
     while the sticky stage below is coming into or leaving the viewport */
  background:#9E9E9C;

  /* the site's hairline is a near-white built for a white page - on grey it reads LIGHTER
     than its own ground, exactly as it did on the kraft */
  --line:rgba(30,28,26,.22);
  border-top:1px solid var(--line);

  /* #5E5952 measures 2.94 on this ground, a clear fail. Same role, drawn for metal */
  --ink-soft:#2A2722;
}

/* >> THE COUNTER IS PAINTED ON THE STICKY STAGE, NOT ON THE SECTION, AND THAT IS THE WHOLE
   FIX FOR TWO SEPARATE FAULTS (wd-s22, both owner-reported).

   1. IT WAS SCROLLING. On `.beer` the background belongs to the tall scroll track, so it
      travelled up the screen while the deck slid sideways - a counter drifting under
      stationary bottles. `.beer-stage` is the element that is actually pinned, so painting
      it there makes the counter hold still, which is what a counter does.

   2. IT WAS TILED, AND TILING IS WHAT MADE THE STRANGE SHAPES. The tile is built by
      mirroring, and the frame's dark patina blotches were being reflected into symmetric
      butterfly forms - the thing the owner circled. **A mirrored tile is only invisible on
      a texture whose features are too small to recognise**; fine paper fibre qualifies and
      a blotchy metal plate does not. The stage is exactly one screen tall, so it needs no
      tile at all: one photograph, `cover`, no repeat, no mirror, no seam. */
.beer-stage{
  background:url("https://media.7starwebstudio.com/example/gch/images/beer-counter-bg.jpg") center center/cover no-repeat;
}
.beer-track{position:relative}

/* >> WHERE THE JUMP STOPS, AND WHY THE CARD IS SIZED IN svh (wd-s16, owner).
   Clicking BEER has to land the section's top seam exactly on the top of the browser
   frame, laptop and phone alike, and the whole deck has to fit in that one frame with
   no dead band under it. Two parts:

   1. THE PIN. `[id]` carries no scroll-margin on desktop (the rail is a LEFT bar, so
      there is nothing at the top to clear) and `#beer` overrides the phone's 68px in
      the media query below. Clearance for the phone's top bar belongs to the SECTION -
      `--bar` of padding inside the sticky stage - not to the scroll. This is the
      studio site's rule (`style.css` line 44 there): scroll-margin only MOVES the
      problem, because it stops the seam that far down and fills the strip above it
      with the tail of the previous section. Padding removes it.

   2. THE FILL. The card used to be a fixed 240px wide with a 250px bottle, so on a
      laptop the deck used about two thirds of the frame and left a white band top and
      bottom. It is now derived from the stage's own height, the way the donor derives
      `--cardw`, so the deck grows into whatever frame it is given and the head group
      rides up to the top of it. `21rem` is the fixed furniture the card has to share
      the frame with, measured: head 145 + counter 35 + the card's own text block 155.
      >> NEVER SIZE THIS OFF vw. A phone is 375px wide and any vw cap collapses the
      card to a stamp; height is the axis that is actually scarce here. */
.beer-stage{
  --bar:0px;
  --stageh:calc(100svh - var(--bar));
  --cardw:clamp(190px, calc((var(--stageh) - 21rem) * .78), 24rem);
  position:sticky;top:0;height:100svh;padding-top:var(--bar);
  display:flex;flex-direction:column;justify-content:center;overflow:hidden;
}
.beer-head{text-align:center;padding:0 1.2rem;margin-bottom:1.6rem}
.beer-head h2{font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(2rem,4.6vw,3.2rem);line-height:.98}
.beer-head p{margin-top:.6rem;color:var(--ink-soft);font-size:.98rem}
/* the card's picture plus its text block. 10.6rem is the text block MEASURED at its
   tallest - a two-line `.beer-say`, which most of the twelve have. At 9.8rem the card
   ran 13px past the rail and crowded the counter under it. */
.beer-rail{position:relative;height:calc(var(--cardw) * 1.05 + 10.6rem)}
.beer-item{
  position:absolute;top:0;left:50%;width:var(--cardw);
  transform:translate(-50%,0);will-change:transform,filter,opacity;
}
/* >> THE CARD KEEPS ITS OWN LIGHT HAIRLINE, WRITTEN OUT RATHER THAN TAKEN FROM `--line`
   (wd-s22). `--line` is redefined on `.beer` for the counter, which is a dark hairline for
   a grey ground - correct there, and wrong on a white card, where it turned a whisper of an
   edge into a drawn outline. The token was scoped to the section; this is the one child
   that did not want it. */
/* >> THE CARD IS PAPER ON A METAL COUNTER (wd-s22). It was white, which was the only
   surface on that screen not part of the story the page tells. This is the MENU'S OWN
   TILE - same file, same scale - so the two papers cannot drift apart and nothing new had
   to be generated or decided.

   The inks travel with the material. `--ink-soft` goes back to the menu's paper ink rather
   than the dark one `.beer` sets for metal, and the style line takes the menu's deeper red,
   because brand red measures 2.70:1 on this ground - the same failure and the same fix as
   the menu kicker. Same material, same inks, every time. */
.beer-card{
  background:url("https://media.7starwebstudio.com/example/gch/images/menu-kraft-bg.jpg") 0 0/1920px auto repeat;
  --ink-soft:#4A423A;
  /* >> NO BORDER, AND NO LIT TOP EDGE, BECAUSE THE TOP OF THIS CARD IS A DARK
     PHOTOGRAPH (owner spotted the sliver, wd-s22). Two separate light lines were
     running around the picture: the 1px border, and an `inset 0 1px 0
     rgba(255,255,255,.9)` highlight. Both are correct on a card whose own surface
     reaches its top edge - and this card's top edge is a near-black bottle shot, so
     each one read as a rim drawn around the photo. It was there when the card was
     white too; the warm colour is simply what made it visible.
     The card is defined by its shadow instead: a wide one that lifts it off the
     counter, and a tight one that is its contact with it. The dark inset bottom edge
     stays - it falls on the paper, where it belongs. */
  border:0;border-radius:14px;overflow:hidden;
  box-shadow:0 18px 38px rgba(60,34,10,.20),
             0 3px 8px rgba(60,34,10,.30),
             inset 0 -2px 0 rgba(90,56,20,.12);
  text-align:center;padding-bottom:1rem;
}
/* >> THE BOTTLE PHOTOGRAPH FILLS THE TOP OF THE CARD. The CSS stand-in bottle it
   replaced is gone (wd-s16). `object-position` sits above centre because the source
   frame is landscape and the bottle stands slightly high in it - a straight centre
   crop cuts the cap. */
/* >> THE PHOTOGRAPH CASTS A SHADOW ONTO THE CARD BELOW IT (wd-s22). Before this the two
   halves were simply stacked, so the picture read as printed flat onto the card. A soft
   line of shade along the join says the photograph sits IN the card, and the card becomes
   an object rather than two rectangles that happen to touch.
   `-6px` on the spread keeps it a line of shade rather than a haze - it fades out about
   six pixels down, roughly the depth an edge would actually throw. `position:relative` and
   the z-index are what let it paint over the white half; the card's `overflow:hidden`
   keeps it inside the rounded corners. */
.bottle-wrap{
  height:calc(var(--cardw) * 1.05);overflow:hidden;background:#140D07;
  position:relative;z-index:1;
  box-shadow:0 7px 12px -6px rgba(60,34,10,.55);
}
.bottle-wrap img{
  width:100%;height:100%;object-fit:cover;object-position:center 42%;display:block;
}
.beer-card h3{font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:1.24rem;margin-top:.9rem;letter-spacing:.01em}
.beer-style{font-size:.8rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
  /* the paper red, same value the menu kicker uses - brand red is 2.70:1 on kraft */
  color:#9E1A08;margin-top:.28rem}
.beer-say{font-size:.85rem;color:var(--ink-soft);line-height:1.5;margin:.55rem auto 0;
  max-width:24ch;padding:0 .8rem}
.beer-foot{display:flex;justify-content:center;gap:.7rem;align-items:baseline;margin-top:.7rem}
.beer-abv{font-size:.78rem;font-weight:700;color:var(--ink-soft)}
.beer-price{font-family:var(--font-display);font-size:1.15rem;color:var(--ink)}
.beer-count{text-align:center;margin-top:1.4rem;font-size:.8rem;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink-soft)}

/* fallback: no JS, or reduced motion - the deck unwinds to a plain scrolling row */
.beer.is-static .beer-track{height:auto !important}
.beer.is-static .beer-stage{position:static;height:auto;padding:clamp(3rem,6vw,5rem) 0}
.beer.is-static .beer-rail{height:auto;display:flex;gap:1rem;overflow-x:auto;
  scroll-snap-type:x mandatory;padding:0 clamp(1.2rem,4vw,2.6rem) 1rem}
.beer.is-static .beer-item{position:static;transform:none !important;filter:none !important;
  opacity:1 !important;flex:0 0 auto;scroll-snap-align:center}

/* ============================================================
   7. OUR STORY
   ============================================================ */
.story-grid{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(1.4rem,3vw,2.6rem);align-items:start}
.story-copy p{color:var(--ink-soft);line-height:1.7;margin-top:1rem;max-width:52ch}
.story-copy p:first-of-type{margin-top:0}
.story-copy strong{color:var(--ink)}
.story-shots{display:grid;grid-template-columns:1fr 1fr;gap:.8rem}
.shot{position:relative;border-radius:12px;overflow:hidden;min-height:150px;
  border:1px solid var(--line);background:#2C1A0C;
  box-shadow:0 12px 26px rgba(60,34,10,.14),inset 0 1px 0 rgba(255,255,255,.8)}
.shot.tall{grid-column:span 2;min-height:210px}
.shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
/* >> THE WIDE ONE CROPS TO ABOUT 2.2:1 OUT OF A 16:9 SOURCE, so it loses the top and
   bottom of the frame. Anchored slightly high because the master's subject - the fryer
   line at the far end - sits above centre; a straight centre crop cuts the pendant lamps
   and leaves a band of empty floor. The two half-width shots are near 1.5:1 and centre
   fine. Same reasoning as `.bottle-wrap`'s `center 42%`. */
.shot.a img{object-position:center 42%}
/* the three gradient stand-ins that sat here are gone - real photographs since wd-s16.
   `.shot`'s flat background is what shows while a lazy image is still loading. */

/* ============================================================
   8. REVIEWS  -  slow marquee, non-pinning
   >> DELIBERATELY NOT A PINNED SECTION. The rolodex and the beer deck already hold the
   visitor twice; a third scroll-hold makes a site feel like it stops you every time you
   try to move. Same conclusion the studio site reached about its own third section.
   ============================================================ */
/* >> THE SECTION IS THE ROOM, BLURRED (owner's call, wd-s16). The cards were #fff on a
   #FFFFFF page - they existed only because of a 1px border. This puts the master
   interior behind the words people are saying about the place.

   THE THREE THINGS THAT MAKE IT WORK, none of them optional:
   1. BLUR 14px, NOT MORE. At 22px the room went to flat brown and could have been any
      photograph. At 14 the pendant lamps still read as lamps, so it is recognisably
      the room from the Story section one screen up - which is the entire point of
      reusing that file rather than generating another.
   2. THE SCRIM IS DARKER AT THE EDGES THAN IN THE MIDDLE. A flat scrim heavy enough to
      carry text at the top kills the lamps in the centre. The ramp protects the heading
      and the section seams and leaves the middle lit.
   3. THE CARDS ARE FROSTED, NOT SOLID, AND THIS IS THE ONE PLACE THAT IS HONEST. A
      frosted panel blurs what is behind it; on the flat white page there was nothing
      back there to blur, which is why the old card looked like nothing. Here there is
      a photograph. Same inset-pair recipe as the house frosted panel.
   >> IF THE STORY IMAGE IS EVER SWAPPED, THIS SECTION CHANGES WITH IT. One file, two
   jobs. That is deliberate - it is the same room - but it is a coupling, so check both. */
.reviews{overflow:hidden;position:relative;isolation:isolate}
/* >> THE BLUR IS BAKED INTO THE FILE, NOT RUN BY THE BROWSER. `filter: blur()` here is a
   live layer that gets re-composited every frame the marquee moves across it, and it was
   a large part of why the strip stuttered. `tools/bake-reviews-bg.py` produces
   `reviews-room-bg.jpg` from the story master. No filter, no scale, no per-frame cost.
   >> RE-RUN THAT SCRIPT IF THE STORY IMAGE CHANGES - this file will not update itself. */
.reviews::before{
  content:"";position:absolute;inset:0;z-index:-2;
  background:url("https://media.7starwebstudio.com/example/gch/images/w32-koreaway-sign.jpg") center 50%/cover no-repeat;
}
.reviews::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,
    rgba(16,10,5,.70) 0%, rgba(16,10,5,.44) 30%,
    rgba(16,10,5,.44) 70%, rgba(16,10,5,.72) 100%);
}
.reviews .kicker{color:#D2C5B3}
.reviews .sec-head h2{color:#FBF7F1;text-shadow:0 2px 14px rgba(0,0,0,.55)}
/* >> THE SPACING IS A MARGIN ON THE CARD, NOT `gap` ON THE TRACK, AND THAT IS A BUG FIX.
   `translateX(-50%)` is half the track's OWN width. With `gap`, twenty cards have only
   nineteen gaps, so half the width lands half a gap short of the duplicate's first card
   and the loop jumps 8px every cycle. A trailing margin on every card gives twenty cards
   twenty gaps, and -50% then lands exactly on the seam. */
/* >> NO `will-change` AND NO `translateZ(0)` ON THIS ELEMENT, AND IT IS NOT AN OVERSIGHT.
   The track is about 7,900px wide. Promoting it to its own compositor layer asks the GPU
   for a texture roughly 15,800px across on a 2x display, which is past the limit - the
   layer fails and THE WHOLE STRIP PAINTS AS NOTHING. That is what the "large gap" was:
   not a hole in the loop, the cards simply never drew. Leave it unpromoted. */
.marquee{display:flex;width:max-content}
.marquee .quote{margin-right:1rem}
@media (prefers-reduced-motion: no-preference){
  .marquee{animation:slide 80s linear infinite}

  /* >> THE PAUSE TRIGGERS ON A CARD, NOT ON THE SECTION (owner, wd-s16). It was
     `.reviews:hover`, which is the whole band edge to edge INCLUDING the heading and
     the section's padding - so the marquee froze while the cursor was nowhere near a
     review, and it read as the animation having finished rather than as a pause.
     `:has(.quote:hover)` is the card itself, top edge to bottom edge. The gaps between
     cards no longer trigger it either. */
  .marquee:has(.quote:hover),.marquee:focus-within{animation-play-state:paused}
  /* older browsers with no :has() fall back to the whole strip - still far tighter
     than the section, and no worse than what this replaced */
  @supports not selector(:has(*)){
    .marquee:hover{animation-play-state:paused}
  }
}
/* the track holds the list twice, so -50% lands exactly on the duplicate's first card */
@keyframes slide{ from{transform:translateX(0)} to{transform:translateX(-50%)} }

/* >> WHY TEN REVIEWS AND NOT FIVE, AND IT IS NOT ONLY ABOUT HAVING MORE TO READ.
   The loop is seamless only while HALF the track is wider than the section - the wrap
   point has to happen off screen. With five cards at 380px the half-track was 1972px,
   so any viewport wider than about 2200 (a 2560 monitor, a wide external display) put
   the seam ON SCREEN and opened a real blank band once per cycle. Doubling the reviews
   doubles the half-track to about 3944 and pushes that limit past 4100px.
   >> SO THE RULE: HALF THE TRACK MUST STAY WIDER THAN THE WIDEST SCREEN THIS IS SEEN
   ON. Removing reviews shrinks it. If the list ever drops back to five, the gap comes
   back, and it comes back only on big monitors - which is exactly the kind of bug that
   does not show up on the machine it was built on. Speed is held near the old 43px/sec
   by taking the duration up with the width; 80s is a touch quicker at about 49px/sec. */
.quote{
  flex:0 0 clamp(280px,30vw,380px);border-radius:14px;padding:1.5rem 1.6rem;
  /* >> NO backdrop-filter HERE, AND THAT IS A PERFORMANCE RULE, NOT A LOOK CHOICE.
     A frosted card has to re-sample the photograph behind it every single frame, and
     there are twenty of them moving at once - it made the marquee visibly skip. This is
     a flat tinted fill over the same photo: same warm glass read, no per-frame cost.
     Do not put backdrop-filter back on a MOVING element. The frosted read comes from a
     white film laid over a warm dark tint instead. */
  background:linear-gradient(rgba(255,255,255,.19),rgba(255,255,255,.09)),
             rgba(30,19,10,.50);
  border:1px solid rgba(255,255,255,.30);
  box-shadow:0 18px 36px rgba(0,0,0,.46),
             0 3px 10px rgba(0,0,0,.34),
             inset 0 1px 0 rgba(255,255,255,.28),
             inset 0 -2px 0 rgba(0,0,0,.30);
}
/* >> THE STARS GET BRIGHTER AND WIDER ON DARK. Brand red #DC2E14 is a mid tone; against
   a near-black card it reads as a dim smudge, and it is the one element in the card that
   has to land at a glance. Lifted toward orange with a soft glow so it still reads as the
   site's red rather than going pink. Do not put --a back here. */
.quote .stars{color:#FF6A3D;letter-spacing:.22em;font-size:1.05rem;
  text-shadow:0 0 10px rgba(255,90,40,.45)}
.quote p{margin-top:.7rem;line-height:1.62;font-size:1rem;color:#F6F2EC}
.quote .who{margin-top:1rem;font-size:.8rem;font-weight:800;letter-spacing:.08em;
  text-transform:uppercase;color:#C0B5A8}

/* ============================================================
   9. VISIT  -  the room fills up
   >> THE FILLING ROOM IS THE ARGUMENT, NOT DECORATION. Closing early is a named
   complaint on this block and late hours are our stated differentiator, so the seats
   filling and the sign coming up to full burn is the page making that point visually.

   >> IT PLAYS ON ITS OWN. IT IS NOT TIED TO SCROLL (owner, wd-s16). A scroll-driven
   version only tells its story to someone who happens to scroll at the right speed
   through the right band of the page, and stalls half-told for anyone who stops. A
   loop plays the whole argument to everyone who looks at it.

   >> FOUR FRAMES OF THE SAME ROOM, SAME LIGHT, DIFFERENT CROWD. Holding the light
   constant is deliberate: it keeps every frame one Kontext edit away from the master
   with a single concept in the prompt, which is the only reliable way to hold the
   walls, tables and camera identical across a set. THE SIGN CARRIES THE TIME OF DAY
   instead - it lifts from a dull daytime tint to full orange burn on the last frame.
   ============================================================ */
/* >> START-ALIGNED, NOT CENTRED. The details list is taller than the room, so centring
   floated the photograph down and left a void under the section heading that read as a
   missing block. */
.visit-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:clamp(1.4rem,3vw,2.6rem);align-items:start}
.front{position:relative;border-radius:14px;overflow:hidden;min-height:330px;
  background:#140D07;
  border:1px solid var(--line);
  box-shadow:0 16px 34px rgba(60,34,10,.18),inset 0 1px 0 rgba(255,255,255,.8)}
/* >> ONE FRAME IS LIT AT A TIME AND THE REST SIT AT ZERO. Every frame is the same room
   from the same camera, so a plain opacity crossfade reads as the crowd arriving rather
   than as two pictures dissolving - the furniture never moves.
   `--fade` is set by the script so the night-to-morning wrap can run slower than the
   steps inside the evening; it is the biggest jump in the loop. */
/* >> THE FADE DURATION IS A PLAIN VALUE HERE, NOT A CUSTOM PROPERTY. Safari does not
   reliably parse `transition: opacity var(--fade)`, and when the shorthand fails to parse
   it drops the WHOLE declaration - so the frames hard-cut instead of crossfading and the
   sign's states snap. The script sets `style.transitionDuration` directly for the slower
   wrap instead. */
.front-frame{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity 1300ms ease-in-out,filter 1300ms ease-in-out;
}
.front-frame.is-on{opacity:1}
/* >> THE ROOM DIMS AS THE SIGN COMES UP, AND THAT IS WHAT MAKES THE GLOW READ. A glow is
   relative - the same drop-shadow on a bright room looks like a smudge and on a dark room
   looks lit. The four photographs all carry the same exposure, so the falling light has to
   come from here. */
.front[data-frame="2"] .front-frame{filter:brightness(.86) saturate(1.10)}
.front[data-frame="3"] .front-frame{filter:brightness(.66) saturate(1.20)}
/* the sign is our real logo laid over the photograph, never generated - so our name is
   always correctly drawn and correctly coloured, at any size, in any frame */
.front-sign{
  position:absolute;left:50%;top:45%;transform:translate(-50%,-50%);z-index:2;
  width:46%;height:auto;pointer-events:none;
  opacity:.40;filter:saturate(.30) brightness(1.18);
  transition:opacity 1.3s ease-in-out,filter 1.3s ease-in-out;
}
.front[data-frame="1"] .front-sign{opacity:.62;filter:saturate(.72) brightness(1.06)}
.front[data-frame="2"] .front-sign{opacity:.86;
  filter:saturate(1) drop-shadow(0 0 20px rgba(255,90,40,.55))}
.front[data-frame="3"] .front-sign{opacity:1;
  filter:saturate(1.2) brightness(1.12)
         drop-shadow(0 0 16px rgba(255,150,90,.80))
         drop-shadow(0 0 38px rgba(255,110,40,.72))
         drop-shadow(0 0 84px rgba(255,80,20,.58))}
/* >> OPTION 2 - THE AERIAL IN A FULL-HEIGHT CARD (owner, wd-s16).
   The panel fills the section rather than stopping wherever the list happens to end, and
   the storefront on the left grows to match it - two equal columns, not a tall box beside
   a short one.

   >> THE SECTION GETS A WIDER WRAP THAN THE REST OF THE PAGE, and that is deliberate. At
   the standard 1040 the card came out around 412px, which is a sidebar, not a panel. 1180
   is enough to let the address lines sit on one line each.

   >> THE PHOTO IS SHARP AND NOW BRIGHTER. It was generated near-vertical with a slight
   fisheye so it reads as Manhattan from above; the first scrim was heavy enough to bury
   that. Brightness is lifted on the image and the scrim pulled back, so the avenue and the
   lit windows carry while the text still holds.

   >> NOTHING ELSE DEPENDS ON `visit-aerial-bg.jpg` - unlike the reviews band, which reuses
   the story master, this file can be swapped here alone. */
#visit .wrap{max-width:1180px}
/* >> VISIT'S CARDS SIT TIGHTER UNDER THE HEADING THAN THE OTHER SECTIONS' CONTENT DOES,
   and that is deliberate rather than a stray value. Every other section puts a list or a
   row of small items under its heading; this one puts two tall panels, and the standard gap
   read as a gulf between the sentence and the thing it introduces. Section spacing itself is
   unchanged - this only closes the space inside it. */
#visit .sec-head{margin-bottom:clamp(1.1rem,2.2vw,1.6rem)}
.visit-grid{grid-template-columns:1fr 1fr;align-items:stretch}
.front{min-height:100%}

.visit-copy{
  position:relative;isolation:isolate;
  border-radius:14px;overflow:hidden;
  display:flex;flex-direction:column;justify-content:center;
  padding:clamp(1.6rem,2.8vw,2.4rem);
  border:1px solid rgba(255,255,255,.18);
  box-shadow:0 16px 34px rgba(0,0,0,.42),
             0 3px 10px rgba(0,0,0,.30),
             inset 0 1px 0 rgba(255,255,255,.20),
             inset 0 -2px 0 rgba(0,0,0,.34);
}
.visit-copy::before{
  content:"";position:absolute;inset:0;z-index:-2;
  background:url("https://media.7starwebstudio.com/example/gch/images/visit-aerial-bg.jpg") center/cover no-repeat;
  /* safe to filter here - nothing animates across this panel, unlike the reviews band */
  filter:brightness(1.32) saturate(1.06);
}
.visit-copy::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,
    rgba(6,9,14,.58) 0%, rgba(6,9,14,.46) 45%, rgba(6,9,14,.56) 100%);
}
.visit-copy dl{margin-top:0}

/* >> THE LIST STEPS UP WITH THE PANEL. It was set for a 412px card; in a half-section
   panel the same small type just looks lost. Labels, body and row spacing move together. */
.visit-copy .visit-row{padding:1.15rem 0;border-bottom:1px solid rgba(255,255,255,.18)}
.visit-copy .visit-row:last-of-type{border-bottom:0}
/* brand red keeps its hue but takes the lifted tone - #DC2E14 goes muddy on near-black,
   the same reason the review stars had to move */
.visit-copy .visit-row dt{font-size:.74rem;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:#FF6A3D;margin-bottom:.35rem}
/* >> A SHADOW, NOT DARKER INK. The text stays white and the legibility comes from the
   shadow behind it - which is what lets the photograph underneath stay bright. Darkening
   the type would have meant darkening the picture to match. */
.visit-copy .visit-row dd{font-size:1.09rem;line-height:1.62;color:#FFFCF8;
  text-shadow:0 1px 3px rgba(0,0,0,.75),0 1px 10px rgba(0,0,0,.45)}
.visit-copy .visit-row dd a{color:#fff;text-decoration-color:rgba(255,255,255,.6)}
.visit-copy .btn-row{margin-top:1.6rem}
/* >> A LINK INSIDE BODY COPY IS UNDERLINED BY DEFAULT, NOT ON HOVER. "Get directions" set
   in bold black with no underline read as a heading, not as something to click - and hover
   is not a thing that exists on the phone most of these visitors are holding. */
.visit-row dd a{font-weight:700;text-decoration:underline;text-underline-offset:3px;
  text-decoration-thickness:1px;text-decoration-color:var(--line-strong)}
@media (hover:hover) and (pointer:fine){ .visit-copy .visit-row dd a:hover{text-decoration-color:#FF6A3D} }

/* ============================================================
   10. FOOTER
   ============================================================ */
/* >> THE FOOTER IS THE SAME BOARD AS THE RAIL, TURNED 90 DEGREES. `hp-foot-bg.jpg` is
   `hp-rail-bg.jpg` rotated - same plank, same grain, same knots - so the two panels read as
   one material rather than two woods that nearly match. The grain runs HORIZONTAL here,
   perpendicular to the rail's vertical, which is how two boards meet at a corner.

   >> EVERY SURFACE VALUE BELOW IS THE RAIL'S, MIRRORED FOR A HORIZONTAL PANEL, NOT
   RE-PICKED BY EYE. The rail is lit from the top with its shaded inner edge on the side
   facing the page and its drop shadow cast the same way; the footer sits below the page
   instead of beside it, so the shaded inner edge moves to the BOTTOM and the drop shadow
   casts UP. The lit top edge and the hairline are unchanged, because the light is still
   above. Change one panel and change the other - they must not drift apart. */
/* >> THE FOOTER SITS OUTSIDE `main`, SO IT HAS TO CARRY THE RAIL OFFSET ITSELF - THIS IS
   THE COLUMN-SPACING BUG AND THE SAFARI BUG, AND THEY ARE THE SAME ONE. `main` is pushed
   clear of the fixed rail with `margin-left:var(--rail-w)`; the footer is a sibling, so it
   spanned the FULL window and centred its content on the window rather than on the visible
   area beside the rail. The whole block therefore sat too far left, with dead space stranded
   on the right - which is what kept reading as wrong column spacing.
   >> AND IT GETS WORSE THE NARROWER THE WINDOW, which is why Safari looked broken while
   Chrome looked merely off: at a 1392px window a centred 1180px block starts at 106px, and
   the rail is 288px wide, so the entire first column was hidden behind it. Nothing about
   Safari's grid handling was involved.
   PADDING, not margin, so the wood still runs the full width behind the rail. */
.foot{
  position:relative;isolation:isolate;color:var(--ink);
  padding:clamp(2.6rem,5vw,3.6rem) 0;
  padding-left:var(--rail-w);
  background-image:url('https://media.7starwebstudio.com/example/gch/images/hp-foot-bg.jpg');
  background-size:cover;background-position:center;
  box-shadow:
    inset 0 -21px 30px -19px rgba(60,34,10,.62),
    inset 0 16px 23px -15px rgba(255,250,240,.62),
    0 -13px 32px rgba(0,0,0,.39),
    0 -4px 7px rgba(0,0,0,.26);
  border-top:1px solid rgba(255,246,232,.34);
}
/* wash A - the rail's warm film, verbatim, so type sits on the grain without flattening it */
.foot::before{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:
    linear-gradient(180deg, rgba(255,250,242,.30), rgba(255,248,236,.16)),
    linear-gradient(90deg, rgba(255,252,246,.16) 0%, rgba(120,74,28,.10) 100%);
}

/* >> FOUR REAL COLUMNS ON A GRID, NOT A FLEX ROW. As a flex row each column was only as
   wide as its own longest line, so they bunched to the left and left a dead band on the
   right - which is what read as "each column is too small". The tracks are uneven ON
   PURPOSE: the address and the hours carry full lines that must not wrap, the link list is
   one word per row, and the socials are four fixed circles. Equal quarters would wrap the
   hours and strand the last two.
   >> AND THE FLEX PROPERTIES ARE GONE, NOT OVERRIDDEN. Leaving `justify-content` and
   `flex-wrap` on a grid container is what threw Safari's spacing out while Chrome ignored
   them. */
.foot-grid{
  display:grid;
  grid-template-columns:repeat(4,1fr);
  gap:clamp(1.4rem,3vw,2.4rem);
  align-items:start;
}
/* >> `min-width:0` IS LOAD-BEARING, NOT TIDYING - IT IS THE SAFARI BUG. A grid item's
   default `min-width` is `auto`, which means a track REFUSES to shrink below its longest
   unbreakable line. "Mon to Thu, 11:30 AM - 11:00 PM" is one such line, so the four `1fr`
   tracks stopped being equal, the row grew past its container, and the page picked up
   horizontal overflow - which is what pushed the whole layout sideways under the rail in
   Safari while Chrome quietly absorbed it. Zeroing the minimum lets the tracks be the equal
   quarters they are declared to be. */
.foot-grid > *{min-width:0}
.foot .wrap{max-width:1180px}
.foot h4{font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:1rem;letter-spacing:.09em;color:var(--ink);
  padding-bottom:.55rem;margin-bottom:.75rem;border-bottom:1px solid rgba(90,56,20,.24)}
.foot p,.foot li{font-size:.95rem;line-height:1.85;color:#4A3826}
.foot a{text-decoration:none;color:inherit}
@media (hover:hover) and (pointer:fine){ .foot a:hover{text-decoration:underline;color:var(--a-dark)} }
.foot ul{list-style:none}
/* the brand name is the one red thing down here, same as the rail's active nav item */
.foot-grid > div:first-child h4{color:var(--a-dark)}
/* >> TYPE ON WOOD GETS THE RAIL'S CARVED SHADOW. A light line under each glyph and a dark
   one over it is what makes lettering read as cut into the board instead of printed on top
   of a photograph. Copied off `.rail nav a`. */
.foot h4,.foot p,.foot li,.foot-base{
  text-shadow:0 1px 0 rgba(255,252,244,.81), 0 -1px 0 rgba(70,42,16,.30);
}
.foot .socials a{background:linear-gradient(180deg,#FFFBF4,#E4D3B8);border-color:rgba(90,56,20,.26);
  box-shadow:0 6px 12px rgba(60,34,10,.28),inset 0 2px 0 rgba(255,255,255,.95),inset 0 -3px 0 rgba(90,56,20,.20)}
.foot .socials svg{fill:#3A2410}
/* >> `margin-top` 2.2 -> 3.4rem, wd-s18 (owner, "slightly lower"). The gap ABOVE the rule is
   what moves the whole group down - the rule and the line travel together because the rule is
   this element's own top border, not a separate thing to keep in step.
   >> IT IS DELIBERATELY NOT DONE BY TAKING PADDING OFF THE BOTTOM OF `.foot`. That would move
   the row down by moving the floor UP, which crowds the row against the end of the page and
   is the one thing the owner ruled out - it still has to sit clear of the bottom edge when
   the page is scrolled all the way down. Adding above makes the footer taller instead. */
.foot-base{margin-top:3.4rem;padding-top:1.2rem;border-top:1px solid rgba(90,56,20,.24);
  font-size:.8rem;color:#6B563E;
  display:flex;justify-content:space-between;align-items:baseline;gap:1rem;flex-wrap:wrap}
/* >> FOUR COLUMNS BECOME TWO ON A PHONE. A quarter of a 390px screen is under 90px, which
   breaks the street address onto a line per word. */
@media (max-width:700px){
  .foot-grid{grid-template-columns:1fr 1fr;gap:2rem 1.4rem}
  /* >> THE HEADINGS RESERVE TWO LINES SO THEIR RULES LINE UP ACROSS THE ROW. "Gangnam
     Chimaek House" wraps to two lines and "Hours" does not, so the two rules sat at
     different heights and the row read as broken. Reserving the height on every heading
     lands them on one line without touching the words.
     >> AND THE MIN-HEIGHT HAS TO INCLUDE THE PADDING. `box-sizing:border-box` is global
     here, so a bare `min-height:2.6em` was two lines of text MINUS the .55rem gap above the
     rule - the two-line brand heading overflowed its own box and crossed its rule, which is
     what the first attempt produced. Adding the padding into the figure fixes it, and
     bottom-aligning drops a one-word heading onto the second line so it sits level with the
     wrapped one beside it. */
  .foot h4{line-height:1.15;min-height:calc(2.3em + .55rem);
    display:flex;align-items:flex-end}
  .foot-base{flex-direction:column;gap:.45rem}
}

/* ============================================================
   11. MOBILE  -  the rail becomes a top bar with a hamburger
   The contact block moves into the panel and the footer, per the approved layout.
   ============================================================ */
@media (max-width:900px){
  :root{--rail-w:0px}
  .main{margin-left:0}
  /* >> 133px IS THE MEASURED HEIGHT OF THE TOP BAR. This was 68px, left over from an
     earlier bar, and it was stale by 65px - anything jumped to under this rule landed
     with its top edge behind the bar. Every nav target now opts out of it explicitly and
     carries its own clearance (see the pinning block lower down), so this is the fallback
     for any other id, and it should equal the bar.
     >> IF THE BAR'S HEIGHT CHANGES, THIS AND `--bar` BOTH MOVE. */
  [id]{scroll-margin-top:133px}

  .rail{
    position:fixed;top:0;left:0;right:0;bottom:auto;width:auto;height:auto;
    flex-direction:row;align-items:center;gap:.8rem;flex-wrap:wrap;
    padding:.6rem .9rem;background-position:center;
    box-shadow:inset 0 16px 23px -15px rgba(255,250,240,.62),0 10px 26px rgba(0,0,0,.34);
    /* >> NO LIGHT HAIRLINE ALONG THE BOTTOM ON A PHONE. On desktop the same line runs
       VERTICALLY between the wood and the dark page and reads as the rail's edge. Turned
       horizontal it lies full width between a bright bar and a dark photograph, and at phone
       pixel density it renders as a soft glowing line - which reads as a blurry band in the
       hero and is nothing to do with the hero image. The drop shadow below already separates
       the bar. Measured: wood 168, this line 181, photo 38. */
    border-right:0;border-bottom:0;
  }
  .rail .logo{width:118px;margin:0}
  .bar-tools{display:flex;align-items:center;gap:.55rem;margin-left:auto}
  .bar-call{
    display:grid;place-items:center;width:40px;height:40px;border-radius:8px;color:#fff;
    background:linear-gradient(180deg,var(--a-lift) 0%, var(--a) 55%, var(--a-dark) 100%);
    box-shadow:0 8px 16px rgba(120,20,4,.42),inset 0 2px 0 rgba(255,255,255,.42),inset 0 -3px 0 rgba(0,0,0,.28);
  }
  .bar-call svg{width:17px;height:17px;fill:#fff}
  .burger{
    width:40px;height:40px;border-radius:8px;border:1px solid rgba(90,56,20,.24);
    background:linear-gradient(180deg,#FFFBF4,#E9DAC2);display:grid;place-items:center;gap:4px;
    cursor:pointer;align-content:center;
    box-shadow:0 6px 12px rgba(60,34,10,.3),inset 0 2px 0 rgba(255,255,255,.95),inset 0 -3px 0 rgba(90,56,20,.18);
  }
  .burger span{display:block;width:17px;height:2px;background:#3A2410;border-radius:2px;
    transition:transform .2s, opacity .2s}
  .burger[aria-expanded="true"] span:nth-child(1){transform:translateY(6px) rotate(45deg)}
  .burger[aria-expanded="true"] span:nth-child(2){opacity:0}
  .burger[aria-expanded="true"] span:nth-child(3){transform:translateY(-6px) rotate(-45deg)}

  /* the panel - nav plus the contact block the rail can no longer show */
  .rail nav,.rail .cta,.rail .meta,.rail .socials{display:none}
  .rail.open nav,.rail.open .cta,.rail.open .meta,.rail.open .socials{display:flex}
  .rail.open nav{flex-direction:column;width:100%;order:3;gap:.55rem;padding-top:.4rem}
  .rail.open nav a{font-size:1.05rem}
  .rail.open .cta{width:100%;order:4}
  .rail.open .meta{display:block;width:100%;order:5}
  .rail.open .socials{width:100%;order:6;margin-top:.2rem;padding-top:.6rem}
  .rail.open{padding-bottom:1.1rem;max-height:100svh;overflow-y:auto}

  /* >> FULL SCREEN ON A PHONE, AND IT IS A MEASUREMENT NOT A TASTE CALL. At 76svh the slot
     was 375x562 and the copy block alone measured 539 of those 562 pixels - the text filled
     the hero, so the photograph had nowhere to be seen and every crop of it read as a
     blow-up. 100svh buys about 200px of clean picture above the copy. */
  .hero{min-height:100svh}
  .hero-inner{padding-top:5rem}

  /* >> THE PHONE HERO IS ANCHORED TO ITS TOP, NOT ITS CENTRE. The phone frame is taller in
     proportion than the slot, so `cover` trims top and bottom - and centred trimming cuts
     the hand and the carton off and leaves the middle of the bottles filling the screen.
     Anchoring to the top keeps the subject and takes the trim out of the padded table
     underneath it, which is exactly what that padding is there to give up. */
  .hero-media img{object-position:center top}

  /* >> AND THE SCRIM TURNS VERTICAL. The desktop scrim darkens the LEFT, because that is
     where the copy sits beside the subject. On a phone the copy runs full width UNDER the
     subject, so a left-side scrim protects nothing and the headline lands on lit bottles.
     This replaces it with a bottom-up ramp. Desktop is untouched - the rule lives inside
     the 900px query. */
  .hero::after{
    background:linear-gradient(180deg,
      rgba(10,6,2,.30) 0%,
      rgba(10,6,2,.24) 34%,
      rgba(10,6,2,.72) 62%,
      rgba(10,6,2,.90) 100%);
  }
  .dish{grid-template-columns:1fr}
  .dish-media{min-height:190px}
  .story-grid,.visit-grid{grid-template-columns:1fr}

  /* >> BEER PINS TO THE TOP OF THE PHONE FRAME. `#beer` opts out of the 68px
     scroll-margin above, so the jump stops on the section's own top seam, and the
     stage hands that 68px back as padding INSIDE itself - so the bar covers the
     section's own empty strip rather than the tail of the menu. `--bar` also shrinks
     `--stageh`, which is what keeps the card from being sized for 68px it cannot use.
     >> 133px IS MEASURED, NOT THE 68px THAT WAS HERE. The bar is the logo plus its
     padding and it renders 133px tall at every phone width checked (390, 500, 780) -
     the 68px that used to sit in `[id]{scroll-margin-top}` above was stale by 65px, which
     is why the kicker was landing behind the bar. That fallback now reads 133px too.
     >> IF THE TOP BAR'S HEIGHT CHANGES, RE-MEASURE AND RESET BOTH. */
  #beer{scroll-margin-top:0}
  .beer-stage{--bar:133px}

  /* >> STORY PINS THE SAME WAY, AND FOR THE SAME REASON. It is an ordinary section
     rather than a pinned stage, so the clearance is plain top padding: 133px of bar
     plus 20px of air, which is what the kicker actually sits on. Started at 40px of
     air and the owner asked for it tighter on a real phone (wd-s16) - the bar is a
     heavy block of wood, so an even gap under it READS bigger than the same gap at
     the foot of the section. The two are deliberately unequal. The 3.4rem the
     section carries by default is only 54px - less than the bar - so with the jump
     stopping on the seam the kicker would sit behind it.
     >> IT IS PADDING, NOT scroll-margin, DELIBERATELY. scroll-margin would stop the
     seam 133px down the screen and fill the strip behind the bar with the tail of the
     menu section. Padding puts the section's own empty space there instead. */
  /* >> STORY AND REVIEWS CARRY THE SAME NUMBER, AND THEY MUST STAY EQUAL. Landing on
     two sections from the same nav and getting two different gaps under the bar is the
     kind of thing that is invisible on either one alone and obvious when you jump
     between them. ONE rule, so they cannot drift.
     >> THIS IS NOW EVERY NAV TARGET EXCEPT BEER, which is a pinned stage and carries its
     own `--bar` clearance a few lines up - the two land on the same line by design. */
  #menu,#signature,#story,#reviews,#visit{scroll-margin-top:0;padding-top:153px}
  /* >> AND THE STICKY CARDS HAVE TO CLEAR THE BAR TOO, WHICH IS A SEPARATE VALUE FROM THE
     SECTION'S PIN. The rolodex holds each card with `position:sticky`, and it was parking
     them 5.2rem from the top of the viewport - 83px, against a 133px bar - so every card
     came to rest with its top edge behind the wood. The section landing correctly does not
     fix this; the card's own resting position is its own number.
     >> IT TRACKS THE BAR. If the bar height changes, this moves with `--bar` and the
     scroll-margin fallback. */
  .dish{top:calc(133px + .8rem)}

  /* the panel IS the desktop treatment now, so a phone only has to drop the wider wrap
     and the two-column stretch */
  #visit .wrap{max-width:1040px}
  .visit-grid{grid-template-columns:1fr;align-items:start}
  .front{min-height:330px}
  .visit-copy{padding:clamp(1.2rem,4vw,1.7rem)}
}

@media (max-width:520px){
  .row{grid-template-columns:1fr auto}
  /* the `.bottle` rule that sat here styled the CSS stand-in, which no longer exists.
     The bottle and the rail are both derived from `--cardw` now, so a phone needs no
     height override at all - only the floor, so the card cannot go under 190px. */
}

/* ============================================================
   LARGE SCREENS - THE COLUMN GROWS INSTEAD OF THE MARGINS (wd-s24, owner)

   The content column is a fixed 1040px centred in whatever space the rail leaves. On a
   27" that leaves a lot, so the column centres itself well away from the rail and the
   hero copy lands over the chicken carton. Growing the column takes that space instead.

   >> ADDITIVE. Nothing below 1600px is touched, so the laptop and phone render exactly as
      before. The growth also starts AT 1040px, so there is no jump at the breakpoint.
   >> IT STAYS CLOSER, IT IS NOT PINNED. The slope is under 1, so the left edge still eases
      right as the window grows - it just stops running away. A true pin needs the column
      to track the window 1:1, which is a different decision.
   >> BODY COPY DOES NOT STRETCH WITH IT - every paragraph here is capped in `ch`, so the
      column widens without the line length following it.
   ============================================================ */
@media (min-width:1600px){
  .wrap{max-width:min(1900px, calc(1040px + (100vw - 1600px) * .9))}
  /* these two run wider than the rest of the page - keep the same 140px offset */
  #visit .wrap, .foot .wrap{max-width:min(2040px, calc(1180px + (100vw - 1600px) * .9))}
}
