/* =====================================================================
   UBORA — the day layer.

   The fixed room behind the page, the fallback plate stack, and the CSS
   tokens the engine writes each frame. Everything here has a sane value
   before any JavaScript runs, so the page is complete without it.
   ===================================================================== */

:root {
  /* Defaults are the late-morning frame — the same moment the reduced-motion
     path pins to, and a safe ground for any page that never boots the engine. */
  --u-ground: 228 216 196;
  --u-panel: 246 242 234;
  --u-glow: 252 250 242;
  --u-sun: 255 246 230;

  --u-shadow-x: -1.6px;
  --u-shadow-y: 2.4px;
  --u-shadow-blur: 22px;
  --u-shadow-alpha: 0.1;
  --u-sun-angle: -8deg;
  --u-sun-elev: 62deg;

  /* Brand constants. These do not move with the hour. */
  --u-clay: #8b5e3c;
  --u-clay-deep: #6f4a2e;
  --u-anchor: #1c1a17;
  --u-espresso: #4a3f35;

  /* One shadow, composed from the sun's actual position, so every lifted
     surface on the page agrees with the rendered room behind it. */
  --u-cast: var(--u-shadow-x) var(--u-shadow-y) var(--u-shadow-blur)
    rgb(74 55 38 / var(--u-shadow-alpha));
}

/* ---------------------------------------------------------------------
   The mount
   --------------------------------------------------------------------- */

.ubora-day {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background-color: rgb(var(--u-ground));
  /* Never intercepts a click; the page scrolls straight over it. */
  pointer-events: none;
  contain: strict;
}

.ubora-day__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 900ms cubic-bezier(0.16, 1, 0.3, 1);
}

.ubora-day.is-live .ubora-day__canvas {
  opacity: 1;
}

/* Plate stack. The first plate is the LCP image and is never lazy — it paints
   before anything else on the page and the canvas dissolves in over it. */
.ubora-day__plate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--u-plate-focal, 50% 50%);
  will-change: opacity;
}

.ubora-day__plate:not(:first-child) {
  opacity: 0;
}

/* Once the renderer is live the photographs have done their job. */
.ubora-day.is-live .ubora-day__plate:not([data-day-poster]) {
  display: none;
}

.ubora-day__tint {
  position: absolute;
  inset: 0;
  opacity: 0;
  mix-blend-mode: soft-light;
  pointer-events: none;
}

.ubora-day.is-live .ubora-day__tint {
  display: none;
}

/* ---------------------------------------------------------------------
   Page content rides above the room
   --------------------------------------------------------------------- */

.ubora-has-day .ubora-day-content {
  position: relative;
  z-index: 1;
}

/* ---------------------------------------------------------------------
   The clock
   ---------------------------------------------------------------------
   The mechanic is stated, not hidden. A thin clay arc with the sun's real
   elevation on it, and the hour it is inside the room.
   --------------------------------------------------------------------- */

.ubora-clock {
  position: fixed;
  right: clamp(1rem, 3vw, 2.5rem);
  bottom: clamp(1rem, 3vw, 2.5rem);
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.85rem 0.5rem 0.6rem;
  border-radius: 999px;
  background: rgb(var(--u-panel) / 0.72);
  backdrop-filter: blur(10px);
  box-shadow: var(--u-cast);
  font-family: "Outfit", -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  color: var(--u-espresso);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

.ubora-clock__dial {
  position: relative;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid rgb(139 94 60 / 0.3);
}

/* The dot rides the dial at the sun's real elevation and azimuth. */
.ubora-clock__dot {
  position: absolute;
  inset: 0;
  transform: rotate(calc(var(--u-sun-angle) * -1));
}

.ubora-clock__dot::after {
  content: "";
  position: absolute;
  top: 1px;
  left: 50%;
  width: 6px;
  height: 6px;
  margin-left: -3px;
  border-radius: 50%;
  background: rgb(var(--u-sun));
  box-shadow: 0 0 8px 2px rgb(var(--u-glow) / 0.7);
}

@media (prefers-reduced-motion: reduce) {
  .ubora-clock {
    display: none;
  }
}

/* ---------------------------------------------------------------------
   Surfaces that live in the room
   ---------------------------------------------------------------------
   Anything using .u-lift casts the same shadow the sun is casting in the
   render. That agreement is the whole reason the page reads as one place
   rather than a canvas with a website sitting on top of it.
   --------------------------------------------------------------------- */

.u-lift {
  box-shadow: var(--u-cast);
}

.u-panel {
  background: rgb(var(--u-panel) / 0.9);
  box-shadow: var(--u-cast);
}

/* A scrim for copy that lands on plaster. Sand-to-transparent at low opacity,
   never a switch to white type — the direction is explicit that overlaid type
   stays Anchor on light plaster. */
.u-scrim {
  position: relative;
}

.u-scrim::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    var(--u-scrim-dir, 105deg),
    rgb(var(--u-ground) / 0.62) 0%,
    rgb(var(--u-ground) / 0.28) 42%,
    rgb(var(--u-ground) / 0) 72%
  );
  pointer-events: none;
}

