/* ============================================================
   Παντοπωλείο Οδυσσέας | style.css
   ============================================================
   STRUCTURE OF THIS FILE
   1. Design tokens (CSS custom properties)
   2. Reset and base typography
   3. Reusable pieces (container, buttons, section rhythm)
   4. Header and navigation
   5. Hero
   6. About
   7. What we offer (bento grid + product tags)
   8. Gallery + lightbox
   9. Location & contact
   10. Footer
   11. Motion (scroll reveals, reduced-motion overrides)
   12. Catalogue page (products.html)

   METHOD: strictly mobile-first. Base rules describe the phone
   layout; @media (min-width: ...) blocks progressively add the
   tablet and desktop layouts. This mirrors how browsers download
   CSS: small screens never pay for desktop complexity.
   ============================================================ */


/* ------------------------------------------------------------
   1. DESIGN TOKENS
   One palette for the whole page: bone neutrals with a green
   cast and a single laurel-green accent. Naming colors by role
   (ink, canvas, accent) instead of appearance means a future
   re-skin touches only this block.
   ------------------------------------------------------------ */
:root {
  /* Color */
  --canvas: #F7F7F2;        /* page background: cool bone, not pure white */
  --surface: #FDFDFA;       /* raised surfaces (header, form panel) */
  --ink: #1B231C;           /* primary text: off-black with a green cast */
  --muted: #57625A;         /* secondary text; passes WCAG AA on canvas */
  --line: #E1E4DC;          /* hairline borders */
  --accent: #365C41;        /* laurel green: the ONE accent of the page */
  --accent-deep: #24402D;   /* hover state + dark footer background */
  --on-accent: #F4F6F0;     /* text placed on green backgrounds */
  --on-accent-muted: #C9D4C4; /* secondary text on green backgrounds */
  --kraft: #F0EAD9;         /* warm paper tint (the no-JS notice) */

  /* Typography */
  --font-display: "GFS Didot", serif;               /* Greek print classic */
  --font-body: "Commissioner", system-ui, sans-serif;

  /* Shape: one radius system for the whole page */
  --radius: 10px;

  /* Layout rhythm. Each section carries this as padding on BOTH its
     top and bottom, so the visible white space between two sections
     is double this value: keep that in mind when tuning it. */
  --header-height: 4.25rem;
  --section-gap: clamp(2.25rem, 5.5vw, 4rem);

  /* Z-index scale, documented so layers never fight:
     1 = sticky header, 2 = mobile nav panel, 10 = lightbox */
  --z-header: 1;
  --z-nav: 2;
  --z-lightbox: 10;
}


/* ------------------------------------------------------------
   2. RESET AND BASE
   ------------------------------------------------------------ */
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1rem;               /* never below 1rem: mobile readability */
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img {
  max-width: 100%;
  height: auto;                  /* keeps the width/height HTML attributes
                                    from distorting the image while still
                                    reserving space (prevents layout shift) */
  display: block;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 400;              /* GFS Didot ships one weight; declaring it
                                    avoids ugly synthetic (faux) bolding */
  line-height: 1.15;
  margin: 0 0 0.75rem;
}

p {
  margin: 0 0 1rem;
  max-width: 65ch;               /* comfortable measure for reading */
}

a {
  color: var(--accent);
}

/* One visible, consistent focus style everywhere. :focus-visible only
   shows the ring for keyboard users, not after mouse clicks. */
:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Anchored sections stop below the sticky header instead of hiding under it. */
section[id] {
  scroll-margin-top: calc(var(--header-height) + 1rem);
}

.noscript-note {
  padding: 1rem;
  text-align: center;
  background: var(--kraft);
  margin: 0;
  max-width: none;
}


/* ------------------------------------------------------------
   3. REUSABLE PIECES
   ------------------------------------------------------------ */
.container {
  width: 100%;
  max-width: 1200px;             /* containment: lines never grow endless */
  margin-inline: auto;
  padding-inline: 1.25rem;
}

.section {
  padding-block: var(--section-gap);
}

.section-title {
  font-size: clamp(1.75rem, 4.5vw, 2.5rem);
  letter-spacing: -0.01em;
}

.section-intro {
  color: var(--muted);
  margin-bottom: 2.5rem;
}

