/* The generated pages, in the app's own visual language.
 * happytime/docs/seo/ — phases 01, 05.
 *
 * **These are the front door, so they have to look like the site.** Search is
 * usually most first visits to a listings site, and every shared
 * /evento/<id> link lands a person here — arriving on an unstyled page and
 * then meeting the app is two different products.
 *
 * Every value below is the app's, not an approximation: the palette is
 * `core/theme/app_theme.dart`, the two-column geometry is `BodyColumns` in
 * `features/home/presentation/widgets/section_band.dart`, and the type is the
 * same Poppins the app bundles. If the app's palette changes, change these.
 *
 * **No JavaScript, no third party, no cookies — and that is load-bearing.**
 * It is why these pages paint immediately and why they need no consent
 * banner. The fonts are served from this origin (the app already ships them
 * subset), so not even a font request leaves it. `verify_seo.js` fails the
 * build if an executable script appears on one of these pages. */

/* Poppins, from the bundle the app already downloads — same origin, already
 * cached for a returning visitor, and subset from 628KB to 122KB by
 * scripts/dev/subset_fonts.sh. Google Fonts would be a third party and, since
 * it transmits the reader's IP, a consent question. */
@font-face {
  font-family: Poppins;
  src: url("/assets/assets/fonts/Poppins-Regular.ttf") format("truetype");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: Poppins;
  src: url("/assets/assets/fonts/Poppins-Bold.ttf") format("truetype");
  /* 600-800, not just 700. The app uses SemiBold and ExtraBold too, and
   * shipping all four cost 119KB on the page whose whole selling point is
   * being 14KB — on the front door, for two weights nobody can pick out of a
   * heading. The browser picks the nearest face, so 600 and 800 render as
   * Bold. Two files, 59KB saved. */
  font-weight: 600 800;
  font-display: swap;
}