.u-scrim > * {
  position: relative;
}

/* ---------------------------------------------------------------------
   Copy over the room
   ---------------------------------------------------------------------
   Where the product sits changes with the variant, so one column rule
   cannot work. Measured off the composited canvas:

     wide / regular   product mass right of ~58%, centred ~70-76%
     portrait         product centred ~43%, so nothing fits beside it

   The engine writes the live variant to <html data-day-variant>, and these
   rules put the type where the plaster actually is. This is the direction's
   own [COPY-SPACE] rule expressed in CSS: "a large clean unbroken area of
   plaster wall ... reserved for overlaid text".
   --------------------------------------------------------------------- */

:root .wp-block-group.u-copy,
.u-copy {
  position: relative;
  z-index: 1;
  /* Never wider than the clean half of the frame, and never wider than a
     comfortable measure.

     The :root .wp-block-group prefix is load-bearing, not decoration. The hero
     group is align="wide", and WordPress emits .wp-block-group.alignwide at
     (0,2,0) which outranks a bare .u-copy at (0,1,0): the copy column silently
     took the 1240px wide-size and the measure never applied. It only became
     visible once the plate gave the column a background. */
  max-width: min(var(--u-copy-max, 46ch), var(--u-copy-span, 46%));

  /* The plate. Fully transparent through the bright morning, so the room shows
     through untouched and this costs nothing; it fades in across the darker
     half of the day, where a feathered scrim cannot reach AA without erasing
     the frame behind it. --u-panel is a near-parchment that holds Anchor body
     text at better than 13:1 at every hour of the ramp.

     The padding is unconditional rather than tied to the plate opacity: making
     it appear only at dusk would shift the copy sideways as the page scrolls. */
  padding: clamp(0.9rem, 2.2vw, 1.75rem) clamp(1rem, 2.6vw, 2rem);
  border-radius: 14px;
  background: rgb(var(--u-panel) / var(--u-copy-plate, 0));
  transition: background 120ms linear;
}

/* Clay is the brand accent for eyebrows, and on a solid parchment panel it is
   fine at 4.78:1. Over the day layer it is not: composited against the real
   frames it lands between 3.2:1 and 3.6:1 at every hour, and unlike Steel it
   cannot be rescued by a heavier plate. Clay Deep is already in the palette and
   holds 4.51:1 at the worst beat, so the accent is remapped for overlaid copy
   only. Anything sitting on a real panel keeps Clay untouched. */
/* Over the day canvas, every ink is Anchor.

   Measured against the real frames, Anchor holds 4.8:1 at its worst beat while
   Steel manages 2.3:1, Clay 3.2:1 and Clay Deep 2.2:1 - and no plate opacity
   rescues them without hiding the room. Hierarchy here comes from size, weight
   and letter-spacing rather than from colour.

   Scoped to text elements rather than the container on purpose: WordPress emits
   .has-clay-color with !important, so the only way through is to remap the
   custom property it reads. Doing that on the container would also repaint the
   clay CTA button, which sits on its own solid fill and is perfectly legible.
   Buttons and links are not in this selector, so they keep the real accent. */
.u-copy :is(h1, h2, h3, h4, h5, h6, p, li, blockquote, span),
.u-plate :is(h1, h2, h3, h4, h5, h6, p, li, blockquote, span) {
  --wp--preset--color--clay: #1C1A17;
  --wp--preset--color--steel: #1C1A17;
  --wp--preset--color--concrete: #1C1A17;
  --wp--preset--color--silver: #1C1A17;
  --wp--preset--color--slate: #1C1A17;
  --wp--preset--color--espresso: #1C1A17;
}