/* Visually hidden but still read by screen readers: the standard
   clip pattern. The chalkboard writing animation uses it so
   assistive tech reads whole words, never single letters. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Buttons: flat, tactile, 44px minimum touch target.
   :active nudges down 1px to feel like a physical press. */
.button {
  display: inline-block;
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 600;
  text-decoration: none;
  text-align: center;
  border-radius: var(--radius);
  padding: 0.8rem 1.6rem;
  min-height: 44px;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background-color 0.2s ease, transform 0.15s ease;
}

.button:active {
  transform: translateY(1px);
}

.button--primary {
  background: var(--accent);
  color: var(--on-accent);
}

.button--primary:hover {
  background: var(--accent-deep);
}

.button--ghost {
  background: transparent;
  color: var(--accent);
  border-color: var(--accent);
}

.button--ghost:hover {
  background: var(--accent);
  color: var(--on-accent);
}


/* ------------------------------------------------------------
   4. HEADER AND NAVIGATION
   Sticky so the language toggle and menu stay reachable.
   position: sticky needs no JavaScript scroll listeners at all.
   ------------------------------------------------------------ */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.site-header__inner {
  min-height: var(--header-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.wordmark {
  font-size: 1.15rem;
  margin: 0;
  letter-spacing: 0.01em;
}

.site-header__controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.lang-toggle {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.9rem;
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  min-width: 44px;
  min-height: 44px;
  cursor: pointer;
  transition: border-color 0.2s ease;
}

.lang-toggle:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* Hamburger: three bars drawn with spans. Mobile only. */
.menu-toggle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: 10px;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}

.menu-toggle__bar {
  display: block;
  height: 2px;
  width: 100%;
  background: var(--ink);
}

/* MOBILE NAV: hidden by default, opened by script.js adding .is-open.
   It drops below the header as a full-width panel. */
.site-nav {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  z-index: var(--z-nav);
}

.site-nav.is-open {
  display: block;
}

.site-nav__list {
  list-style: none;
  margin: 0;
  padding: 0.5rem 1.25rem 1rem;
}

.site-nav__list a {
  display: block;                /* full-width row = easy touch target */
  padding: 0.75rem 0;
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
}

.site-nav__list li:last-child a {
  border-bottom: none;
}

.site-nav__list a:hover {
  color: var(--accent);
}

/* DESKTOP NAV: from 768px the panel becomes an inline, single-line menu
   and the hamburger disappears. */
@media (min-width: 768px) {
  .menu-toggle {
    display: none;
  }

  .site-nav {
    display: block;
    position: static;
    background: transparent;
    border-bottom: none;
  }

  .site-nav__list {
    display: flex;
    gap: 1.75rem;
    padding: 0;
  }

  .site-nav__list a {
    padding: 0.25rem 0;
    border-bottom: none;
  }
}


/* ------------------------------------------------------------
   5. HERO
   Full height photo with a left-to-right scrim: the gradient is
   darkest where the text sits, guaranteeing readable contrast.
   Content is left-aligned and sits low, an asymmetric composition
   instead of the generic dead-center hero.
   ------------------------------------------------------------ */
.hero {
  /* 100vh fallback first; svh corrects the iOS address-bar jump. */
  min-height: calc(100vh - var(--header-height));
  min-height: calc(100svh - var(--header-height));
  display: flex;
  align-items: flex-end;
  /* Our own photo of the storefront. The page serves a web-sized copy
     (1920px wide) from images/hero/web/, not the 8MB camera original
     kept in images/hero/: the hero is the first thing to paint, so its
     file size directly decides how fast the page feels. The gradient
     on top guarantees readable text. */
  background-image:
    linear-gradient(75deg, rgba(15, 24, 17, 0.82) 0%, rgba(15, 24, 17, 0.55) 45%, rgba(15, 24, 17, 0.25) 100%),
    url("images/hero/web/IMG_3465.jpg");
  background-size: cover;
  background-position: center;
}

.hero__content {
  padding-block: 4rem 5rem;
  color: var(--on-accent);
}

.hero__eyebrow {
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: 1rem;
}

.hero__title {
  font-size: clamp(2.4rem, 8vw, 4.5rem);
  color: #FFFFFE;
  max-width: 14ch;
  margin-bottom: 1rem;
}

.hero__subtitle {
  font-size: clamp(1.05rem, 2.5vw, 1.25rem);
  max-width: 46ch;
  margin-bottom: 2rem;
}

.hero__cta {
  font-size: 1.05rem;
  padding: 0.9rem 2rem;
}


/* ------------------------------------------------------------
   6. ABOUT
   Mobile: single column (text, then photo).
   Desktop: asymmetric two-column grid, 3fr text / 2fr image.
   ------------------------------------------------------------ */
.about__grid {
  display: grid;
  grid-template-columns: 1fr;    /* mobile default */
  gap: 2.5rem;
}

.about__figure {
  margin: 0;
}

.about__figure img {
  width: 100%;
  border-radius: var(--radius);
  object-fit: cover;
}

.about__figure figcaption {
  font-size: 0.9rem;
  color: var(--muted);
  padding-top: 0.6rem;
}

@media (min-width: 768px) {
  .about__grid {
    grid-template-columns: 3fr 2fr;
    gap: 4rem;
    align-items: center;
  }
}


/* ------------------------------------------------------------
   7. WHAT WE OFFER
   7a. Category bento: four cells of deliberately unequal size,
       built with grid-template-areas so the HTML order stays
       logical for screen readers while the layout is free.
   ------------------------------------------------------------ */
.offer__grid {
  display: grid;
  grid-template-columns: 1fr;    /* mobile: simple stack */
  gap: 1rem;
  margin-bottom: 3.5rem;
}

.offer-card {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  min-height: 11rem;
  display: flex;
  align-items: flex-end;
}

.offer-card__body {
  padding: 1.25rem;
}

.offer-card__body h3 {
  font-size: 1.35rem;
  margin-bottom: 0.35rem;
}

.offer-card__body p {
  margin: 0;
  color: var(--muted);
  font-size: 0.95rem;
}

/* Photo cells: the image fills the card and a bottom scrim keeps
   the light text readable over any photo. */
.offer-card--photo .offer-card__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.offer-card--photo .offer-card__body {
  position: relative;            /* sits above the absolutely-placed image */
  width: 100%;
  background: linear-gradient(to top, rgba(15, 24, 17, 0.85), rgba(15, 24, 17, 0));
  padding-top: 3rem;
}

.offer-card--photo h3 {
  color: #FFFFFE;
}

.offer-card--photo p {
  color: #E4E8DF;
}

/* Tablet: two columns, fruits runs tall down the left. */
@media (min-width: 768px) {
  .offer__grid {
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "fruits veg"
      "fruits goods"
      "drinks drinks";
  }

  .offer-card--fruits { grid-area: fruits; }
  .offer-card--veg    { grid-area: veg; }
  .offer-card--goods  { grid-area: goods; }
  .offer-card--drinks { grid-area: drinks; }
}

/* Desktop: three columns, still asymmetric. */
@media (min-width: 1024px) {
  .offer__grid {
    grid-template-columns: 1.3fr 1fr 1fr;
    grid-template-areas:
      "fruits veg    goods"
      "fruits drinks drinks";
  }

  .offer-card {
    min-height: 13rem;
  }
}

/* ------------------------------------------------------------
   7b. Product chalkboard signs: the signature element of the page.
   Each card imitates the little chalkboards propped on crates at
   a Greek laiki market: deep laurel green board, bone lettering,
   the price written big and bright. The dark cards deliberately
   echo the hero scrim and the footer, bookending the light page.
   ------------------------------------------------------------ */
/* THE HOME CHALKBOARD: one big board for the week's produce, like
   the board propped outside the shop door. Each line reads
   "Name .......... price" with the origin written small underneath.
   script.js writes the lines letter by letter (see the chalk rules
   in the Motion section). */
.chalkboard {
  position: relative;
  background: var(--accent-deep);
  border: 1px solid rgba(244, 246, 240, 0.18);
  border-radius: var(--radius);
  padding: clamp(1.5rem, 4vw, 2.5rem) clamp(1.25rem, 4vw, 2.75rem);
  max-width: 760px;              /* a board, not a full-width banner */
  margin-bottom: 2rem;
}

/* The thin inner line drawn just inside the edge, like the chalk
   frame on a real market board. pointer-events: none keeps this
   purely decorative layer from swallowing clicks. */
.chalkboard::before {
  content: "";
  position: absolute;
  inset: 10px;
  border: 1px solid rgba(244, 246, 240, 0.28);
  border-radius: calc(var(--radius) - 4px);
  pointer-events: none;
}

.chalkboard__title {
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 3vw, 1.75rem);
  color: var(--on-accent);
  text-align: center;
  margin: 0 0 1.25rem;
  max-width: none;
}

