/* ============================================================
   Shams Convention — Components
   Depends on: tokens.css, base.css, layout.css
   ============================================================ */

/* ---------- Glass -----------------------------------------
   The design asks for backdrop-filter on nearly every surface. That is
   a real INP hazard: each blurred layer is a separate compositor pass,
   and this audience is largely on mid-range Android. So the expensive
   blur is opt-in via .glass--blur (header, hero, one feature card) and
   everything else gets a flat translucent fill that looks the same
   over the marble field but costs nothing.                        */

.glass {
  background: var(--glass-fill);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-ambient), var(--bevel);
}

@media (min-width: 768px) {
  .glass--blur {
    background: rgba(255, 255, 255, .38);
    -webkit-backdrop-filter: blur(var(--blur-glass)) saturate(140%);
    backdrop-filter: blur(var(--blur-glass)) saturate(140%);
  }
}

.glass--solid { background: var(--glass-fill-strong); }

/* ---------- Buttons --------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  min-height: 44px;
  padding: .75rem 1.5rem;
  border-radius: var(--r-full);
  font-size: var(--fs-small);
  font-weight: var(--fw-semibold);
  letter-spacing: .06em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease),
              box-shadow var(--dur) var(--ease);
}

.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn svg { width: 18px; height: 18px; flex-shrink: 0; }

.btn--primary {
  background: var(--primary);
  color: var(--on-primary);
  border: 1px solid var(--primary);
  box-shadow: 0 4px 16px rgba(85, 95, 78, .22);
}

.btn--primary:hover {
  background: var(--primary-container);
  box-shadow: 0 8px 24px rgba(85, 95, 78, .28);
}

.btn--ghost {
  background: transparent;
  color: var(--bronze-deep);
  border: 1px solid var(--bronze);
}

.btn--ghost:hover { background: var(--bronze-wash); border-color: var(--bronze-deep); }

.btn--wa {
  background: var(--wa);
  color: #fff;
  border: 1px solid var(--wa);
  box-shadow: 0 4px 16px var(--wa-shadow);
}

.btn--wa:hover {
  background: var(--wa-hover);
  border-color: var(--wa-hover);
  box-shadow: 0 8px 24px var(--wa-shadow);
}

.btn--block { width: 100%; }
.btn--sm { min-height: 38px; padding: .5rem 1rem; font-size: var(--fs-label); }

/* ---------- Draft notice ----------------------------------
   A loud, deliberately un-pretty banner for content that is a working
   draft rather than confirmed fact. Delete the element to remove it. */

.notice {
  display: flex;
  gap: var(--s-2);
  align-items: flex-start;
  padding: var(--s-2) var(--s-3);
  margin-bottom: var(--s-5);
  background: #FFF3CD;
  border: 1px dashed var(--bronze-deep);
  border-radius: var(--r);
  color: #6B4E00;
  font-size: var(--fs-small);
}

.notice svg { width: 20px; height: 20px; flex-shrink: 0; margin-top: 2px; }
.notice strong { color: #573F00; }

/* ---------- Chip ------------------------------------------ */

.chip {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .375rem .875rem;
  border-radius: var(--r-full);
  background: var(--bronze-wash);
  border: 1px solid rgba(179, 126, 95, .28);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--bronze-deep);
  width: fit-content;
}

.chip--sage {
  background: rgba(85, 95, 78, .10);
  border-color: rgba(85, 95, 78, .26);
  color: var(--primary);
}

/* ---------- Card ------------------------------------------ */

.card {
  padding: var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  transition: transform var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}

.card--hover:hover {
  transform: translateY(-4px);
  border-color: rgba(179, 126, 95, .45);
  box-shadow: var(--shadow-lift), var(--bevel);
}

.card__icon {
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  border-radius: var(--r-full);
  background: var(--bronze-wash);
  color: var(--bronze-deep);
  margin-bottom: var(--s-1);
}

.card__icon svg { width: 22px; height: 22px; }