/* .u-plate is .u-copy's centred sibling. The reveal and the close are centred
   bands, not left columns, so they need the same dusk plate without u-copy's
   46% left-hand measure. */
/* `:root .wp-block-group` earns its keep: this class sits on an alignfull
   section, and `.has-global-padding > .alignfull` (0,2,0) sets a negative
   margin-left to break the section out of the page gutter. A bare `.u-plate`
   (0,1,0) lost that fight and the plate stayed pinned to the left edge. */
:root .wp-block-group.u-plate,
.u-plate {
  position: relative;
  z-index: 1;
  /* Bounded and centred. This class sits on an alignfull section, so without a
     max-width the plate spanned the whole viewport and washed the entire frame
     rather than backing the copy. */
  max-width: min(94vw, 960px);
  margin-inline: auto;
  padding: clamp(1.4rem, 3vw, 2.6rem) clamp(1.2rem, 3vw, 2.4rem);
  border-radius: 16px;
  background: rgb(var(--u-panel) / var(--u-copy-plate, 0));
  transition: background 120ms linear;
}

/* The homepage filter pills sit directly on the nine-bottle lineup, and their
   default fill is transparent, so bottle labels read straight through the
   inactive chips. Over the day layer they get a real surface. */
/* :not(.is-active) is load-bearing. Without it this (0,3,0) rule repainted the
   selected pill's background light while .ubora-chip.is-active (0,2,0) kept
   setting parchment TEXT, which is parchment on parchment at 1.01:1 and made
   the active label disappear entirely. The selected pill keeps its dark fill. */
:root[data-day-variant] .ubora-beat .ubora-chip:not(.is-active) {
  background: rgb(var(--u-panel, 246 242 234) / 0.94);
  border-color: rgb(28 26 23 / 0.14);
}

/* The why beat's three captions sit on the room, not inside .u-copy, so they
   missed both the Anchor remap and any plate: Steel body text on a sunlit arch
   measures well under the AA floor. They get the same treatment the copy layer
   gets, scoped to the beat so nothing else moves. */
:root[data-day-variant] .ubora-whyoil__body {
  background: rgb(var(--u-panel, 246 242 234) / 0.92);
  border-radius: 12px;
  padding: clamp(0.9rem, 2vw, 1.3rem) clamp(1rem, 2.2vw, 1.4rem);
}
:root[data-day-variant] .ubora-whyoil__body :is(h3, h4, p, span, li) {
  --wp--preset--color--steel: #1C1A17;
  --wp--preset--color--clay: #1C1A17;
  --wp--preset--color--concrete: #1C1A17;
  color: #1C1A17;
}

/* The close beat now sits over the nine-on-a-beam, which is the busiest frame
   on the site. The hour-driven plate only reaches 0.34 at t=1, which is plenty
   over an empty ledge and not nearly enough over nine bottles, so every panel
   in this beat gets the fixed surface instead. (0,4,0) to clear the
   `:root .wp-block-group.u-plate` rule above. */
:root[data-day-variant] .ubora-beat--close .u-plate,
:root[data-day-variant] .ubora-beat--close .u-copy {
  background: rgb(var(--u-panel, 246 242 234) / 0.92);
  border-radius: 14px;
}

/* Some copy sits over a frame that is busy at EVERY hour, not just the dark
   ones: the collection beat centres its heading over the nine-bottle lineup,
   and the proof beat centres over the hero frame where the bottle stands. The
   hour-driven plate is deliberately 0 through the bright half, so those two
   would have no backing at all. They get a fixed panel instead. */
/* (0,4,0) on purpose. The hour-driven `.u-plate` rule above is written
   `:root .wp-block-group.u-plate` to get past WordPress's alignfull margins,
   which also made it outrank a plain `.u-plate--solid` - the always-on panel
   silently lost to a background of `alpha 0` and the copy sat bare on the
   photograph. Matching the shape of that selector and adding one class keeps
   this predictably on top. */
:root .wp-block-group.u-plate.u-plate--solid,
.u-plate--solid {
  background: rgb(var(--u-panel, 246 242 234) / 0.92);
}