.chalkboard__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.chalkboard__row {
  padding-block: 0.85rem;
}

.chalkboard__row + .chalkboard__row {
  border-top: 1px solid rgba(244, 246, 240, 0.12);
}

/* One written line: name on the left, price on the right, and a
   dotted chalk leader stretching between them, menu-board style. */
.chalkboard__line {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
}

.chalkboard__name {
  font-size: clamp(1.15rem, 2.5vw, 1.4rem);
  color: var(--on-accent);
  margin: 0;
}

.chalkboard__dots {
  flex: 1;
  border-bottom: 2px dotted rgba(244, 246, 240, 0.35);
  transform: translateY(-4px);   /* rides the text baseline */
}

.chalkboard__price {
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--on-accent);
  white-space: nowrap;           /* the price never breaks in two */
}

.chalkboard__price small {
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--on-accent-muted);
}

.chalkboard__origin {
  margin: 0.15rem 0 0;
  font-size: 0.9rem;
  color: var(--on-accent-muted);
}

/* Skeleton lines shown while products load. */
.chalkboard__row--skeleton {
  min-height: 2.9rem;
  background: rgba(244, 246, 240, 0.07);
  border-radius: var(--radius);
  margin-block: 0.75rem;
}

/* DESKTOP LAYOUT: on wide screens the board grows wide and shallow
   instead of tall and narrow. The lines flow into two columns, read
   left to right, like two lists chalked side by side. The skeleton
   lines inherit the same grid automatically. */