/* ---------- Feature list ---------------------------------- */

.ticks { display: grid; gap: .875rem; }

.ticks li {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: .75rem;
  align-items: start;
  color: var(--on-surface-variant);
}

.ticks svg {
  width: 20px; height: 20px;
  color: var(--primary);
  margin-top: 2px;
}

/* Label/value specification list. */
.spec-list > div {
  display: grid;
  grid-template-columns: minmax(9rem, 40%) 1fr;
  gap: var(--s-2);
  padding: .75rem 0;
  border-bottom: 1px solid var(--outline-variant);
}

.spec-list > div:last-child { border-bottom: 0; }

.spec-list dt {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--on-surface-variant);
  align-self: center;
}

.spec-list dd { font-weight: var(--fw-semibold); color: var(--on-surface); }

/* Hairline separated list with a bronze indicator on hover. */
.rule-list li {
  position: relative;
  padding: .875rem 0 .875rem var(--s-3);
  border-bottom: 1px solid var(--outline-variant);
  transition: padding-left var(--dur) var(--ease);
}

.rule-list li::before {
  content: '';
  position: absolute;
  left: 0; top: 50%;
  width: 8px; height: 1px;
  background: var(--bronze);
  transform: translateY(-50%) scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease-out);
}

.rule-list li:hover::before { transform: translateY(-50%) scaleX(1); }
.rule-list li:last-child { border-bottom: 0; }

/* ---------- Accordion -------------------------------------
   Animates grid-template-rows 0fr -> 1fr, which resolves to the panel's
   true height. Stitch used max-height:500px, which snaps shut on any
   answer longer than that.

   Without JS every panel stays open, so the content is readable and
   crawlable. The `.js` class (set inline in <head>) opts into the
   collapsed behaviour only once we know scripting is on.          */

.accordion { display: grid; gap: var(--s-2); }

.accordion__item { overflow: hidden; }

.accordion__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
  padding: var(--s-3);
  text-align: left;
  font-size: var(--fs-body-lg);
  font-weight: var(--fw-semibold);
  color: var(--on-surface);
  transition: color var(--dur-fast) var(--ease);
}

.accordion__trigger:hover { color: var(--bronze-deep); }

.accordion__chevron {
  flex-shrink: 0;
  width: 20px; height: 20px;
  color: var(--bronze);
  transition: transform var(--dur) var(--ease);
}

.accordion__trigger[aria-expanded='true'] .accordion__chevron { transform: rotate(180deg); }

/* The clipping element must carry NO padding of its own. overflow:hidden
   clips at the padding box, so padding here would leak that many pixels
   of the answer while the panel is collapsed. min-height:0 is also
   required — grid items default to min-height:auto and refuse to shrink
   below their content. Spacing therefore lives on .accordion__body. */
.accordion__panel > div {
  overflow: hidden;
  min-height: 0;
}

.accordion__body {
  padding: 0 var(--s-3) var(--s-3);
  color: var(--on-surface-variant);
}

.js .accordion__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur) var(--ease);
}

.js .accordion__item.is-open .accordion__panel { grid-template-rows: 1fr; }

/* ---------- Form fields ----------------------------------- */

.form { display: grid; gap: var(--s-3); }

.field { display: grid; gap: .375rem; }

.field > label {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--on-surface-variant);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: .75rem .875rem;
  background: rgba(255, 255, 255, .6);
  border: 1px solid var(--outline-variant);
  border-radius: var(--r);
  color: var(--on-surface);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}

.field textarea { resize: vertical; min-height: 120px; }

.field input::placeholder,
.field textarea::placeholder { color: var(--outline); }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  background: var(--surface-lowest);
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(85, 95, 78, .14);
}

.field--invalid input,
.field--invalid select,
.field--invalid textarea { border-color: #BA1A1A; }

.field__error {
  font-size: var(--fs-label);
  color: #93000A;
  min-height: 1em;
}

.form__note { font-size: var(--fs-small); color: var(--on-surface-variant); }

.form__status {
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r);
  background: rgba(85, 95, 78, .10);
  border: 1px solid rgba(85, 95, 78, .26);
  color: var(--primary);
  font-size: var(--fs-small);
}