/* A plate at dusk is a deliberate surface, so it gets the same soft lift the
   other panels on the site have. Kept off entirely while it is transparent,
   because a shadow with no visible box casts onto bare plaster. */
@supports (color: rgb(0 0 0 / 0)) {
  .u-copy,
  .u-plate {
    box-shadow: 0 var(--u-shadow-y, 2px) calc(var(--u-shadow-blur, 18px) * 1.4)
      rgb(28 26 23 / calc(var(--u-copy-plate, 0) * 0.14));
  }
}

/* Landscape: the product owns the right, so type stays left of it. The gutter
   is generous because the bottle's cast shadow reaches further left than the
   bottle does. */
:root[data-day-variant='wide'],
:root[data-day-variant='regular'] {
  --u-copy-span: 46%;
  --u-copy-align: start;
}

:root[data-day-variant='wide'] .u-copy,
:root[data-day-variant='regular'] .u-copy {
  /* !important here is required, not laziness. WordPress's constrained layout
     emits `.is-layout-constrained > :where(...) { margin-left: auto !important }`,
     and !important outranks specificity however the selector is written. Without
     this the copy column centres itself over the middle of the frame, the clean
     left half goes unused, and the type sits on top of the product. */
  /* Pulled back by exactly the plate's own inline padding, so the TEXT lands on
     the shared measure and the plate bleeds outward around it. Aligning the box
     instead left the copy visibly indented from the card grids at every bright
     hour, because the plate it was padding for is transparent there. */
  margin-left: calc(-1 * clamp(1rem, 2.6vw, 2rem)) !important;
  margin-right: auto !important;
}

/* Portrait: the product is centred and full height, so beside it is not an
   option. Type goes above, in the upper third where the plaster is cleanest,
   and the section anchors to the top rather than the middle. */
:root[data-day-variant='portrait'] {
  --u-copy-span: 100%;
  --u-copy-align: start;
}

:root[data-day-variant='portrait'] .u-copy {
  max-width: 100%;
  text-align: left;
}

:root[data-day-variant='portrait'] .u-copy-section {
  align-content: start;
  padding-top: clamp(2rem, 9vh, 6rem);
}

/* ONE MEASURE FOR THE WHOLE DAY.
   -------------------------------------------------------------------
   Every beat used to carry a 16px inline padding while its alignwide
   children took the 1240px centred wide-size. Two systems in one section, so
   the hero copy started at x=48, the why heading at x=64, and the card grids
   at x=261: headings hugged the viewport edge while the content they
   introduced sat 200px further in, and the plates each found their own edge.

   The gutter is now the wide container's own inset, so a beat's padding box IS
   the 1240px measure on a wide screen and shrinks to a plain gutter below it.
   Everything inside then shares one left edge: .u-copy, the beat headings, the
   plates and the alignwide grids all start together.

   The horizontal padding was removed from the block attributes in
   front-page.html rather than overridden here, because an inline style would
   otherwise have needed !important to beat. */
:root[data-day-variant] .ubora-beat,
:root[data-day-variant] .ubora-after-day {
  --u-gutter: clamp(1.25rem, 4vw, 3rem);
  padding-inline: max(var(--u-gutter), calc((100% - 1240px) / 2));
}

/* Each beat owns a screen.
   -------------------------------------------------------------------
   The harness this was tuned in gave every beat `min-height: 100vh` and
   centred its content, so the day advanced at one hour per screen and felt
   like time passing. The real homepage never got that rule, so beat height
   became whatever its content happened to be: 0.54 viewports for the hero,
   0.44 for the reveal, 1.76 for the close. The whole morning went by in half
   a screen while the close dragged, which is why the live page did not scroll
   like the thing we tested.

   min-height only ever raises a short beat; the collection and close beats are
   already taller than a screen and are untouched.

   display:grid is not decoration either. .u-copy-section below sets
   `align-content`, which does nothing on a block element - the vertical
   centring it was written for has never actually run.

   Scoped to [data-day-variant] so this only applies while the engine is live.
   With JS off the page keeps its natural flow rather than stretching to six
   empty screens. */
:root[data-day-variant] .ubora-beat {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  align-content: center;
}

/* A held beat is a stop in the day, not a pause in the reading: the collection
   grid is taller than a screen already and should sit at the top of its beat
   rather than float in the middle of it. */