@media (min-width: 1024px) {
  .chalkboard {
    max-width: none;             /* span the whole content column */
    padding: 2rem 3rem 2.25rem;
  }

  .chalkboard__title {
    margin-bottom: 1rem;
  }

  .chalkboard__list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(2.5rem, 5vw, 4.5rem);
  }

  .chalkboard__row {
    padding-block: 0.7rem;
    /* The mobile "line above every row but the first" trick would
       give column two a lonely border at its top, so on the grid we
       switch to bottom borders instead, and clear them from the two
       last cells (the bottom of each column). */
    border-bottom: 1px solid rgba(244, 246, 240, 0.12);
  }

  .chalkboard__row + .chalkboard__row {
    border-top: none;
  }

  .chalkboard__row:nth-last-child(-n+2) {
    border-bottom: none;
  }
}

.tag-grid {
  list-style: none;
  margin: 0 0 2rem;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;    /* mobile: one tag per row */
  gap: 1rem;
}

@media (min-width: 640px) {
  .tag-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .tag-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.tag {
  background: var(--accent-deep);
  /* A faint bone inner line, like the chalk frame drawn around the
     edge of a real market board. */
  border: 1px solid rgba(244, 246, 240, 0.18);
  border-radius: var(--radius);
  padding: 1.2rem 1.35rem;
  transition: transform 0.25s ease;
}

.tag:hover {
  transform: translateY(-2px);
}

.tag__category {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--on-accent-muted);
  margin: 0 0 0.35rem;
}

.tag__name {
  font-size: 1.35rem;
  color: var(--on-accent);
  margin-bottom: 0.5rem;
}

/* The price is the loudest thing on the board, just like the real
   chalk numbers at the market. */
.tag__price {
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--on-accent);
  margin: 0 0 0.2rem;
}

.tag__price small {
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--on-accent-muted);
}

.tag__origin {
  font-size: 0.9rem;
  color: var(--on-accent-muted);
  margin: 0;
}

/* Skeleton state: board-shaped blocks shown while products load,
   in a quieter green so the shimmer reads on top. Matching the
   final dimensions avoids layout jumps (CLS). */
.tag--skeleton {
  min-height: 8.25rem;
  background: #2E4A38;
  border-color: transparent;
}

/* Inline error shown when products.json cannot be fetched. */
.tag-grid__error {
  grid-column: 1 / -1;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem;
  color: var(--ink);
}

.tag-grid__error p {
  margin: 0 0 0.25rem;
}

.tag-grid__error .hint {
  color: var(--muted);
  font-size: 0.9rem;
}