:root {
  --cream: #fff8e7;   /* colorScheme.surface */
  --wash: #fcebd1;    /* HomeBandTints.splitBody, 0.13 of orange over cream */
  --panel: #fffdf8;   /* BodyColumns.sidebarPanel */
  --card: #fffcf5;    /* cardBackground */
  --ink: #3b2e22;     /* onSurface */
  --orange: #e8963c;  /* primary */
  --muted: #7b6f60;
  --line: #e7dcc8;

  /* BodyColumns, to the pixel. */
  --envelope: 1240px;
  --sidebar: 300px;
  --gap: 40px;
  --gutter: 32px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font: 400 16px/1.55 Poppins, system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* ---- site chrome ------------------------------------------------------- */

/* **The app's own web header**, from `_WebHeader` in core/widgets/web_shell.dart:
 * a 72px bar on the lightest tint in the product with a hairline divider, the
 * brand on the left, the four destinations and the language switcher on the
 * right. Capped at 1440 rather than at the envelope, because that is the
 * header's own `_maxContentWidth`. */

header.site {
  background: #fef2dc;
  border-bottom: 1px solid rgba(59, 46, 34, 0.14);
}
header.site .bar {
  max-width: 1440px;
  margin: 0 auto;
  height: 72px;
  padding: 0 20px;
  display: flex;
  align-items: center;
  gap: 10px;
}

.mark {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px;
  border-radius: 12px;
  font-weight: 800;
  font-size: 1.05rem;
  letter-spacing: -0.5px;
  color: var(--ink);
  text-decoration: none;
}
.mark img { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; }

/* The destinations read as links, not as tabs: no filled pill, which is app
 * language. None is marked current — this page is not one of them. */
.tabs {
  margin-left: auto;
  display: flex;
  align-items: center;
}
.tabs a {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 8px 14px;
  color: rgba(59, 46, 34, 0.78);
  text-decoration: none;
  font-size: 0.92rem;
  font-weight: 600;
}
.tabs a:hover { color: var(--ink); }
.tabs svg {
  width: 19px; height: 19px; flex: 0 0 auto;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

/* The hairline separators the app puts either side of the greeting and
 * before the language control. See _HeaderRule. */
.rule {
  width: 1px; height: 22px; flex: 0 0 auto;
  background: rgba(59, 46, 34, 0.16);
}

/* **The greeting.** Small label on top, the phrase in the app's heavy
 * display weight under it, both on one line and both ellipsizing — a phrase
 * can run to sixty characters and the navigation is what the header is for.
 * The text is swapped by the one inline script on the page; the markup's
 * default is a real greeting, which is what a reader with JavaScript off and
 * every AI crawler sees. */
.greet {
  display: flex; flex-direction: column; justify-content: center;
  min-width: 0; flex: 0 1 auto; gap: 1px;
}
.greet .hello {
  font-size: 0.68rem; font-weight: 600; letter-spacing: 0.2px;
  /* 0.75 rather than 0.6: the header is the deepest tint in the product, and
   * 0.6 ink on it reads at 3.4:1 — under the floor for text this small. */
  color: rgba(59, 46, 34, 0.75);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.greet .phrase {
  font-size: 1rem; font-weight: 800; letter-spacing: -0.3px; line-height: 1.1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* **The language dropdown, with no JavaScript.** `<details>`/`<summary>` is
 * a native disclosure widget: it opens on click, it is keyboard operable and
 * it ships in every browser. The closed state is the app's pill — tinted
 * primary at 10%, flag, name, chevron. */
.langs { position: relative; flex: 0 0 auto; }
.langs summary {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 9px; border-radius: 999px;
  background: rgba(232, 150, 60, 0.10);
  color: #96570f; font-size: 0.82rem; font-weight: 600;
  cursor: pointer; list-style: none; user-select: none;
}
/* The default disclosure triangle, removed in both engines — the chevron is
   drawn, so the triangle would be a second one. */
.langs summary::-webkit-details-marker { display: none; }
.langs summary::marker { content: ''; }
.langs summary:hover { background: rgba(232, 150, 60, 0.18); }
.langs .chevron {
  width: 16px; height: 16px; flex: 0 0 auto;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform 150ms;
}
.langs[open] .chevron { transform: rotate(180deg); }
.langs ul {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 20;
  margin: 0; padding: 6px; list-style: none; min-width: 170px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: 0 8px 24px rgba(59, 46, 34, 0.18);
}
.langs li a, .langs li .on {
  display: flex; align-items: center; gap: 10px;
  /* 12px gives a 44px row, which is the touch-target floor and also what
   * Material's PopupMenuItem is in the app. The old 8px was a mouse-sized
   * menu in a page that is indexed from its phone rendering. */
  padding: 12px; border-radius: 8px;
  color: var(--ink); text-decoration: none; font-size: 0.9rem;
}
.langs li a:hover { background: rgba(59, 46, 34, 0.06); }
/* The current language is in the menu and is not a link: a control that does
   nothing is worse than no control. `aria-current` says which, and the tick
   is what the app draws. */
.langs li .on { font-weight: 700; }
.langs .check {
  width: 16px; height: 16px; margin-left: auto; flex: 0 0 auto;
  fill: none; stroke: var(--orange); stroke-width: 2.4;
  stroke-linecap: round; stroke-linejoin: round;
}
.langs .flag {
  flex: 0 0 auto; width: 20px; height: 14px;
  /* The hairline is the app's: without it a yellow-heavy flag floats on a
   * pale header with no edge and stops reading as a flag. */
  border-radius: 2px; overflow: hidden;
  box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.18);
}

/* The app's own breakpoints: the labels go first, then the language names —
 * the navigation is what a header is for, and a setting gives up its width
 * before the links give up theirs. See _compactNav and _compactLanguage. */
/* The app's own order: the greeting goes first, then the language name, and
 * only then the nav labels. Dropping them together would read as the header
 * falling apart rather than as it adapting. See _greetingWidth. */
@media (max-width: 1120px) {
  .greet, .rule { display: none; }
}
@media (max-width: 1040px) {
  .langs summary .name { display: none; }
}
/* **Touch targets, raised only where there are fingers.** The desktop header
 * keeps the app's exact padding — this is meant to be the same header — but
 * below 900px the labels are gone and what is left is icon-sized, so the rows
 * grow to clear the 44px floor. Google reports anything smaller as a mobile
 * usability error, and the mobile rendering is the one that gets indexed. */
@media (max-width: 900px) {
  .tabs a { padding: 13px 10px; }
  .tabs a span { display: none; }
  .langs summary { padding: 12px 10px; }
}
@media (max-width: 560px) {
  .mark span { display: none; }
  header.site .bar { height: 60px; padding: 0 12px; }
}

footer.site {
  max-width: var(--envelope);
  margin: 0 auto;
  padding: 14px var(--gutter);
  display: flex;
  flex-wrap: wrap;
  gap: 10px 16px;
  align-items: baseline;
}
footer.site { justify-content: center; padding-bottom: 28px; }
footer.site ul {
  display: flex; flex-wrap: wrap; gap: 8px 18px;
  margin: 0; padding: 0; list-style: none; font-size: 0.82rem;
}
/* **A 24px hit area, not a 19px line of text.**
 *
 * These four were the only links on a generated page under the floor: the
 * pills, the listing rows and the directions link were all taken to 44 when
 * the sidebar was rebuilt, and the footer was missed — 19px of line box, which
 * fails the site's own 44 and Lighthouse's laxer 24 alike. Padding rather than
 * a height, so the text keeps its baseline and the row keeps its shape, with
 * the same amount pulled back off the list so the footer does not grow by the
 * padding on every row. */
footer.site a {
  color: var(--muted);
  display: inline-block;
  padding: 12px 4px;
}
footer.site ul { margin: -12px -4px; }

/* ---- the two-column body ----------------------------------------------- *
 * The home page's shape: one full-bleed wash from the header down, the
 * content capped at the envelope, and the sidebar a warm-white panel with
 * square corners running the full height — a column of the page rather than a
 * card on it. See SplitBody and BodyColumns.sidebarPanel. */

.wash { background: var(--wash); }

.envelope {
  max-width: var(--envelope);
  margin: 0 auto;
  padding: 0 var(--gutter);
  display: grid;
  grid-template-columns: 1fr;
}

main { min-width: 0; padding: 28px 0 36px; }

/* The panel, stacked under the content until there is room beside it. */
.panel {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 18px 0 28px;
}

/* **A rule wherever a surfaceless block meets anything.** The panel read as
 * one undifferentiated wash: the space *between* blocks was the same as the
 * space *inside* them, so a link at the foot of one block sat as close to the
 * heading below it as to the text it belonged to, and nothing had an edge.
 *
 * A rule rather than a card around each block. Boxing them was the other
 * answer and costs more than it gives: a white card on a white panel is an
 * outline inside an outline of nearly the same colour, and five of them turn
 * one column into five competing objects. A rule is the quietest mark that
 * still divides — which is why newspapers settled on it.
 *
 * Only around `.book` and `.weekend`, the two blocks with no surface of their
 * own. Two bordered boxes in a row get the plain gap above, because a
 * hairline between two borders is a third line doing nothing. Mirrors
 * `_SectionRule` and the `bare` flag in `SiteSidebar`. */
.panel > .book + *,
.panel > .weekend + * {
  border-top: 1px solid rgba(190, 172, 142, 0.75);
  padding-top: 16px;
  margin-top: -2px;
}

/* BodyColumns.minViewport. **800, down from 1340**: the panel used to vanish
 * below 1340px, taking the book, the sponsor and the advertising with it for
 * most of the web's traffic. It now stays beside the page for as long as the
 * main column can still be a page, and below that it sits at the end of it —
 * which is where the rules above already put it. The panel must still never be
 * the only route to anything. */
@media (min-width: 800px) {
  .envelope {
    grid-template-columns: 1fr var(--sidebar);
    column-gap: var(--gap);
    align-items: start;
  }
  .panel {
    background: var(--panel);
    /* No radius, flush top and bottom: the panel meets the header above and
     * the footer below with nothing between, so the page reads as one surface
     * changing colour down a vertical seam. */
    padding: 18px 16px;
    min-height: 100%;
    align-self: stretch;
  }
  /* The sibling links live in the panel at this width. */
  .narrow-only { display: none; }
  .aside-block { display: block; }
}

/* ---- the article ------------------------------------------------------- */

article {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 20px;   /* the app's card radius */
  overflow: hidden;
  padding: 0 22px 26px;
}

.kicker {
  margin: 20px 0 0;
  color: var(--orange);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

h1 { margin: 6px 0 14px; font-size: 1.75rem; line-height: 1.18; font-weight: 800; letter-spacing: -0.6px; }
h2 { margin: 26px 0 8px; font-size: 1.05rem; font-weight: 700; }

/* **220px tall, which is the expanded card's hero** — not a full-width 3:2.
 *
 * At this column width 3:2 is a 527px photograph, and everything that decides
 * whether to go — the dates, the times, the ages, the price — started below
 * the fold. A picture that has to be scrolled past is not doing its job; this
 * is the height the app itself gives it. `object-fit: cover` keeps the same
 * crop behaviour, so the photo is still framed and not squashed. */
.hero {
  display: block;
  width: calc(100% + 44px);
  height: 220px;
  margin: 0 -22px 18px;
  object-fit: cover;
  background: var(--line);
}

.description { margin: 0; }

/* One day per row: the weekday, then every start time on it. */
.days { margin: 0; padding: 0; list-style: none; }
.days li {
  display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: baseline;
  padding: 10px 0; border-bottom: 1px solid var(--line);
}
.days li:last-child { border-bottom: 0; }
.days time { font-weight: 600; min-width: 11em; }
.days .times { color: var(--orange); font-weight: 700; font-variant-numeric: tabular-nums; }
.days .count { color: var(--muted); font-size: 0.85rem; }

/* The price of one day, and only where it differs from the event's own —
   see showingsByDay. Quiet, because it is an exception to a number the facts
   list already states: shouting it on the one cheap matinee would make the
   other four days look like they were missing something. */
.days .day-price {
  color: var(--ink);
  font-weight: 700;
  font-size: 0.85rem;
  background: var(--wash, #fdf3e6);
  border-radius: 999px;
  padding: 1px 8px;
}

.facts {
  display: grid; grid-template-columns: auto 1fr; gap: 4px 16px;
  margin: 18px 0 0; padding: 14px 0 0; border-top: 1px solid var(--line);
}
.facts dt { color: var(--muted); font-size: 0.85rem; }
.facts dd { margin: 0; font-weight: 600; }

/* The event's own one-line place, under the days — the card's location row. */
.where { margin: 14px 0 0; font-weight: 600; }

/* ---- the venue block --------------------------------------------------- *
 * The practical half of "should we go": where it is, how to get there and
 * what you will find when you arrive. Drawn as `_VenueSection` draws it — a
 * tinted panel inside the card, 0.08 of the app's secondary over the card. */

.venue {
  margin: 22px 0 0;
  padding: 14px 14px 16px;
  border-radius: 16px;
  background: rgba(190, 172, 142, 0.16);
}
.venue h2 { margin: 0; font-size: 1rem; color: #96570f; }
.venue h3 { margin: 14px 0 6px; font-size: 0.88rem; font-weight: 700; }
.venue-name { margin: 10px 0 2px; font-weight: 600; }
.address { margin: 0; color: var(--muted); font-size: 0.9rem; }
.getting-there { margin: 10px 0 0; font-size: 0.9rem; color: var(--muted); }

/* ---- the map ----------------------------------------------------------- *
 * A 3x3 mosaic of OpenStreetMap tiles, served from this origin, with the pin
 * over the venue — see happycms/lib/tiles.js. It does not pan or zoom, because
 * that needs a script and a script needs a consent banner; the app is what
 * pans, and the directions link below is what Saturday morning needs.
 *
 * **The pin is centred by offsetting the mosaic, not by cropping it.** The
 * mosaic sits at the box's centre, shifted by the venue's own pixel position
 * (the inline `margin-left`/`margin-top` the generator writes), so the pin
 * lands dead centre at every container width.
 *
 * 512px is not a taste decision: one ring of tiles guarantees the venue is at
 * least 256px from every edge of the 768px mosaic, so 512 is the widest box
 * that can never show past it. See mosaicFor. */

.map {
  position: relative;
  height: 240px;
  max-width: 512px;
  margin: 12px 0 0;
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
  /* Visible while the tiles decode, so the block does not flash white. */
  background: #e9e3da;
}
.map .tiles {
  position: absolute;
  left: 50%;
  top: 50%;
  display: grid;
  grid-template-columns: repeat(auto-fill, 256px);
  line-height: 0;
}
.map .tiles img { display: block; width: 256px; height: 256px; }
.map .pin {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46px;
  height: 54px;
  /* Half the width across, and up by the distance from the top of the glyph to
   * its tip — the pin stands *above* the point it marks. The same anchoring
   * mistake in the app was 54px wrong, which on this map is most of a block;
   * see the comment on Alignment.topCenter in venue_map.dart. */
  margin-left: -23px;
  margin-top: -50px;
  filter: drop-shadow(0 2px 3px rgba(59, 46, 34, 0.35));
}
/* Required by the tile usage policy, and simply correct: the map is somebody
 * else's work. */
.map .attribution {
  position: absolute;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 2px 7px;
  border-top-left-radius: 8px;
  background: rgba(255, 253, 248, 0.84);
  color: var(--muted);
  font-size: 0.64rem;
}
.map .attribution a { color: inherit; }

.directions { margin: 12px 0 0; }
/* 44px for the same reason as the pills above: this is the one link on the
 * page a reader presses while standing up, on the way somewhere. */
.directions a {
  display: inline-flex; align-items: center;
  min-height: 44px; padding: 0 16px; border-radius: 999px;
  border: 1px solid rgba(232, 150, 60, 0.45); background: var(--card);
  color: #96570f; text-decoration: none; font-weight: 600; font-size: 0.88rem;
}

/* Transport and facilities, as the card's pills. One line per fact, wrapped —
 * the modes are not ranked, so a row is the right shape for them. */
.transport, .chips {
  margin: 0; padding: 0; list-style: none;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.transport li, .chips li {
  padding: 5px 11px; border-radius: 999px;
  background: var(--card); border: 1px solid var(--line);
  font-size: 0.82rem;
}
.transport li { font-weight: 600; }

.finished {
  margin: 0 0 14px; padding: 10px 12px; border-radius: 10px;
  background: rgba(59, 46, 34, 0.06); color: var(--muted); font-weight: 600;
}

/* ---- an Idea ------------------------------------------------------------ *
 * The page an Idea's button opens. With the forwarding query it redirects on
 * arrival and none of this is seen; without it, this is what the reader gets,
 * which is why it is a page about the product and not a bare bounce. */

.idea .review {
  margin: 16px 0 0;
  padding: 12px 16px;
  border-left: 3px solid var(--orange);
  background: rgba(232, 150, 60, 0.07);
  border-radius: 0 10px 10px 0;
  font-style: italic;
}
.idea .cta { margin: 22px 0 10px; }
/* The one action, drawn like the app's FilledButton — and its own class
   rather than `.app`, because that one means "open the Flutter app" and this
   one leaves for a shop. */
.shop {
  display: block; padding: 15px 18px; border-radius: 14px;
  background: var(--orange); color: #fff;
  font-weight: 700; text-align: center; text-decoration: none;
}
/* Required, not decoration: a paid link has to say it is one, and the page
   that carries it is where that belongs. */
.disclosure {
  margin: 12px 0 0; color: var(--muted);
  font-size: 0.8rem; line-height: 1.45;
}

/* ---- about us ---------------------------------------------------------- */

/* The one page on the site that is prose. Everything else here is a card, a
   row or a fact list, so the measure has to be set explicitly: at the full
   column width these paragraphs run to about 110 characters, which is half as
   long again as anything a reader tracks comfortably. */
.about p { max-width: 62ch; }
.about .lead {
  margin: 0 0 18px;
  font-size: 1.15rem;
  line-height: 1.5;
  font-weight: 600;
}
.about-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 18px 0 4px;
  max-width: none;
}
/* A pill rather than the full-width orange `.shop` button: that one means "you
   are leaving to buy something" and these two are an invitation to say hello.
   Outlined, in the app's own chip shape. */
.about-link {
  display: inline-block;
  padding: 10px 16px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fffdf8;
  color: var(--ink);
  font-weight: 600;
  font-size: 0.92rem;
  text-decoration: none;
}
.about-link:hover { border-color: var(--orange); color: var(--orange); }

/* ---- listing rows ------------------------------------------------------ */

.listing .count { margin: 0 0 14px; color: var(--muted); font-size: 0.9rem; }
.listing ul.events { margin: 0; padding: 0; list-style: none; }
.listing ul.events li { border-bottom: 1px solid var(--line); }
.listing ul.events li:last-child { border-bottom: 0; }
.listing ul.events a {
  display: flex; gap: 14px; align-items: center;
  padding: 12px 0; text-decoration: none; color: inherit;
}
.listing ul.events a:hover .name { color: var(--orange); }
.listing ul.events img {
  flex: 0 0 auto; width: 86px; height: 58px;
  object-fit: cover; border-radius: 10px; background: var(--line);
}
.listing .row { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.listing .name { font-weight: 600; }
.listing .when { color: var(--orange); font-size: 0.86rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.listing .facts {
  display: block; margin: 0; padding: 0; border: 0;
  color: var(--muted); font-size: 0.82rem;
}

/* ---- sibling links, in the panel or in the article --------------------- */

.siblings { margin-top: 24px; padding-top: 14px; border-top: 1px solid var(--line); }
.siblings h2 { margin: 0 0 8px; font-size: 0.95rem; }
.siblings ul, .aside-block ul {
  margin: 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: 8px;
}
/* **44px tall, which is a tap target and not a style choice.** These were
 * 7px of padding on a 0.86rem line — about 30px of real height, under the
 * 44px touch guideline and reported by Search Console as a mobile usability
 * error. `.siblings` is the set that shows on a phone (`.aside-block` is
 * hidden below the split), so this is the one that was actually being
 * mis-tapped. `min-height` with centring rather than more padding, so the
 * number is the guarantee instead of arithmetic over a font size. */
.siblings a, .aside-block a {
  display: inline-flex; align-items: center;
  min-height: 44px; padding: 0 14px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--card); color: inherit;
  text-decoration: none; font-size: 0.86rem;
}
/* **Hidden below the split, because the article shows the same links there.**
 *
 * They are in the HTML twice — once in the article for narrow screens, once in
 * the panel for wide ones — and without this both were visible on a phone, so
 * a reader saw the links twice. Googlebot renders mobile-first, which is
 * exactly the width where the duplicate was showing.
 *
 * One copy visible at every width, and neither copy hidden at *all* widths:
 * responsive display is fine, permanently CSS-hidden content is devalued. */
.aside-block { display: none; }
.aside-block h2 { margin: 0 0 8px; font-size: 0.92rem; }
.aside-block ul { flex-direction: column; }
.aside-block a { text-align: center; }

/* ---- the sidebar's own items ------------------------------------------- *
 * The web home page's sidebar, item for item: weekend picks, an advert, the
 * Instagram card, an advert, a sponsored block. See HomeSidebar and
 * siteAside() in happycms/lib/seo_chrome.js. */

.weekend h2 {
  /* Centred and a size up, matching `.book h2` above it: the panel's two text
   * blocks are headed alike. Mirrors _WeekendPicks. */
  margin: 0 0 8px; font-size: 1.1rem; font-weight: 800; letter-spacing: -0.2px;
  text-align: center;
}
.weekend ul { margin: 0; padding: 0; list-style: none; }
.weekend .nothing { margin: 0; color: var(--muted); font-size: 0.85rem; }
.weekend li.pick { border-bottom: 1px solid rgba(231, 220, 200, 0.6); }
.weekend li.pick:last-child { border-bottom: 0; }
/* `min-height` declared although the real target already exceeds it: three
 * stacked lines (the day and times, the name, the price) plus padding is
 * about 60px. It is here because the estimate in seo-guard measures a single
 * nominal line box and cannot see the stack — so the floor states what the
 * layout already guarantees, which makes the number honest without changing
 * a pixel. Not padding added to satisfy a gate; the gate was right to ask
 * and wrong about this one. */
.weekend li.pick a {
  display: flex; flex-direction: column; gap: 1px;
  min-height: 44px; justify-content: center;
  padding: 7px 2px; text-decoration: none; color: inherit;
}
.weekend li.pick a:hover .name { color: var(--orange); }
/* The weekday and every time on it, in the orange the cards use for when. */
.weekend .when {
  color: var(--orange); font-weight: 700; font-size: 0.63rem;
  letter-spacing: 0.04em; font-variant-numeric: tabular-nums;
}
.weekend .name { font-weight: 600; font-size: 0.92rem; line-height: 1.25; }
/* One fact per line, the rule the event cards follow. */
.weekend .price { color: var(--muted); font-size: 0.7rem; }
.weekend .more { margin: 6px 0 0; font-size: 0.85rem; }
.weekend .more a { color: var(--orange); }

/* ---- the book of the month -------------------------------------------- *
 * **Part of the panel, not a card on it.** No background, no border, no fixed
 * height: the aside is already the paper (--card), so a white box drawn on a
 * white panel is a rectangle you can just about see, and an outline inside an
 * outline of nearly the same colour is a border around nothing. The same
 * reasoning `.weekend` above is built on, and the same heading treatment down
 * to the negative tracking — two headings in one column that differ in weight
 * or colour read as two columns.
 *
 * It was a fixed 300x250 matching the advertising format. A fixed height is
 * what inventory needs; content has no such duty, and squeezing a cover, a
 * title, an author, a review and an action into 250px left the cover at about
 * 90px — a thumbnail of a book rather than a book. */

.book h2 {
  margin: 0 0 12px;
  /* A size up from the other panel headings (0.95rem) and centred — the book
   * block is a single centred object, and a left-aligned small heading over a
   * centred cover read as two unrelated things. Mirrors BookOfTheMonthCard,
   * and `.weekend h2` below matches it. */
  font-size: 1.1rem;
  font-weight: 800;
  letter-spacing: -0.2px;
  text-align: center;
}
/* One link around the whole block, so a cover and a caption do not both
 * navigate. `display: block` rather than flex: the children stack, and the
 * cover centres itself with `margin: auto` below. */
.book-link {
  display: block;
  text-decoration: none;
  color: inherit;
}
.book-link img {
  display: block;
  margin: 0 auto 12px;
  width: 112px;
  height: 168px;
  object-fit: cover;
  /* Books have square corners; 4px is the printing, not a card. */
  border-radius: 4px;
  /* The only elevation in the block, and it is doing real work: it separates
   * a pale cover from pale paper, which a border would do more loudly. */
  box-shadow: 0 2px 6px rgba(90, 74, 52, 0.22);
}
.book-title {
  display: block;
  font-weight: 700;
  font-size: 0.9rem;
  line-height: 1.25;
}
.book-link:hover .book-title { color: var(--orange); }
.book-author {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: 0.72rem;
}
.book-review {
  margin-top: 8px;
  color: var(--muted);
  font-size: 0.75rem;
  line-height: 1.35;
  /* Four lines then an ellipsis: enough for a real thought, and a ceiling so
   * one long review cannot push the adverts below it off the column. */
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Styled as the panel's other link is, in the brand colour, with an arrow
 * that says it leaves the site. A pseudo-element rather than an icon file:
 * it costs no request and cannot fail to load. */
.book-go {
  display: inline-block;
  margin-top: 10px;
  color: var(--orange);
  font-weight: 700;
  font-size: 0.8rem;
}
.book-go::after { content: " \2197"; }
.book-link:hover .book-go { text-decoration: underline; }
.book-note {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 0.6rem;
}

/* The follow card: Instagram's own gradient, darkened a notch so white text
 * reads along all of it — the exact stops `_InstagramFollowCard` uses. */
.ig {
  display: flex; align-items: center; gap: 14px;
  padding: 16px 18px; border-radius: 20px;
  text-decoration: none; color: #fff;
  background: linear-gradient(135deg, #b29952, #af5815, #961d53, #692186, #374095);
  box-shadow: 0 6px 12px rgba(150, 29, 83, 0.4);
}
.ig-icon {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.3);
  display: flex; align-items: center; justify-content: center;
}
.ig-icon svg {
  width: 22px; height: 22px;
  fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.ig-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ig-title { font-weight: 700; font-size: 0.98rem; }
.ig-handle { color: rgba(255, 255, 255, 0.75); font-size: 0.86rem; }

/* ---- advertising space ------------------------------------------------- *
 * The 300x250 the app reserves, drawn as the app draws it while ADS_ENABLED
 * is false: a labelled placeholder that stores nothing and loads nothing. See
 * adSlot() in happycms/lib/seo_chrome.js for what filling it would cost. */

/* ---- the sponsor invitation -------------------------------------------- *
 * The unsold directly-sold box, as an offer rather than a gap. It keeps the
 * `.ad` box's exact 300x250 so nothing moves when a creative arrives, and
 * replaces the grey with a soft wash of the brand colour: grey is the colour
 * of something missing, and the reader this is aimed at is a local business
 * already reading the site. Mirrors `_SponsorInvite` in the app.
 *
 * A solid border, not dashed. A dashed outline is the universal mark of an
 * empty slot, which is the impression this exists to undo. */

.ad-invite {
  border-color: rgba(232, 150, 60, 0.35);
  background: linear-gradient(
    to bottom,
    rgba(232, 150, 60, 0.10),
    rgba(232, 150, 60, 0.03)
  );
}
.sponsor-invite {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  height: 100%;
  padding: 18px;
  box-sizing: border-box;
  text-decoration: none;
  color: inherit;
  text-align: center;
}
.sponsor-mark {
  width: 46px;
  height: 46px;
  margin-bottom: 6px;
  border-radius: 50%;
  background: rgba(232, 150, 60, 0.16);
  display: flex;
  align-items: center;
  justify-content: center;
}
.sponsor-mark svg {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: var(--orange);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sponsor-title {
  font-size: 0.95rem;
  font-weight: 800;
  letter-spacing: -0.2px;
  color: var(--ink);
}
.sponsor-body {
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--muted);
}
.sponsor-cta {
  margin-top: 6px;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--orange);
}
.sponsor-cta::after { content: " \2709"; }
.sponsor-invite:hover .sponsor-cta { text-decoration: underline; }

.ad {
  width: 300px;
  height: 250px;
  max-width: 100%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(190, 172, 142, 0.7);
  border-radius: 10px;
  background: rgba(190, 172, 142, 0.18);
  color: var(--muted);
  text-align: center;
}
.ad svg {
  width: 28px; height: 28px;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: 0.7;
}
.ad-note { font-size: 0.72rem; padding: 0 10px; }
/* Only the directly-sold box carries the word. See adSlot(). */
.ad-label {
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; opacity: 0.75;
}

/* ---- the consent banner ------------------------------------------------ *
 * These pages needed none until advertising arrived, and it exists for that
 * reason alone: they still set no cookies of their own. What needs agreement
 * is loading somebody else's script, so a decline leaves the page exactly as
 * it was — nothing third-party is fetched at all.
 *
 * `hidden` in the markup and unhidden by the script only when there is no
 * stored answer, so a reader who already chose never sees it flash. */

.consent[hidden] { display: none; }
.consent {
  position: fixed;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 50;
  width: min(560px, calc(100vw - 32px));
  padding: 16px 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: 0 10px 30px rgba(59, 46, 34, 0.22);
}
.consent-text { margin: 0; font-size: 0.88rem; line-height: 1.5; }
.consent-text strong { display: block; margin-bottom: 2px; }
.consent-actions {
  display: flex; gap: 10px; justify-content: flex-end;
  margin: 14px 0 0; flex-wrap: wrap;
}
/* Both choices are buttons of the same size. A decline styled as an
   afterthought beside a prominent accept is a dark pattern, and the GDPR
   asks for consent to be as easy to refuse as to give. */
.consent-actions button {
  flex: 1 1 auto;
  min-width: 140px;
  min-height: 44px;
  padding: 11px 18px;
  border-radius: 999px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.consent-no {
  background: var(--card);
  border: 1px solid var(--line);
  color: var(--ink);
}
.consent-yes {
  background: var(--orange);
  border: 1px solid var(--orange);
  color: #fff;
}

/* ---- phone ------------------------------------------------------------- */

@media (max-width: 600px) {
  :root { --gutter: 16px; }
  article { padding: 0 16px 22px; border-radius: 16px; }
  .hero { width: calc(100% + 32px); margin-left: -16px; margin-right: -16px; }
  h1 { font-size: 1.45rem; }
  .hero { height: 170px; }
  .days time { min-width: 100%; }
  .map { height: 200px; }
}

/* **No dark mode, deliberately.** There was a `prefers-color-scheme: dark`
 * block here and it was the bug: the app has no dark theme — `AppTheme` builds
 * one `ColorScheme` at `Brightness.light` and `app.dart` sets no `darkTheme` —
 * so a reader whose browser prefers dark got dark generated pages and a light
 * app. These pages are the front door to that app; looking like something else
 * is worse than looking plain.
 *
 * Add it back the day the app grows a dark theme, from the app's own values,
 * not from an inversion of these. */
