/* ==========================================================================
   Lumenant - under construction
   Single-screen holding page. Photographic plate + live type + ambient light.
   ========================================================================== */

/* --- fonts ---------------------------------------------------------------- */
/* Only the three weights the page actually sets: 400 (supporting line),
   500 (headline, tagline, footer), 700 (the "a brighter" accent). */
@font-face{font-family:Figtree;src:url(fonts/figtree-400.woff2) format('woff2');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:Figtree;src:url(fonts/figtree-500.woff2) format('woff2');font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:Figtree;src:url(fonts/figtree-700.woff2) format('woff2');font-weight:700;font-style:normal;font-display:swap}

/* --- tokens --------------------------------------------------------------- */
:root{
  /* brand - sampled from the Lumenant mark and the reference composition */
  --teal-ink:      #12454D;
  --teal-ink-soft: #2B646D;
  --lime:          #8ABB2D;
  --warm:          255, 248, 222;   /* sunlight, as rgb parts */

  /* The window in the plate. .bloom and the dust motes anchor here. */
  --sun-x: 86%;
  --sun-y: -2%;

  /* Origin of the conic ray fan, kept separate from --sun-x/--sun-y. On a
     wide viewport the convergence point sitting just off the top-right reads
     correctly, because that is where the window is. On a narrow one the same
     point lands inside the frame and the page becomes an obvious starburst,
     so the breakpoints push this origin well off-canvas: far from the vertex
     the rays arrive nearly parallel, which is what daylight through a window
     actually looks like. --ray-span grows to match so the mask still reaches. */
  --ray-x: 86%;
  --ray-y: -2%;

  /* Ray-fan diameter. The distance mask on .rays is sized `closest-side`, so
     this value alone decides how fast the light falls off across the screen.
     It tracks vw (not vmax) because the fall-off has to stay proportional to
     the *width*: on a tall phone, vmax would put the entire viewport inside
     the full-strength zone and the page would render as a beige starburst.
     The px cap keeps the promoted layer from reaching absurd sizes on very
     wide monitors - 3000px square is ~36MB, 250vw on a 5K display is ~10x. */
  --ray-span: min(250vw, 3000px);

  /* Absolute strength of each light layer. Every one of these is referenced
     from inside its @keyframes rather than being set alongside the animation,
     because an animated `opacity` outranks a static declaration - see the
     note above @keyframes sweep-a. Breakpoints override these. */
  --ray-op-a: 1;
  --ray-op-b: .55;
  --shaft-op-1: .62;
  --shaft-op-2: .48;

  /* the warm dim carried *between* shafts */
  --gap-a: #C2B283;
  --gap-b: #D3C69C;
  --shaft-dim-1: #DCCFA4;
  --shaft-dim-2: #E5DBBB;

  --bloom-size: 110vmax;
  --bloom-op: 1;

  --ease-out: cubic-bezier(.22, 1, .36, 1);
}

/* --- reset ---------------------------------------------------------------- */
*,*::before,*::after{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;
  font-family:Figtree,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  color:var(--teal-ink);
  background:#EDF3F2;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
  overflow:hidden;
}

/* --- stage ---------------------------------------------------------------- */
.stage{
  position:relative;
  width:100%;
  height:100vh;
  height:100dvh;
  overflow:hidden;
  isolation:isolate;          /* stops the blend modes below reaching the page */
}

/* --- photographic plate --------------------------------------------------- */
.plate{position:absolute;inset:0;display:block;z-index:0}
.plate img{
  width:100%;
  height:100%;
  object-fit:cover;
  object-position:50% 52%;
  display:block;
  filter:saturate(1.02) brightness(1.012);
}

/* ==========================================================================
   LIGHT
   Layers run dimmest -> brightest. All decorative, all inert.
   ========================================================================== */

/* Deliberately has NO z-index. Giving this element one would make it a
   stacking context, and every mix-blend-mode below would then blend against
   this group's own transparency instead of the photographic plate - the
   light would composite flat and none of it would read. z-index:auto keeps
   it in the same painting group as .plate (z-index:0), so it still paints
   above the photo, while .stage's `isolation` stops the blend going further. */
.light{position:absolute;inset:0;pointer-events:none;overflow:hidden}

/* 1 - bloom: the window itself, breathing */
.bloom{
  position:absolute;
  left:var(--sun-x); top:var(--sun-y);
  width:var(--bloom-size); height:var(--bloom-size);
  margin-left:calc(var(--bloom-size) / -2);
  margin-top:calc(var(--bloom-size) / -2);
  background:
    radial-gradient(circle at 50% 50%,
      rgba(var(--warm), .58)  0%,
      rgba(var(--warm), .30) 14%,
      rgba(var(--warm), .12) 30%,
      rgba(var(--warm), .04) 46%,
      rgba(var(--warm), 0)   62%);
  mix-blend-mode:screen;
  will-change:opacity, transform;
  animation:breathe 13s ease-in-out infinite;
}