/* ------------------------------------------------------------
   8. GALLERY: the rolling carousel
   HOW THE SEAMLESS LOOP WORKS
   The track holds identical copies of the photo set (script.js
   clones them). JavaScript advances the strip's scrollLeft every
   frame; whenever the position passes one full set width it
   silently subtracts that width, and because the next copy looks
   identical, the jump is invisible. Each set carries padding-right
   equal to its internal gap so the seam between copies matches.

   The strip is a REAL scroll container at all times, which is why
   swiping on touch screens and the mouse wheel both work: they
   all just move scrollLeft. With JavaScript disabled the strip is
   simply a scrollable row and every photo stays reachable.
   ------------------------------------------------------------ */
.gallery__carousel {
  /* The carousel copies the .container metrics (same max-width, same
     side padding: keep these two values in sync with .container!) so
     the photo strip starts exactly where the section heading above it
     starts, in line with the rest of the page. */
  max-width: 1200px;
  margin-inline: auto;
  padding-inline: 1.25rem;
}

.gallery__strip {
  overflow-x: auto;              /* fallback mode: scroll by hand */
  padding-block: 0.5rem;
}

/* Once script.js takes over (wheel steering + autoplay), the
   scrollbar is noise: hide it. The strip still scrolls by wheel,
   by touch and programmatically; only the bar itself disappears. */
.gallery__strip--enhanced {
  scrollbar-width: none;         /* Firefox */
}

.gallery__strip--enhanced::-webkit-scrollbar {
  display: none;                 /* Chrome, Edge, Safari */
}

.gallery__track {
  display: flex;
  width: max-content;            /* the track is as wide as its photos */
}

.gallery__set {
  display: flex;
  gap: 1rem;
  padding-right: 1rem;           /* must equal the gap: seamless seam */
  flex-shrink: 0;
}

.gallery__item {
  padding: 0;
  border: none;
  background: none;
  cursor: zoom-in;
  border-radius: var(--radius);
  overflow: hidden;
  display: block;
  flex: 0 0 auto;
}

/* Every photo gets identical dimensions; object-fit: cover crops
   whatever the source shape is into the same frame. */
.gallery__item img {
  width: 280px;
  height: 210px;
  object-fit: cover;
}

@media (min-width: 768px) {
  .gallery__item img {
    width: 340px;
    height: 250px;
  }
}

/* MOBILE CARD STACK: on phones script.js swaps the film strip for a
   deck of stacked photo cards by adding this class. It is added by
   JavaScript only, so the no-JS fallback keeps the plain scrollable
   row. The strip's flex layout collapses into a single frame; every
   card sits absolutely inside it, and script.js deals out which
   three are visible (top card clean, two peeking below). */
.gallery__carousel--stack .gallery__strip {
  overflow: visible;
}

.gallery__carousel--stack .gallery__track {
  display: block;
  width: auto;
}

.gallery__carousel--stack .gallery__set {
  display: block;
  position: relative;
  padding: 0;
  max-width: 340px;
  margin-inline: auto;
  aspect-ratio: 3 / 4;           /* one portrait frame, like a printed
                                    snapshot: most of the gallery's
                                    photos are portrait, so the crop
                                    stays gentle */
  margin-bottom: 22px;           /* room for the corners of the fanned
                                    cards peeking below the top one */
}

.gallery__carousel--stack .gallery__item {
  position: absolute;
  inset: 0;
  touch-action: pan-y;           /* vertical finger = page scroll,
                                    horizontal finger = card swipe */
  user-select: none;
  -webkit-user-select: none;
  box-shadow: 0 8px 20px rgba(27, 35, 28, 0.16);
  transition: transform 0.3s ease;
}

/* While a finger drags the card it must follow instantly, with no
   easing lagging behind the touch. */
.gallery__carousel--stack .gallery__item.is-dragging {
  transition: none;
}

.gallery__carousel--stack .gallery__item img {
  width: 100%;
  height: 100%;
  pointer-events: none;          /* also stops native image dragging
                                    from hijacking the card swipe */
}

/* The small "3 / 21" position counter under the deck. */
.gallery__counter {
  text-align: center;
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0.75rem 0 0;
  max-width: none;
}