:root[data-day-variant] .ubora-beat[data-day-hold] {
  align-content: start;
  padding-top: clamp(3rem, 8vh, 6rem);
}

/* A scrim under the type only, strength driven by the hour: invisible through
   the bright middle of the day, rising at dawn and sunset where the plaster
   goes deep and Anchor type would otherwise lose contrast. Sand-to-transparent
   per the direction, never a switch to white type.

   Generously oversized and fully feathered — a bounded box shows its own edge
   as a faint rectangle over plaster, which is worse than no scrim at all. */
.u-copy::before {
  content: '';
  position: absolute;
  inset: -60% -70% -60% -90%;
  background: radial-gradient(
    58% 46% at 32% 50%,
    rgb(var(--u-ground) / calc(var(--u-copy-scrim, 0.1) * 1.6)) 0%,
    rgb(var(--u-ground) / calc(var(--u-copy-scrim, 0.1) * 0.7)) 40%,
    rgb(var(--u-ground) / 0) 70%
  );
  pointer-events: none;
  z-index: -1;
}

/* Narrow landscape (a short wide window) has no room for a 46% column either;
   fall back to the portrait treatment. */
@media (max-width: 720px) {
  :root .u-copy {
    max-width: 100%;
  }
  :root .u-copy-section {
    align-content: start;
    padding-top: clamp(2rem, 9vh, 6rem);
  }
}

/* ---------------------------------------------------------------------
   The collection grid, over the room
   ---------------------------------------------------------------------
   The card grid sits on top of a photographic backdrop whose composition
   changes with the viewport, so the grid has to change with it too. Keyed
   off the same data-day-variant the engine publishes.

     wide      the lineup backdrop is nine bottles in one row, so the cards
               echo it: one row of nine.
     regular   two rows, cards big enough to read the affirmation line.
     portrait  a horizontal scroll strip. A 3-up grid of tiny cards over a
               vertical photograph reads as clutter, and a phone user
               swiping a shelf of bottles matches what is behind them.
   --------------------------------------------------------------------- */

.u-collection {
  display: grid;
  gap: clamp(0.5rem, 1vw, 0.9rem);
  width: 100%;
}

:root[data-day-variant='wide'] .u-collection {
  grid-template-columns: repeat(9, 1fr);
  max-width: min(96rem, 94vw);
}

:root[data-day-variant='regular'] .u-collection {
  grid-template-columns: repeat(5, 1fr);
  max-width: min(72rem, 92vw);
}

/* Phones: a swipeable shelf, not a grid. */
:root[data-day-variant='portrait'] .u-collection {
  display: flex;
  max-width: 100%;
  margin-inline: calc(50% - 50vw);
  padding-inline: max(1.25rem, calc(50vw - 50%));
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

:root[data-day-variant='portrait'] .u-collection::-webkit-scrollbar {
  display: none;
}

:root[data-day-variant='portrait'] .u-collection > * {
  flex: 0 0 42vw;
  max-width: 190px;
  scroll-snap-align: start;
}

/* The card itself. Lifts on the same shadow the sun is casting behind it. */
.u-card {
  display: grid;
  gap: 0.1rem;
  padding-bottom: 0.7rem;
  border-radius: 6px;
  overflow: hidden;
  background: rgb(var(--u-panel) / 0.92);
  box-shadow: var(--u-cast);
  text-decoration: none;
  transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1);
}

@media (hover: hover) {
  .u-card:hover {
    transform: translateY(-3px);
  }
}

.u-card img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

/* At nine across the affirmation line has nowhere to go, so it is dropped and
   the card carries the name and the price only. */
:root[data-day-variant='wide'] .u-card__affirm {
  display: none;
}

/* The clock belongs to the day, so it leaves with it.
   It is position:fixed, so it kept ticking over the footer, sitting on
   parchment with no day layer behind it at all: a clock reading 19:27 against
   a surface that is not part of the room. index.js adds .is-past-day once the
   last beat is behind the viewport. */
.ubora-clock {
  transition: opacity 260ms ease, transform 260ms ease;
}
:root.is-past-day .ubora-clock {
  opacity: 0;
  transform: translateY(0.4rem);
  pointer-events: none;
}