/* 2 - god rays: two conic fans at different periods, counter-rotating.
   These blend with `multiply`, not `screen`. The wall behind them is already
   near-white, so *added* light is invisible - screen over ~#FBFBF9 does
   essentially nothing. Instead the gaps between rays carry a warm dim and the
   ray cores stay pure white (multiply by #fff is a no-op), which is how
   sunlight actually reads on a bright surface. Softness lives in the colour
   stops rather than a blur filter, so the layer stays composited and the
   sweep holds 60fps even at 3000px square. */
.rays{
  position:absolute;
  left:var(--ray-x); top:var(--ray-y);
  width:var(--ray-span); height:var(--ray-span);
  margin-left:calc(var(--ray-span) / -2);
  margin-top:calc(var(--ray-span) / -2);
  transform-origin:50% 50%;
  mix-blend-mode:multiply;
  will-change:transform, opacity;
  -webkit-mask-image:radial-gradient(closest-side,
      rgba(0,0,0,.34) 1%,  rgba(0,0,0,.95) 8%,  rgba(0,0,0,1) 26%,
      rgba(0,0,0,.86) 44%, rgba(0,0,0,.52) 62%, rgba(0,0,0,.18) 78%,
      transparent 92%);
          mask-image:radial-gradient(closest-side,
      rgba(0,0,0,.34) 1%,  rgba(0,0,0,.95) 8%,  rgba(0,0,0,1) 26%,
      rgba(0,0,0,.86) 44%, rgba(0,0,0,.52) 62%, rgba(0,0,0,.18) 78%,
      transparent 92%);
}

.rays--a{
  background:repeating-conic-gradient(from 174deg at 50% 50%,
    var(--gap-a)  0deg,
    #FFF7E2       2.8deg,
    #FFFFFF       4.6deg,
    #FFF7E2       6.4deg,
    var(--gap-a)  9.4deg,
    var(--gap-a) 13.8deg);
  animation:sweep-a 38s ease-in-out infinite;
}

/* 8.3deg against 13.8deg is deliberately non-harmonic. On a round multiple
   this fan would sit neatly inside the first one's gaps and the two would
   average out to flat; off-beat, they interfere and the light reads as
   irregular the way real shafts do. */
.rays--b{
  background:repeating-conic-gradient(from 203deg at 50% 50%,
    var(--gap-b)  0deg,
    #FFFBEE       1.6deg,
    #FFFFFF       2.8deg,
    #FFFBEE       4.0deg,
    var(--gap-b)  6.0deg,
    var(--gap-b)  8.3deg);
  animation:sweep-b 61s ease-in-out infinite;
}

/* 3 - shafts: broad bands of sunlight travelling across the wall. Same trick
   as the fans - a warm multiply flank either side of an untouched core makes
   the beam legible without blowing the highlights. */