/* LIGHTBOX: fixed overlay above everything. The [hidden] attribute
   keeps it out of the accessibility tree until opened. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-lightbox);
  background: rgba(15, 24, 17, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

.lightbox[hidden] {
  display: none;
}

.lightbox__figure {
  margin: 0;
  max-width: min(90vw, 1000px);
}

.lightbox__img {
  max-width: 100%;
  max-height: 80vh;
  width: auto;
  height: auto;
  margin-inline: auto;
  border-radius: var(--radius);
}

.lightbox__caption {
  color: #E4E8DF;
  text-align: center;
  padding-top: 0.75rem;
  font-size: 0.95rem;
}

.lightbox__close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 44px;
  height: 44px;
  font-size: 1.75rem;
  line-height: 1;
  color: var(--on-accent);
  background: transparent;
  border: 1px solid rgba(244, 246, 240, 0.4);
  border-radius: var(--radius);
  cursor: pointer;
}

.lightbox__close:hover {
  background: rgba(244, 246, 240, 0.15);
}

/* Set by script.js while the lightbox is open, so the page
   behind the overlay stops scrolling. */
body.no-scroll {
  overflow: hidden;
}


/* ------------------------------------------------------------
   9. LOCATION & CONTACT
   Mobile: stacked. Desktop: map/address beside the form.
   ------------------------------------------------------------ */
.visit__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 3rem;
}

.visit__where h3,
.visit__form-wrap h3 {
  font-size: 1.4rem;
}

.visit__map {
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line);
  margin-block: 1.25rem;
}

.visit__map iframe {
  display: block;
  width: 100%;
  height: 320px;
  border: 0;
}

.visit__address {
  font-style: normal;            /* <address> defaults to italic */
  margin-bottom: 1.5rem;
}

.visit__address p {
  margin-bottom: 0.25rem;
}

.visit__call {
  width: 100%;                   /* thumb-sized target on phones */
}

@media (min-width: 768px) {
  .visit__grid {
    grid-template-columns: 1fr 1fr;
    gap: 4rem;
  }

  .visit__call {
    width: auto;
  }
}

/* FORM: labels above inputs, errors below. Field text is 1rem+
   so iOS does not auto-zoom when a field receives focus. */
.contact-form {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-bottom: 1.25rem;
}

.form-field label {
  font-weight: 600;
  font-size: 0.95rem;
}

.form-field input,
.form-field textarea {
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--ink);
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.7rem 0.9rem;
}

.form-field input:focus-visible,
.form-field textarea:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.form-field textarea {
  resize: vertical;
}

/* Error text: dark red that passes AA contrast on the bone canvas. */
.form-error {
  color: #8C2F26;
  font-size: 0.9rem;
  margin: 0;
  min-height: 1.2em;             /* reserves the line: no jump on error */
}

input[aria-invalid="true"],
textarea[aria-invalid="true"] {
  border-color: #8C2F26;
}

.form-status {
  margin: 1rem 0 0;
  font-weight: 600;
  color: var(--accent-deep);
}


/* ------------------------------------------------------------
   10. FOOTER
   The one dark block of the page: deep laurel green anchors the
   brand at the end, mirroring the dark hero at the start.
   ------------------------------------------------------------ */
.site-footer {
  background: var(--accent-deep);
  color: var(--on-accent);
  padding-block: 3rem 1.5rem;
  margin-top: var(--section-gap);
}

.site-footer__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  padding-bottom: 2rem;
}

.site-footer__wordmark {
  font-family: var(--font-display);
  font-size: 1.25rem;
  margin-bottom: 0.5rem;
}

.site-footer p {
  color: var(--on-accent-muted);
}

.site-footer a {
  color: var(--on-accent);
}

.site-footer__nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-footer__nav a {
  display: inline-block;
  padding: 0.3rem 0;
  text-decoration: none;
}

.site-footer__nav a:hover {
  text-decoration: underline;
}

.site-footer__contact {
  font-style: normal;
}

.site-footer__contact p {
  margin-bottom: 0.3rem;
}

.site-footer__legal {
  border-top: 1px solid rgba(244, 246, 240, 0.2);
  padding-top: 1.25rem;
}

.site-footer__legal p {
  font-size: 0.9rem;
  margin: 0;
}

@media (min-width: 768px) {
  .site-footer__grid {
    grid-template-columns: 2fr 1fr 1fr;
    gap: 3rem;
  }
}