.form__status[hidden] { display: none; }

/* ---------- Lightbox --------------------------------------
   Native <dialog> + showModal() gives focus trapping, Esc-to-close and
   an inert background for free — a lot less JS to get right.      */

.lightbox {
  /* NOT redundant, do not delete. A native dialog is centred solely by the
     UA's `inset: 0` + `width/height: fit-content` + `margin: auto`. base.css's
     `* { margin: 0 }` is author-origin, so it outranks that `margin: auto`, and
     the definite width below then over-constrains the box against inset:0 —
     the end insets get dropped and it pins to the top-left corner. Restating
     `margin: auto` is what puts it back in the middle.

     `height` may be a length but must never be `auto`. With auto the block
     axis falls through to CSS 2.1 §10.6.4's six rules, which zero the margins
     and stretch the box to the full viewport; a definite height keeps the
     equal-margins branch, so this still centres. */
  margin: auto;
  width: min(96vw, 1200px);
  max-width: none;
  /* Fixed, so the panel is identical for all six photos and nothing shifts
     when you page through. Sized to the images it swung 777 -> 557 on desktop
     and 614 -> 252 on mobile, where the widest shot collapsed to a sliver. */
  height: min(90vh, 860px);
  padding: 0;
  border: 1px solid var(--glass-border);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lift);
  overflow: hidden;

  /* A modal dialog lives in the top layer and flips `display`, so a plain
     transition has nothing to run against. @starting-style supplies the
     pre-open style, and allow-discrete holds `display`/`overlay` open long
     enough for the exit to play. Engines without either simply pop the panel
     in — the same instant behaviour this had before. */
  opacity: 0;
  transform: translateY(8px) scale(.97);
  transition: opacity var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out),
              overlay var(--dur) allow-discrete,
              display var(--dur) allow-discrete;
}

/* Flex column so the photo absorbs whatever height the caption bar does not
   want, which is what keeps the bar on screen at every viewport now that the
   panel height is fixed. Scoped to [open] deliberately: an author `display`
   on the bare class would outrank the UA's `dialog:not([open]) { display:
   none }` and leave the closed dialog visible on the page. */
.lightbox[open] {
  display: flex;
  flex-direction: column;
  opacity: 1;
  transform: none;
}

/* Must sit after the [open] rule above — it is the style the entry
   transition starts from. */
@starting-style {
  .lightbox[open] { opacity: 0; transform: translateY(8px) scale(.97); }
}

.lightbox::backdrop {
  background: rgba(27, 28, 28, .78);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity var(--dur) var(--ease),
              overlay var(--dur) allow-discrete,
              display var(--dur) allow-discrete;
}

.lightbox[open]::backdrop { opacity: 1; }

@starting-style {
  .lightbox[open]::backdrop { opacity: 0; }
}

/* Fades in on every swap. show() in main.js drops .is-in, forces a reflow and
   re-adds it, which keeps the duration here in CSS rather than in a JS timer.

   The `transition: none` on the base state is what makes that work. A
   transition reads its timing from the *after-change* style, so with the
   transition declared here the class removal would start its own 580ms fade
   to zero — the reflow would still measure ~1, and re-adding the class would
   animate 1 -> 1, i.e. nothing. Un-transitioned, removal snaps to 0 and only
   the re-add animates. */
.lightbox img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: var(--surface-high);
  opacity: 0;
  transition: none;
}

.lightbox img.is-in {
  opacity: 1;
  transition: opacity var(--dur) var(--ease-out);
}

/* Positioning context for the overlaid controls, and the flex sizing the img
   used to carry — min-height:0 is still load-bearing here, because a flex
   item will not shrink below its content's intrinsic height without it and a
   tall photo would push the bar out through the bottom of the panel. */