.shaft{
  position:absolute;
  top:-55%;
  height:210%;
  mix-blend-mode:multiply;
  filter:blur(22px);
  will-change:transform, opacity;
}
.shaft--1{
  left:-45%; width:56%;
  background:linear-gradient(90deg,
    #FFFFFF 0%, var(--shaft-dim-1) 26%, #FFFDF4 50%, var(--shaft-dim-1) 74%, #FFFFFF 100%);
  transform:rotate(16deg);
  animation:drift-1 29s cubic-bezier(.45,.05,.55,.95) infinite;
}
.shaft--2{
  left:-30%; width:34%;
  background:linear-gradient(90deg,
    #FFFFFF 0%, var(--shaft-dim-2) 30%, #FFFFFF 52%, var(--shaft-dim-2) 72%, #FFFFFF 100%);
  transform:rotate(16deg);
  animation:drift-2 43s cubic-bezier(.45,.05,.55,.95) infinite;
  animation-delay:-11s;
}

/* 4 - dust motes (canvas) */
.motes{
  position:absolute; inset:0;
  width:100%; height:100%;
  mix-blend-mode:screen;
  opacity:.85;
}

/* 5 - warmth: ties the frame together, top-right to bottom-left */
.warmth{
  position:absolute; inset:0;
  background:linear-gradient(203deg,
    rgba(255,246,208,.30) 0%,
    rgba(255,250,232,.10) 34%,
    rgba(255,255,255,0)   58%,
    rgba(24,110,120,.05) 100%);
  mix-blend-mode:soft-light;
  animation:warmth-shift 21s ease-in-out infinite;
}

/* ==========================================================================
   CONTENT
   ========================================================================== */
.content{
  position:relative;
  z-index:2;
  height:100%;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:clamp(28px, 4.6vh, 62px);
  padding:max(28px, 5vh) clamp(20px, 5vw, 64px);
  text-align:center;
  transform:translateY(-2.5%);
}

/* a barely-there halo so the type never loses the wall behind it when a
   shaft sweeps across */
.content::before{
  content:'';
  position:absolute;
  left:50%; top:46%;
  width:min(1020px, 90vw);
  height:min(660px, 76vh);
  transform:translate(-50%,-50%);
  background:radial-gradient(ellipse at 50% 50%,
    rgba(255,255,255,.34) 0%,
    rgba(255,255,255,.20) 40%,
    rgba(255,255,255,.06) 64%,
    rgba(255,255,255,0)   82%);
  z-index:-1;
  pointer-events:none;
}

/* --- logo lockup ---------------------------------------------------------- */
.lockup{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:clamp(6px, .9vh, 12px);
}

.wordmark{
  display:block;
  width:clamp(216px, 27vw, 430px);
  height:auto;
  opacity:0;
  transform:translateY(14px);
  animation:rise .95s var(--ease-out) .12s forwards;
}

.tagline{
  margin:0;
  /* the mark's dot-spray sits left of the wordmark, so centring on the image
     box would read as off-centre; this nudges the line under the letters */
  padding-left:clamp(30px, 4.3vw, 66px);
  font-size:clamp(12.5px, 1.42vw, 22px);
  font-weight:500;
  letter-spacing:.004em;
  color:var(--teal-ink);
  white-space:nowrap;
  opacity:0;
  transform:translateY(12px);
  animation:rise .95s var(--ease-out) .26s forwards;
}

/* --- headline ------------------------------------------------------------- */
.headline{
  margin:0;
  max-width:19ch;
  font-size:clamp(27px, 4.15vw, 63px);
  font-weight:500;
  line-height:1.2;
  letter-spacing:-.014em;
  text-wrap:balance;
}
.headline .line{
  display:block;
  opacity:0;
  transform:translateY(16px);
  animation:rise 1.05s var(--ease-out) forwards;
}
.headline .line:nth-child(1){animation-delay:.42s}
.headline .line--2{
  margin-top:.16em;
  font-weight:400;
  color:var(--teal-ink-soft);
  animation-delay:.58s;
}
.headline em{
  font-style:normal;
  font-weight:700;
}

/* ==========================================================================
   MOTION
   ========================================================================== */
@keyframes rise{
  to{opacity:1; transform:translateY(0)}
}
@keyframes breathe{
  0%,100%{opacity:calc(var(--bloom-op) * .72); transform:scale(1)}
  50%    {opacity:var(--bloom-op);             transform:scale(1.045)}
}

/* An animated `opacity` beats a static `opacity` declaration outright, so
   setting strength on the rule and breathing in the keyframe would silently
   discard the former - every per-breakpoint dimming below would do nothing.
   Multiplying the token inside the keyframe keeps the breakpoints in charge
   of absolute strength while the animation only shapes the breath. */
@keyframes sweep-a{
  0%  {transform:rotate(-2.6deg); opacity:calc(var(--ray-op-a) * .72)}
  50% {transform:rotate( 2.6deg); opacity:var(--ray-op-a)}
  100%{transform:rotate(-2.6deg); opacity:calc(var(--ray-op-a) * .72)}
}
@keyframes sweep-b{
  0%  {transform:rotate( 3.4deg); opacity:calc(var(--ray-op-b) * .56)}
  50% {transform:rotate(-3.4deg); opacity:var(--ray-op-b)}
  100%{transform:rotate( 3.4deg); opacity:calc(var(--ray-op-b) * .56)}
}
@keyframes drift-1{
  0%  {transform:rotate(16deg) translateX(0);     opacity:0}
  14% {opacity:var(--shaft-op-1)}
  82% {opacity:var(--shaft-op-1)}
  100%{transform:rotate(16deg) translateX(215vw); opacity:0}
}
@keyframes drift-2{
  0%  {transform:rotate(16deg) translateX(0);     opacity:0}
  18% {opacity:var(--shaft-op-2)}
  78% {opacity:var(--shaft-op-2)}
  100%{transform:rotate(16deg) translateX(260vw); opacity:0}
}
@keyframes warmth-shift{
  0%,100%{opacity:.85}
  50%    {opacity:1}
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

/* wide + short: pull the plate up so the sofa and table stay in frame */
@media (min-aspect-ratio: 16/9){
  .plate img{object-position:50% 58%}
}

/* tablet / portrait-ish */
@media (max-width: 900px){
  :root{
    --sun-x:84%;
    --ray-x:118%; --ray-y:-38%;
    --ray-span:min(340vw, 2600px);
    /* far less canvas to spread over, so identical contrast reads much louder */
    --ray-op-a:.62; --ray-op-b:.30;
    --shaft-op-1:.50; --shaft-op-2:.36;
    --gap-a:#CFC29B; --gap-b:#DED4B4;
    --shaft-dim-1:#E9E0C6; --shaft-dim-2:#F0E9D6;
    --bloom-size:min(150vw, 1100px); --bloom-op:.8;
  }
  /* keep the window and the plant in frame - a centred cover crop on a
     portrait viewport lands on the blank middle of the plate and the
     photograph disappears entirely */
  .plate img{object-position:70% 50%}
  .headline{max-width:16ch; font-size:clamp(26px, 5.4vw, 44px)}
  .wordmark{width:clamp(214px, 46vw, 340px)}
  .tagline{font-size:clamp(12px, 2.5vw, 18px); padding-left:clamp(28px, 7vw, 52px)}
}

/* phones: reframe the plate so the type sits on clean wall, and keep the
   window edge visible top-right so the light still reads as motivated */
@media (max-width: 640px){
  :root{
    --sun-x:88%; --sun-y:-4%;
    --ray-x:152%; --ray-y:-64%;
    --ray-span:min(460vw, 3000px);
    --ray-op-a:.62; --ray-op-b:.28;
    --shaft-op-1:.34; --shaft-op-2:.24;
    --gap-a:#DDD2B2; --gap-b:#E9E2CA;
    --shaft-dim-1:#F2ECDB; --shaft-dim-2:#F7F3E8;
    --bloom-size:min(135vw, 760px); --bloom-op:.62;
  }
  .plate img{object-position:84% 46%}
  .content{gap:clamp(24px, 4vh, 38px); transform:translateY(-2%)}
  .content::before{
    width:118vw; height:70vh;
    background:radial-gradient(ellipse at 50% 50%,
      rgba(255,255,255,.60) 0%, rgba(255,255,255,.38) 40%,
      rgba(255,255,255,.12) 66%, rgba(255,255,255,0) 82%);
  }
  .headline{max-width:15ch; letter-spacing:-.008em}
  .shaft{filter:blur(18px)}
}

@media (max-width: 380px){
  .headline{font-size:clamp(23px, 6.6vw, 30px)}
  .wordmark{width:min(224px, 62vw)}
  .tagline{letter-spacing:0}
}

/* very short viewports (landscape phones) - compress rather than clip */
@media (max-height: 520px){
  .content{gap:14px; transform:none}
  .wordmark{width:clamp(180px, 22vw, 260px)}
  .headline{font-size:clamp(19px, 3.1vw, 30px); max-width:34ch}
  .headline .line--2{margin-top:.1em}
}

/* ==========================================================================
   ACCESSIBILITY
   ========================================================================== */
@media (prefers-reduced-motion: reduce){
  .bloom,.rays,.shaft,.warmth{animation:none !important}
  /* Parked, not merely frozen. Held at full strength the fan reads as a hard
     sunburst - the moving version only ever gets away with that peak because
     it is passing through it. The static composition is deliberately softer. */
  .rays--a{transform:rotate(-1deg);  opacity:calc(var(--ray-op-a) * .5)}
  .rays--b{transform:rotate(1.6deg); opacity:calc(var(--ray-op-b) * .45)}
  .shaft--1{transform:rotate(16deg) translateX(74vw);  opacity:calc(var(--shaft-op-1) * .65)}
  .shaft--2{transform:rotate(16deg) translateX(128vw); opacity:calc(var(--shaft-op-2) * .65)}
  .bloom{opacity:.9}
  .warmth{opacity:.9}
  .motes{display:none}
  .wordmark,.tagline,.headline .line{
    animation:none !important; opacity:1; transform:none;
  }
}

@media (prefers-contrast: more){
  :root{--teal-ink:#0B3138; --teal-ink-soft:#1B4C55}
  .content::before{
    background:radial-gradient(ellipse at 50% 50%,
      rgba(255,255,255,.86) 0%, rgba(255,255,255,.66) 42%,
      rgba(255,255,255,.24) 68%, rgba(255,255,255,0) 84%);
  }
}