/* ------------------------------------------------------------
   11. MOTION
   Scroll reveals are gated twice:
   a) html.js  : they only arm when JavaScript actually loaded,
      so users without JS never face invisible content;
   b) prefers-reduced-motion: no-preference : users who asked
      their OS for less motion get everything instantly visible.
   Only transform and opacity are animated: both run on the GPU
   compositor, so scrolling stays smooth on cheap phones.
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;     /* the hero CTA glides to the products */
  }

  html.js .reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 0.6s ease, transform 0.6s ease;
  }

  html.js .reveal.is-visible {
    opacity: 1;
    transform: translateY(0);
  }

  /* Loading shimmer on the skeleton boards: a soft bone glint,
     subtle enough not to glare against the dark green. */
  .tag--skeleton {
    background-image: linear-gradient(100deg,
      rgba(244, 246, 240, 0) 30%,
      rgba(244, 246, 240, 0.16) 50%,
      rgba(244, 246, 240, 0) 70%);
    background-size: 200% 100%;
    animation: shimmer 1.4s infinite;
  }

  @keyframes shimmer {
    from { background-position: 150% 0; }
    to   { background-position: -50% 0; }
  }

  /* The chalkboard skeleton lines get the same shimmer, tinted bone
     so it reads softly against the dark green board. */
  .chalkboard__row--skeleton {
    background-image: linear-gradient(100deg,
      rgba(244, 246, 240, 0) 30%,
      rgba(244, 246, 240, 0.1) 50%,
      rgba(244, 246, 240, 0) 70%);
    background-size: 200% 100%;
    animation: shimmer 1.4s infinite;
  }

  /* CHALK WRITING on the home board. script.js wraps each letter in
     a .chalk-char span carrying its queue position in --char-index;
     the letters start invisible and fade in one after another, top
     line to bottom line, once the board scrolls into view
     (.chalkboard--writing). Only opacity animates: the spans stay
     inline so words wrap normally, and the compositor does the work.
     Under reduced motion the spans are never built at all. */
  .chalkboard .chalk-char {
    opacity: 0;
  }

  .chalkboard--writing .chalk-char {
    animation: chalk-write 0.15s ease-out both;
    /* 18ms per letter: the speed of the writing hand. */
    animation-delay: calc(var(--char-index, 0) * 18ms);
  }

  @keyframes chalk-write {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
}

/* Belt and braces: if the user asks for reduced motion, kill every
   transition and animation declared anywhere above. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}


/* ------------------------------------------------------------
   12. CATALOGUE PAGE (products.html)
   The full product list with search and category filters.
   The product tags themselves reuse the .tag styles from
   section 7b: same component, different page.
   ------------------------------------------------------------ */

/* On subpages the wordmark is a link home; it should look identical
   to the plain-text wordmark on the home page. */
.wordmark a {
  color: inherit;
  text-decoration: none;
}

.catalog__back {
  display: inline-block;
  font-weight: 600;
  margin-bottom: 1.5rem;
}

/* CONTROLS: stacked on mobile; from tablet width the search box and
   the filter buttons share one row. */
.catalog__controls {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  margin-block: 1.5rem 2.5rem;
}

.catalog__search label {
  display: block;
  font-weight: 600;
  font-size: 0.95rem;
  margin-bottom: 0.4rem;
}

.catalog__search input {
  width: 100%;
  font-family: var(--font-body);
  font-size: 1rem;               /* 1rem+ stops iOS zooming on focus */
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.7rem 0.9rem;
}

.catalog__search input:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.filter-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.filter-button {
  font-family: var(--font-body);
  font-size: 0.95rem;
  font-weight: 600;
  padding: 0.55rem 1.1rem;
  min-height: 44px;              /* full touch target, like every button */
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

.filter-button:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* The pressed state comes straight from the aria-pressed attribute:
   styling the accessibility state means the visual state can never
   drift out of sync with what screen readers announce. */
.filter-button[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.filter-button[aria-pressed="true"]:hover {
  background: var(--accent-deep);
  color: var(--on-accent);
}

@media (min-width: 768px) {
  .catalog__controls {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: 2rem;
  }

  .catalog__search {
    flex: 1;
    max-width: 420px;
  }
}

/* Empty state: shown when no product survives search + filter.
   It spans the whole grid and explains how to recover. */
.catalog-empty {
  grid-column: 1 / -1;
  background: var(--surface);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  padding: 2rem 1.5rem;
  text-align: center;
}

.catalog-empty p {
  margin: 0 0 0.25rem;
  max-width: none;
}

.catalog-empty .hint {
  color: var(--muted);
  font-size: 0.95rem;
}