.lightbox__stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  /* Horizontal drags belong to the swipe handler in main.js; vertical pan and
     pinch-zoom stay with the browser. Without this the UA can claim the
     gesture first and the swipe never lands. */
  touch-action: pan-y pinch-zoom;
}

.lightbox__bar {
  flex-shrink: 0;          /* the photo gives up height, never the caption */
  padding: var(--s-2) var(--s-3);
  border-top: 1px solid var(--outline-variant);
}

.lightbox__caption { font-size: var(--fs-small); color: var(--on-surface-variant); }

/* Dark controls with white glyphs, not the site's usual bronze-on-light. The
   letterbox behind them is always --surface-high (#EAE7E7), so a light scrim
   would have almost nothing to contrast against; --glass-border is white at
   40% and would be invisible here for the same reason. The ink is the
   backdrop's own rgba(27,28,28), so they read as part of the viewer. At 62%
   over the palest possible backing the glyph still measures ~5:1, and it only
   improves over darker photos. */
.lightbox__btn {
  position: absolute;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border-radius: var(--r-full);
  border: 0;
  background: rgba(27, 28, 28, .62);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  color: #fff;
  transition: background-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}

/* calc, not translateY(-50%), so :active keeps `transform` to itself — and
   not a negative margin, which base.css's `* { margin: 0 }` makes a fragile
   thing to lean on. */
.lightbox__btn--prev  { left:  var(--s-2); top: calc(50% - 22px); }
.lightbox__btn--next  { right: var(--s-2); top: calc(50% - 22px); }
.lightbox__btn--close { right: var(--s-2); top: var(--s-2); }

.lightbox__btn:hover  { background: rgba(27, 28, 28, .82); }
.lightbox__btn:active { transform: scale(.94); }

/* The global ring is bronze on a 3px outset, which would sit on the photo and
   could land bronze-on-bronze. Inset white keeps it on the button's own dark
   scrim, so it is legible whatever the photo underneath is doing. */
.lightbox__btn:focus-visible { outline: 2px solid #fff; outline-offset: -5px; }

.lightbox__btn svg { width: 20px; height: 20px; }

/* ---------- Scroll reveal ---------------------------------
   Gated on `.js` so that with scripting off nothing is ever hidden —
   which also means a crawler always sees fully-rendered content.  */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
}

.js [data-reveal].is-visible { opacity: 1; transform: none; }

/* Stagger children of a group without touching inline styles. */
[data-reveal-group] > *:nth-child(1) { transition-delay: 0ms; }
[data-reveal-group] > *:nth-child(2) { transition-delay: 110ms; }
[data-reveal-group] > *:nth-child(3) { transition-delay: 220ms; }
[data-reveal-group] > *:nth-child(4) { transition-delay: 330ms; }
[data-reveal-group] > *:nth-child(5) { transition-delay: 440ms; }
[data-reveal-group] > *:nth-child(6) { transition-delay: 550ms; }

/* ---------- Media frame ----------------------------------- */

.frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xl);
  background: var(--surface-high);
}

.frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}

.frame--zoom:hover img { transform: scale(1.05); }

/* A frame matted inside a glass panel, like a mounted photograph. */
.framed { padding: 10px; border-radius: var(--r-xl); overflow: hidden; }
.framed img { border-radius: var(--r-lg); }

.frame__caption {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  padding: var(--s-4) var(--s-3) var(--s-3);
  background: linear-gradient(to top, rgba(27, 28, 28, .78), transparent);
  color: #fff;
}

/* .frame__title is the <span> form, used where the frame is a <button>
   (a gallery tile) and a heading element would be invalid content. */
.frame__caption h3,
.frame__caption .frame__title {
  display: block;
  font-size: var(--fs-body-lg);
  font-weight: var(--fw-semibold);
}

.frame__caption p,
.frame__caption .frame__sub { display: block; font-size: var(--fs-small); opacity: .9; }
