/* GLAM 2026 palette. Pink leads, navy anchors text, gold finishes. */
:root {
  --glam-pink: #F04E9A;
  --deep-rose: #B83577;
  --petal: #FCCDE3;
  --blush-white: #FFF2F8;
  --glam-navy: #1B2A4A;
  --gold-sand: #C4A882;
}

* { box-sizing: border-box; }

/* The hidden attribute must win over any display rule below. Without this,
   .thumb (inline-block) and .gallery (flex) stay visible when hidden is set -
   which showed customers a broken photo preview. */
[hidden] { display: none !important; }


body {
  margin: 0;
  padding: 2rem 1.25rem 4rem;
  font-family: Montserrat, system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--glam-navy);
  background: var(--blush-white);
}

main {
  max-width: 620px;
  margin: 0 auto;
}

header {
  text-align: center;
  margin-bottom: 2rem;
}

h1 {
  margin: 0 0 .35rem;
  font-size: 1.65rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--deep-rose);
}

.product {
  margin: 0;
  font-size: .8rem;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--gold-sand);
}

label {
  display: block;
  margin-bottom: .5rem;
  font-size: .85rem;
  font-weight: 600;
}

textarea {
  width: 100%;
  padding: .85rem 1rem;
  font: inherit;
  font-size: 1rem;
  color: inherit;
  background: #fff;
  border: 2px solid var(--petal);
  border-radius: 10px;
  resize: vertical;
}

textarea:focus {
  outline: none;
  border-color: var(--glam-pink);
}

button {
  width: 100%;
  margin-top: .85rem;
  padding: .9rem;
  font: inherit;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: #fff;
  background: var(--glam-pink);
  border: none;
  border-radius: 10px;
  cursor: pointer;
  transition: background .15s;
}

button:hover:not(:disabled) { background: var(--deep-rose); }

button:disabled {
  background: var(--petal);
  color: var(--deep-rose);
  cursor: default;
}

/* Orientation picker */
fieldset {
  margin: 0 0 1.25rem;
  padding: 0;
  border: none;
}

legend {
  padding: 0;
  margin-bottom: .5rem;
  font-size: .85rem;
  font-weight: 600;
}

#orientation {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
}

.choice {
  flex: 1 1 calc(50% - .3rem);
  margin: 0;
  padding: .7rem;
  font-size: .9rem;
  font-weight: 500;
  text-align: center;
  background: #fff;
  border: 2px solid var(--petal);
  border-radius: 10px;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}

.choice input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.choice:has(input:checked) {
  background: var(--glam-pink);
  border-color: var(--glam-pink);
  color: #fff;
  font-weight: 600;
}

.choice:has(input:focus-visible) {
  outline: 2px solid var(--glam-navy);
  outline-offset: 2px;
}

.status {
  margin-top: 1.5rem;
  padding: .9rem 1rem;
  font-size: .9rem;
  text-align: center;
  border-radius: 10px;
  background: #fff;
  border: 1px solid var(--petal);
}

.status.error {
  color: var(--deep-rose);
  background: #fff;
  border-color: var(--deep-rose);
  text-align: left;
}

#result {
  margin-top: 1.5rem;
}

.frame {
  position: relative;
  line-height: 0;
  border-radius: 10px;
  /* Clips the light sweep below to the artwork's own corners. */
  overflow: hidden;
}

#image {
  display: block;
  width: 100%;
  border-radius: 10px;
  box-shadow: 0 6px 24px rgba(27, 42, 74, .13);
}

/* The print-file download. Internal only - it appears on the password
   protected deployment beside the saved-designs link, and never on the
   storefront. Quiet, because it is a tool rather than an offer. */
.download {
  margin: .8rem 0 0;
  text-align: center;
}

.download a {
  font-size: .85rem;
  font-weight: 600;
  color: var(--deep-rose);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.download a:hover { color: var(--glam-pink); }

/* Glammie, the design helper. Custom only - every other theme wraps what the
   customer types in a great deal of our own wording, so there is nothing for
   her to help with.

   She is drawn as somebody speaking rather than as a toolbar. The audience is
   a grandmother who is not sure how to describe a picture, and a row of
   controls says "operate me" where a person says "I can help". */
.glammie {
  margin-top: .9rem;
  padding: 1rem 1rem .9rem;
  background: var(--blush-white);
  border: 1px solid var(--petal);
  border-radius: 14px;
}

.glammie-intro {
  display: flex;
  align-items: flex-start;
  gap: .6rem;
}

/* Gifty Gretchen is a full-length figure - she stands about two and a half
   times her own width, where the old avatar was head and shoulders. So the
   bubble is aligned to the TOP of her rather than the bottom: level with her
   face and her raised hand, which is where a speech bubble belongs. Aligning
   to the bottom, as the old one did, would put her words beside her shoes. */
.glammie-avatar {
  flex: 0 0 auto;
  width: 72px;
  height: auto;
  align-self: flex-start;
  filter: drop-shadow(0 3px 6px rgba(27, 42, 74, .16));
  animation: glammie-in .5s cubic-bezier(.2, .8, .3, 1) both;
}

@keyframes glammie-in {
  from { opacity: 0; transform: translateY(8px) scale(.94); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* A speech bubble, with the tail pointing back at her. */
.glammie-bubble {
  position: relative;
  flex: 1 1 auto;
  padding: .7rem .9rem;
  background: #fff;
  border: 1px solid var(--petal);
  border-radius: 14px;
  animation: glammie-said .4s ease-out both;
}

.glammie-bubble p {
  margin: 0;
  font-size: .87rem;
  line-height: 1.5;
}

.glammie-bubble p + p { margin-top: .5rem; }

.glammie-bubble::before,
.glammie-bubble::after {
  content: "";
  position: absolute;
  left: -7px;
  bottom: 14px;
  width: 0;
  height: 0;
  border: 7px solid transparent;
  border-right-color: var(--petal);
  border-left: 0;
}

/* The inner triangle sits a pixel inside the outer one, so the tail gets the
   same hairline border as the bubble instead of a solid wedge. */
.glammie-bubble::after {
  left: -6px;
  border-right-color: #fff;
}

@keyframes glammie-said {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.glammie-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .8rem;
  margin-top: 1rem;
}

.glammie-ask {
  width: auto;
  margin-top: 0;
  padding: .55rem 1rem;
  font-size: .88rem;
}

/* Deliberately quiet. Keeping their own words is the ordinary thing to do,
   not a correction, so it should not look like a warning. */
.glammie-undo {
  width: auto;
  margin-top: 0;
  padding: 0;
  font-size: .82rem;
  font-weight: 600;
  color: var(--deep-rose);
  background: none;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.glammie-undo:hover:not(:disabled) { background: none; color: var(--glam-pink); }

.glammie-said { margin-top: .9rem; }
.glammie-said .help { margin: .55rem 0 0; }

/* She is a flourish, and a flourish is the first thing to drop. */
@media (prefers-reduced-motion: reduce) {
  .glammie-avatar,
  .glammie-bubble { animation: none; }
}

/* ---------------------------------------------------------------------------
   The wait.

   A generation takes about thirty seconds. That used to be one line of text on
   a white card, which is a long time to look at nothing - and the moment most
   likely to lose someone who has already done the work of uploading a photo.

   So: their own child's photo on screen, the steps ticking off as they happen
   rather than replacing each other, a bar that moves, and an honest sentence
   about how long it takes. The point is not to disguise the wait but to make
   it feel like something is being made.
   --------------------------------------------------------------------------- */

/* The panel takes the whole screen. Inside the iframe that means the frame's
   own viewport; the product page pins the frame to the window when the widget
   sends glam:takeover, so on the live site this really is full bleed. Without
   that message it still works, just inside the frame's box. */
/* While the wait is up, the widget's own document must not scroll behind it.
   The product page is frozen separately by the theme on glam:takeover; this is
   the inside of the frame, which the theme cannot reach. Without it a drag on
   the panel scrolls the form underneath and the scene appears to run on. */
html.glam-waiting,
html.glam-waiting body {
  overflow: hidden;
  height: 100%;
}

.studio {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: #05070F;
  color: #fff;
  animation: glam-open .5s cubic-bezier(.2, .8, .3, 1) both;
}

@keyframes glam-open {
  from { opacity: 0; transform: scale(1.03); }
  to   { opacity: 1; transform: scale(1); }
}

/* The stage is a camera. Everything inside .studio-world sits at a real Z
   distance from it, so the separation between the picture and the leaves in
   front of it is computed by the browser rather than approximated here. */
.studio-stage {
  position: relative;
  flex: 1 1 auto;
  overflow: hidden;
  perspective: 900px;
  perspective-origin: 50% 40%;
}

/* Two nested elements because one element cannot run two animations on
   transform - the second would simply win. The world dollies forward over the
   length of the wait; the sway drifts a fraction of a degree, which is what
   reads as a camera held rather than bolted down. */
.studio-world {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  will-change: transform;
  animation: glam-dolly var(--glam-wait, 32s) cubic-bezier(.4, 0, .7, 1) forwards;
}

@keyframes glam-dolly {
  from { transform: translateZ(0); }
  to   { transform: translateZ(150px); }
}

.studio-sway {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  animation: glam-sway 17s ease-in-out infinite;
}

@keyframes glam-sway {
  0%, 100% { transform: rotateY(-.6deg) rotateX(.28deg); }
  50%      { transform: rotateY(.6deg)  rotateX(-.28deg); }
}

/* Sized up rather than scaled up. A layer at z = -500 with a 900px perspective
   appears at 900/1400 of its size, so it is built at 156% and lands back at
   full frame - and it rasterises at that larger size, so it stays sharp.
   Using transform: scale would just magnify pixels. */
.studio-scene {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 156%;
  height: 156%;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  backface-visibility: hidden;
  transform: translate(-50%, -50%) translateZ(-500px);
}

/* The same picture again, middle masked out, parked much closer to the camera.
   The edges of the scene are already dark foliage, so masking the centre away
   leaves a foreground of leaves - and because it sits nearer, it swings
   further than the picture behind it. Real depth from one file. */
.studio-scene-frame {
  width: 117%;
  height: 117%;
  transform: translate(-50%, -50%) translateZ(-140px);
  -webkit-mask-image: radial-gradient(56% 46% at 50% 44%,
    transparent 0%, transparent 46%, rgba(0, 0, 0, .65) 66%, #000 84%);
  mask-image: radial-gradient(56% 46% at 50% 44%,
    transparent 0%, transparent 46%, rgba(0, 0, 0, .65) 66%, #000 84%);
}

/* The arrival.

   It starts as a blurred speck at the far end of the valley and closes hard.
   The easing is back-loaded on purpose: barely there for most of the wait,
   then unmistakable - a wait that builds has to gather rather than travel at a
   constant rate. The blur clearing as it comes is what sells the distance.

   It stands between the painting and the leaves in front, so the near frame
   passes over it as the camera comes forward, and behind the child's photo,
   which keeps them the closest thing on screen. */
.studio-beast {
  position: absolute;
  left: 50%;
  bottom: 30%;
  z-index: 5;
  /* Boxed as a share of the stage rather than in vmin. On a tall phone vmin is
     the WIDTH, so a vmin-tall box is short and low and he ends up buried in
     the foliage at the bottom of the frame - which is exactly where he went
     missing. A percentage box keeps him the same size relative to the screen
     on any shape of phone. */
  width: 92%;
  height: 46%;
  margin-left: -46%;
  transform-origin: 50% 100%;
  animation: glam-approach var(--glam-wait, 32s) cubic-bezier(.6, .01, .76, .44) forwards;
}

/* Carries the picture, and the walk. Nested because the approach already owns
   the transform on the parent, and one element cannot run two. */
.studio-beast-walk {
  width: 100%;
  height: 100%;
  background-position: center bottom;
  background-size: contain;
  background-repeat: no-repeat;
}

/* Matched to the footstep cadence above. */
.studio-beast-walk { animation: glam-bob 1.9s ease-in-out infinite; }
.studio.act3 .studio-beast-walk { animation-duration: 1.25s; }

@keyframes glam-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-.45vmin); }
}

@keyframes glam-approach {
  from { transform: translateZ(-380px) scale(.05); opacity: .25; filter: blur(2.6px); }
  45%  { opacity: .8; }
  to   { transform: translateZ(-380px) scale(1); opacity: 1; filter: blur(0); }
}

/* Their own child's photo, at the size a face deserves, and kept in a frame.
   A photo dissolved into a painted scene is the pasted-on look this whole
   product line exists to beat, and the wait must not teach customers to
   expect it. */
.studio-photo {
  position: absolute;
  left: 50%;
  top: 63%;
  z-index: 14;
  width: 32vmin;
  height: 32vmin;
  margin-left: -16vmin;
  margin-top: -16vmin;
  border-radius: 50%;
  overflow: hidden;
  border: .5vmin solid rgba(255, 242, 248, .94);
  box-shadow: 0 2vmin 5vmin rgba(2, 8, 10, .75);
  background: var(--blush-white);
  animation: glam-portrait .8s cubic-bezier(.2, .8, .3, 1) both;
}

@keyframes glam-portrait {
  from { opacity: 0; transform: scale(.86); }
  to   { opacity: 1; transform: scale(1); }
}

/* Over the wait the photo warms toward the palette the artwork is drawn in.
   Not a metaphor - their photo really is becoming the art. */
.studio-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  animation: glam-warm var(--glam-wait, 32s) ease-in-out forwards;
}

@keyframes glam-warm {
  from { filter: saturate(1)    sepia(0)   contrast(1); }
  to   { filter: saturate(1.35) sepia(.28) contrast(1.07); }
}

/* A slow light passing over the photo, so it reads as being looked AT. */
.studio-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0) 40%,
    rgba(255, 255, 255, .42) 50%,
    rgba(255, 255, 255, 0) 60%
  );
  animation: glam-scan 3.4s ease-in-out infinite;
}

@keyframes glam-scan {
  from { transform: translateY(-100%); }
  to   { transform: translateY(100%); }
}

/* Tightens and quickens as the moment closes in. */
.studio-halo {
  position: absolute;
  left: 50%;
  top: 63%;
  z-index: 13;
  width: 32vmin;
  height: 32vmin;
  margin-left: -16vmin;
  margin-top: -16vmin;
  border-radius: 50%;
  pointer-events: none;
}

.studio.act2 .studio-halo { animation: glam-halo 1.6s ease-in-out infinite; }
.studio.act3 .studio-halo { animation: glam-halo .7s ease-in-out infinite; }

@keyframes glam-halo {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
  50%      { box-shadow: 0 0 4vmin 1.2vmin var(--glam-accent, #FCCDE3); }
}

/* Footsteps, not a tremor.

   This used to be a continuous shake at six times a second, which on a long
   generation is a minute of unbroken vibration - and unbroken vibration is a
   reliable way to make somebody queasy. It also did not read as anything: the
   idea was always that the ground registers something walking up.

   So the stage is still for most of each cycle and takes one short impact,
   which settles inside a third of a second. The escalation is in the CADENCE
   rather than the speed - the steps come closer together as he closes, which
   is what walking toward someone actually sounds like. The body's bob shares
   the same period, so each step lands with a step.

   Vertical only. Sideways jitter is what reads as motion sickness. */
.studio.act2 .studio-stage { animation: glam-step 1.9s ease-out infinite; }
.studio.act3 .studio-stage { animation: glam-step 1.25s ease-out infinite; }

/* Not every world has something heavy walking in it. A kingdom at golden hour
   or a lantern-lit lagoon has nothing that would shake the ground, and a
   scene can say so. The build then comes from the camera, the copy and the
   halo alone - which is enough. */
.studio.quiet .studio-stage { animation: none !important; }

@keyframes glam-step {
  0%   { transform: translate3d(0, 0, 0); }
  4%   { transform: translate3d(0, .42vmin, 0); }
  10%  { transform: translate3d(0, -.2vmin, 0); }
  17%  { transform: translate3d(0, .1vmin, 0); }
  24%  { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* A scrim under the beat line. The first worlds were all dusk, so white text
   sat on dark scenery and needed nothing; the flat cartoon one is bright green
   grass exactly where the words go. This keeps the copy readable on any scene
   without tinting the picture itself, and it reads as the scene settling into
   the bar below rather than as a box behind the text. */
.studio-stage::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 30%;
  z-index: 12;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(3, 6, 12, 0), rgba(3, 6, 12, .82));
}

/* One line at a time, full width. The beats arrive closer together as the
   wait goes on - a wait that builds has to accelerate, not tick. */
.studio-beat {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 3.5vmin;
  z-index: 15;
  margin: 0;
  padding: 0 1.2rem;
  text-align: center;
  font-size: clamp(1rem, 4.6vw, 1.35rem);
  font-weight: 700;
  line-height: 1.35;
  text-wrap: balance;
  text-shadow: 0 .3vmin 2.4vmin rgba(2, 6, 10, .95);
}

.studio-beat span {
  display: inline-block;
  animation: glam-beat .55s cubic-bezier(.2, .85, .3, 1) both;
}

@keyframes glam-beat {
  from { opacity: 0; transform: translateY(1.1vmin) scale(.97); filter: blur(3px); }
  to   { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}

.studio-foot {
  position: relative;
  z-index: 16;
  flex: 0 0 auto;
  padding: 1rem 1.2rem calc(1rem + env(safe-area-inset-bottom, 0px));
  background: #05070F;
}

/* The bar runs to 92% over the expected time and waits there. It never claims
   to be finished before it is. */
.studio-bar {
  position: relative;
  height: 6px;
  margin-bottom: .7rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, .16);
}

.studio-bar span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  position: relative;
  background: linear-gradient(90deg, var(--glam-accent, #FCCDE3), #fff);
}

.studio-bar span::after {
  content: "";
  position: absolute;
  right: -6px;
  top: 50%;
  width: 12px;
  height: 12px;
  margin-top: -6px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 10px 2px var(--glam-accent, #FCCDE3);
}

.studio-note {
  margin: 0;
  font-size: .78rem;
  text-align: center;
  color: rgba(255, 255, 255, .62);
}

/* The held breath. Everything stops for four hundred milliseconds before the
   artwork lands. Excitement is not made by more motion; it is made by taking
   the motion away right before the payoff. */
.studio-breath {
  position: absolute;
  inset: 0;
  z-index: 20;
  background: #04060E;
  opacity: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.studio-breath p {
  margin: 0;
  font-size: clamp(1.1rem, 5vw, 1.5rem);
  font-weight: 800;
  letter-spacing: .04em;
  color: var(--glam-accent, #FCCDE3);
  opacity: 0;
}

.studio.breathing .studio-breath   { animation: glam-breath .34s ease-in forwards; }
.studio.breathing .studio-breath p { animation: glam-breath-word .3s ease-out .12s forwards; }

@keyframes glam-breath { to { opacity: 1; } }
@keyframes glam-breath-word {
  from { opacity: 0; transform: scale(.94); }
  to   { opacity: 1; transform: scale(1); }
}

/* Freeze the world while the breath is held. */
.studio.breathing .studio-stage *  { animation-play-state: paused !important; }
.studio.breathing .studio-stage    { animation: none !important; }

.studio-flash {
  position: absolute;
  inset: 0;
  z-index: 30;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}

.studio.flashing .studio-flash { animation: glam-flash .42s ease-out forwards; }

@keyframes glam-flash {
  0%   { opacity: 0; }
  12%  { opacity: .95; }
  100% { opacity: 0; }
}

/* Anyone who has asked their system for less motion gets the scene already
   arrived and nothing moving. The panel still works: the face, the line, the
   bar, the promise. */
@media (prefers-reduced-motion: reduce) {
  .studio,
  .studio-world,
  .studio-sway,
  .studio-stage,
  .studio-beast,
  .studio-beast-walk,
  .studio-photo,
  .studio-photo img,
  .studio-photo::after,
  .studio-halo,
  .studio-beat span,
  .studio-breath,
  .studio-breath p,
  .studio-flash {
    animation: none !important;
  }

  /* Motion off: it has already arrived. */
  .studio-beast { transform: translateZ(-380px) scale(1); opacity: 1; filter: none; }

  .studio.breathing .studio-breath,
  .studio.breathing .studio-breath p { opacity: 1; }
}

/* ---------------------------------------------------------------------------
   The reveal.
   
   A design took thirty seconds to make and used to arrive as a src swap - it
   was simply there, the way an error message is there. This gives it a moment:
   the artwork comes up out of a blur like a photograph developing, a soft
   light passes across it once, and the child's name lands a beat later so the
   picture is seen before it is read.

   Only on a NEW design. Clicking back through the gallery is instant, because
   a re-reveal of something already seen is theatre, and theatre you cannot
   skip becomes an obstacle.
   --------------------------------------------------------------------------- */

@keyframes glam-develop {
  from { opacity: 0; filter: blur(22px) saturate(.75); transform: scale(1.015); }
  60%  { opacity: 1; }
  to   { opacity: 1; filter: blur(0) saturate(1); transform: scale(1); }
}

@keyframes glam-sweep {
  from { transform: translateX(-130%) rotate(8deg); opacity: 0; }
  35%  { opacity: .55; }
  to   { transform: translateX(130%) rotate(8deg); opacity: 0; }
}

@keyframes glam-rise {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: translateY(0); }
}

.frame.revealing #image {
  animation: glam-develop 1s cubic-bezier(.2, .7, .3, 1) both;
}

.frame.revealing::after {
  content: "";
  position: absolute;
  top: -25%;
  left: 0;
  width: 45%;
  height: 150%;
  pointer-events: none;
  background: linear-gradient(
    100deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, .38) 45%,
    rgba(255, 255, 255, .55) 50%,
    rgba(255, 255, 255, .38) 55%,
    rgba(255, 255, 255, 0) 100%
  );
  animation: glam-sweep 1.15s ease-out .28s both;
}

.design-heading.revealing {
  animation: glam-rise .5s ease-out .55s both;
}

/* Anyone who has asked their system for less motion gets none of it. */
@media (prefers-reduced-motion: reduce) {
  .frame.revealing #image,
  .frame.revealing::after,
  .design-heading.revealing {
    animation: none;
  }
}

/* Print guides. Insets are set in app.js from the server's measurements.
   Solid = trim (where the blanket is cut). Dashed = safe zone. */
.trimzone,
.safezone {
  position: absolute;
  pointer-events: none;
  border-radius: 2px;
}

.trimzone {
  border: 2px solid rgba(255, 255, 255, .95);
  outline: 1px solid rgba(27, 42, 74, .6);
}

.safezone {
  border: 2px dashed rgba(255, 255, 255, .9);
  outline: 1px dashed rgba(27, 42, 74, .55);
}

.toggle {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-top: .85rem;
  font-size: .85rem;
  font-weight: 500;
  cursor: pointer;
}

.toggle input {
  width: 1rem;
  height: 1rem;
  accent-color: var(--glam-pink);
  cursor: pointer;
}

.toggle em {
  font-style: normal;
  color: var(--gold-sand);
}

/* Edit panel, shown under a generated image */
.edit {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--petal);
}

button.secondary {
  background: #fff;
  color: var(--deep-rose);
  border: 2px solid var(--glam-pink);
}

button.secondary:hover:not(:disabled) {
  background: var(--glam-pink);
  color: #fff;
}

button.secondary:disabled {
  background: #fff;
  border-color: var(--petal);
  color: var(--petal);
}

.hint {
  margin: .6rem 0 0;
  font-size: .78rem;
  color: var(--gold-sand);
  text-align: center;
}

/* Reference photo upload */
.optional {
  font-style: normal;
  font-weight: 500;
  color: var(--gold-sand);
}

.upload {
  margin-bottom: 1.1rem;
}

input[type="file"] {
  width: 100%;
  padding: .7rem;
  font: inherit;
  font-size: .85rem;
  background: #fff;
  border: 2px dashed var(--petal);
  border-radius: 10px;
  cursor: pointer;
}

input[type="file"]::file-selector-button {
  margin-right: .75rem;
  padding: .4rem .8rem;
  font: inherit;
  font-size: .8rem;
  font-weight: 600;
  color: #fff;
  background: var(--glam-pink);
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

.thumb {
  position: relative;
  display: inline-block;
  margin-top: .7rem;
}

.thumb img {
  display: block;
  max-height: 110px;
  border-radius: 8px;
  border: 2px solid var(--petal);
}

.clear {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 24px;
  height: 24px;
  padding: 0;
  margin: 0;
  font-size: 1rem;
  line-height: 1;
  color: #fff;
  background: var(--deep-rose);
  border-radius: 50%;
}

/* Final render panel */
.final {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--petal);
}

.final .hint {
  color: var(--glam-navy);
  opacity: .7;
}

.final em {
  font-style: normal;
  font-weight: 600;
  color: var(--deep-rose);
}

.saved {
  margin: .6rem 0 0;
  font-size: .8rem;
  font-weight: 600;
}

.saved a {
  color: var(--glam-pink);
  text-decoration: none;
}

.saved a:hover { text-decoration: underline; }

/* Campaign-driven fields */
.field {
  margin-bottom: 1.1rem;
}

.field input[type="text"] {
  width: 100%;
  padding: .85rem 1rem;
  font: inherit;
  font-size: 1rem;
  color: inherit;
  background: #fff;
  border: 2px solid var(--petal);
  border-radius: 10px;
}

.field input[type="text"]:focus {
  outline: none;
  border-color: var(--glam-pink);
}

.help {
  margin: -.25rem 0 .5rem;
  font-size: .78rem;
  font-weight: 500;
  color: var(--gold-sand);
}

.blurb {
  margin: 0 0 .5rem;
  font-size: .9rem;
  line-height: 1.4;
}

/* Choice fields (style pickers and the like) */
.choices {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
}

.choice.tall {
  flex: 1 1 calc(33.333% - .4rem);
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding: .8rem .6rem;
  text-align: center;
}

.choice.tall small {
  font-size: .68rem;
  font-weight: 500;
  line-height: 1.3;
  opacity: .75;
}

@media (max-width: 460px) {
  .choice.tall { flex: 1 1 100%; }
}

/* Choices with a picture (the dinosaur styles): a small portrait of each, so
   the customer sees which is which. They stay three across on a phone, where
   stacked they would push Continue far down the screen. */
.choice.tall.has-image { position: relative; padding: .45rem .45rem .7rem; }
.choice-img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
  border-radius: 6px;
  margin: 0 0 .35rem;
  background: var(--petal);
}
.choices:has(.has-image) .choice.tall { flex: 1 1 0; min-width: 0; }

/* "Most popular" sits on the card's top edge, selected or not. */
.choice.has-badge { position: relative; }
.choice-badge {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -55%);
  padding: .15rem .55rem;
  border-radius: 999px;
  background: var(--glam-navy);
  color: #fff;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .02em;
  white-space: nowrap;
  z-index: 1;
}
.choices:has(.has-badge) { padding-top: .5rem; }

/* Everything generated this session */
.gallery {
  display: flex;
  gap: .5rem;
  margin-top: .85rem;
  overflow-x: auto;
  padding-bottom: .25rem;
}

.gallery button {
  flex: 0 0 auto;
  width: 64px;
  height: 78px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: #fff;
  border: 2px solid var(--petal);
  border-radius: 8px;
  cursor: pointer;
}

.gallery button[aria-current="true"] {
  border-color: var(--glam-pink);
}

.gallery img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* "See it on your pillow" - the design on a photograph of the real blank. */
.see-on-product {
  display: block;
  margin: .75rem auto 0;
}

.mockup-view {
  margin: .75rem 0 0;
}

.mockup-view img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12px;
}

/* Choose / try again */
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  margin-top: 1rem;
}

.actions button {
  flex: 1 1 auto;
  margin-top: 0;
}

#choose {
  flex: 2 1 60%;
  text-align: center;
  justify-content: center;
}

/* Greets the customer by their child's name the moment a design lands. Set
   above the artwork so it is read first. */
.design-heading {
  margin: 0 0 .7rem;
  font-size: 1.15rem;
  font-weight: 700;
  text-align: center;
  color: var(--deep-rose);
}

.chosen {
  margin: .85rem 0 0;
  padding: .8rem 1rem;
  font-size: .88rem;
  font-weight: 600;
  text-align: center;
  color: var(--deep-rose);
  background: #fff;
  border: 2px solid var(--glam-pink);
  border-radius: 10px;
}

/* Several reference photos */
.thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: .7rem;
}

.thumbs:empty { display: none; }

.count {
  margin: .5rem 0 0;
}

/* Glammie's starter ideas.
   Wrapped rather than scrolled: a row that scrolls sideways hides its own
   contents on a phone, and the whole point is that every option is visible
   without anyone having to discover it. */
.glammie-chips {
  display: flex;
  flex-wrap: wrap;
  gap: .45rem;
  margin: .7rem 0 .1rem;
}

.glammie-chip {
  font: inherit;
  font-size: .82rem;
  line-height: 1.25;
  text-align: left;
  padding: .45rem .8rem;
  border-radius: 999px;
  border: 1px solid var(--line, #d9cfc2);
  background: rgba(255, 255, 255, .6);
  color: inherit;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease, transform .15s ease;
}

.glammie-chip:hover {
  background: #fff;
  border-color: var(--gold, #b8862f);
}

.glammie-chip:active { transform: scale(.97); }

.glammie-chip:focus-visible {
  outline: 2px solid var(--gold, #b8862f);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .glammie-chip { transition: none; }
  .glammie-chip:active { transform: none; }
}

/* The bundle offer. Deliberately warm rather than loud - it appears at the
   moment somebody has just seen their child, and an aggressive upsell there
   sours the best thirty seconds in the whole flow. */
.bundle {
  margin-top: 1.1rem;
  padding: 1.1rem 1.2rem;
  border-radius: 12px;
  border: 1px solid var(--line, #d9cfc2);
  background: rgba(255, 255, 255, .55);
  animation: bundle-in .45s cubic-bezier(.2, .8, .3, 1) both;
}

@keyframes bundle-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

.bundle h3 { margin: 0 0 .35rem; font-size: 1.05rem; }
.bundle p  { margin: 0 0 .9rem; font-size: .9rem; opacity: .85; }

/* Declining has to be as easy to find as accepting, or the offer reads as a
   trap. It sits on its own line under the button, full width, quiet but
   unmistakably a thing you can press - not a link tucked beside it.

   It MUST set its own colour. The base `button` rule paints text #fff for the
   pink buttons, and this one has no background to sit on - so inheriting that
   left white text on a near-white panel: invisible until the cursor happened
   to cross it. The same base rule fills a button rose on hover, which turned
   the skip into a solid bar the moment it became findable. Both are overridden
   here on purpose; changing `button` later must not quietly take them back. */
.bundle-skip {
  display: block;
  width: 100%;
  background: none;
  border: 0;
  margin: .1rem 0 0;
  padding: .7rem;
  font: inherit;
  font-size: .9rem;
  font-weight: 600;
  color: var(--glam-navy);
  opacity: 1;
  cursor: pointer;
  text-decoration: underline;
}

.bundle-skip:hover:not(:disabled) {
  background: none;
  color: var(--deep-rose);
}

.bundle-skip:focus-visible {
  outline: 2px solid var(--gold, #b8862f);
  outline-offset: 2px;
  border-radius: 8px;
}

@media (prefers-reduced-motion: reduce) {
  .bundle { animation: none; }
}

/* Answering her. Hidden until she has said something, because a reply box with
   nothing to reply to is just another empty field. */
.glammie-reply {
  display: flex;
  gap: .5rem;
  margin-top: .7rem;
}

.glammie-reply-input {
  flex: 1 1 0;
  width: 100%;
  min-width: 0;
  font: inherit;
  font-size: .9rem;
  padding: .55rem .8rem;
  border-radius: 999px;
  border: 1px solid var(--line, #d9cfc2);
  background: #fff;
}

/* The box they talk to Gifty in wraps and grows as they type, rather than
   running off the side where they cannot see it (Gretchen, 2026-09-21).
   16px so an iPhone does not zoom the page when it is tapped. */
textarea.glammie-reply-input {
  display: block;
  resize: none;
  overflow: hidden;
  font-size: 1rem;
  line-height: 1.35;
  border-radius: 18px;
}
.glammie-reply { align-items: flex-end; }

.glammie-reply-input:focus-visible {
  outline: 2px solid var(--gold, #b8862f);
  outline-offset: 1px;
}

/* Every button here is width:100% by default, which squashed the input to a
   sliver and pushed Send off the right edge of a phone. */
.glammie-send {
  flex: 0 0 auto;
  width: auto;
  margin-top: 0;
  padding: .55rem 1.1rem;
  font-size: .9rem;
  border-radius: 999px;
}

/* The conversation. Her replies sit left, the customer's sit right, the way
   every messaging app on their phone already works - so nobody has to be told
   which is which. */
.glammie-thread {
  display: flex;
  flex-direction: column;
  gap: .45rem;
  margin-top: .8rem;
}

.glammie-thread:empty { margin-top: 0; }

.glammie-msg {
  display: flex;
  max-width: 100%;
  animation: glammie-msg-in .28s cubic-bezier(.2, .8, .3, 1) both;
}

.glammie-msg.them { justify-content: flex-start; }
.glammie-msg.me   { justify-content: flex-end; }

.glammie-msg .glammie-bubble {
  max-width: 82%;
  margin: 0;
  padding: .55rem .85rem;
  font-size: .9rem;
  line-height: 1.45;
  border-radius: 14px;
}

/* Hers keeps the shop's voice; theirs is quieter, because it is only an echo
   of what they just typed. */
.glammie-msg.them .glammie-bubble {
  border-bottom-left-radius: 5px;
}

.glammie-msg.me .glammie-bubble {
  border-bottom-right-radius: 5px;
  background: rgba(0, 0, 0, .05);
  border-color: transparent;
}

/* The bubble tail on the standing avatar is drawn with pseudo-elements; a
   thread bubble must not inherit them or every line grows a beak. */
.glammie-msg .glammie-bubble::before,
.glammie-msg .glammie-bubble::after { content: none; }

@keyframes glammie-msg-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* A cursor while she is still typing, and nothing once she stops. */
.glammie-bubble.typing::after {
  content: "";
  display: inline-block;
  width: .45em;
  height: 1em;
  margin-left: .08em;
  vertical-align: -.15em;
  background: currentColor;
  opacity: .55;
  animation: glammie-caret 1s steps(1) infinite;
}

@keyframes glammie-caret {
  50% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .glammie-msg { animation: none; }
  .glammie-bubble.typing::after { animation: none; opacity: .4; }
}

/* Three examples, and they should sit as chips rather than as six full-width
   rows - on a phone the long ones still take a line each, which is fine at
   three and was a wall at six. */
.glammie-chips { margin: .8rem 0 .2rem; }

/* The reply box is the first thing under her, so it gets a little more room
   than a field buried in a form would. */
.glammie-reply { margin-top: .8rem; }

/* ---------------------------------------------------------------------------
   One question at a time (?flow=steps) - the Storybook landing page.
   Everything here is scoped to body.flow-steps, so product pages, which keep
   the all-at-once form, are not touched.
   --------------------------------------------------------------------------- */

body.flow-steps { padding: .75rem 1.25rem 1.5rem; }
body.flow-steps header { display: none; }
body.flow-steps main { max-width: 440px; }

.flow-steps .step-back {
  width: auto;
  margin: 0 0 .35rem -.4rem;
  padding: .45rem .6rem;
  font-size: .9rem;
  font-weight: 600;
  color: var(--deep-rose);
  background: none;
}
.flow-steps .step-back:hover:not(:disabled) { background: var(--petal); }
.flow-steps .step-back::before { content: "\2039"; margin-right: .35rem; font-size: 1.2rem; line-height: 0; }

.flow-steps .step-bar { display: flex; gap: 5px; margin: 0 0 1rem; }
.flow-steps .step-bar i { flex: 1; height: 4px; border-radius: 2px; background: var(--petal); }
.flow-steps .step-bar i.on { background: var(--glam-pink); }

.flow-steps .step-heading {
  margin: .25rem 0 .35rem;
  font-size: 1.4rem;
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -.015em;
  text-wrap: balance;
  color: var(--glam-navy);
}
.flow-steps .field > .help,
.flow-steps .step-why { margin: 0 0 1rem; font-size: .9rem; color: #4B5872; }

/* The photo step: one big box to press. */
.flow-steps .upload input[type="file"] {
  position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none;
}
.flow-steps .drop-tile {
  display: grid;
  place-content: center;
  justify-items: center;
  gap: .15rem;
  height: 260px;
  margin: 0;
  text-align: center;
  background: #fff;
  border: 2px dashed #EABBD1;
  border-radius: 18px;
  cursor: pointer;
}
.flow-steps .drop-tile b { font-size: 1rem; }
.flow-steps .drop-tile small { font-size: .8rem; font-weight: 500; color: #7D879B; }
.flow-steps .drop-plus {
  display: grid; place-items: center;
  width: 56px; height: 56px; margin-bottom: .5rem;
  font-size: 2rem; font-weight: 500; line-height: 1;
  color: var(--glam-pink); background: var(--blush-white); border-radius: 50%;
}
.flow-steps .drop-tile { position: relative; overflow: hidden; height: 300px; }
.flow-steps .drop-empty { display: grid; justify-items: center; gap: .15rem; }
.flow-steps .drop-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.flow-steps .drop-tile.has { border-style: solid; border-color: #fff; }
.flow-steps .drop-tile.has .drop-empty { visibility: hidden; }
.flow-steps .drop-swap {
  display: none;
  position: absolute; top: 10px; right: 10px;
  padding: .4rem .75rem;
  font-size: .75rem; font-weight: 700; color: var(--glam-navy);
  background: rgba(255, 255, 255, .92); border-radius: 100px;
}
.flow-steps .drop-tile.has .drop-swap { display: block; }

/* The name as it will read on the design. */
.flow-steps .name-preview {
  margin-top: 1rem; padding: 1.1rem .9rem;
  text-align: center; background: #fff;
  border: 1px solid #F3DCE7; border-radius: 16px;
}
.flow-steps .name-preview small {
  display: block; margin-bottom: .5rem;
  font-size: .7rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: #7D879B;
}
.flow-steps .lettering {
  min-height: 1.1em;
  font-size: 2.1rem; font-weight: 800; letter-spacing: .04em; line-height: 1.05;
  overflow-wrap: anywhere; color: var(--glam-pink);
}
.flow-steps .lettering:empty::before { content: "THEIR NAME"; opacity: .3; }
.flow-steps .lettering-epic { color: #8B8577; text-shadow: 0 2px 0 #5E594E, 0 4px 6px rgba(0, 0, 0, .25); }
.flow-steps .lettering-animated { color: #FFC634; -webkit-text-stroke: 2px #1E5FB8; text-shadow: 0 4px 0 #1E5FB8; }
.flow-steps .lettering-comic { color: #FFD23F; -webkit-text-stroke: 2px #C8102E; text-shadow: 3px 3px 0 #1B1B1B; }

/* The reveal: their photo as a polaroid on the left, level with the rider.
   It used to sit in the bottom corner, on top of the child's name, which is
   always along the bottom of the design (Gretchen, 2026-09-18). */
.flow-steps .reveal-polaroid {
  position: absolute; left: 10px; top: 30%; z-index: 3;
  width: 22%; max-width: 92px;
  padding: 4px 4px 15px;
  line-height: 1; background: #fff; border-radius: 5px;
  box-shadow: 0 8px 18px rgba(27, 42, 74, .3);
  transform: rotate(-5deg);
}
.flow-steps .reveal-polaroid img { display: block; width: 100%; aspect-ratio: 3 / 4; object-fit: cover; border-radius: 3px; box-shadow: none; }
.flow-steps .reveal-polaroid em {
  position: absolute; left: 0; right: 0; bottom: 3px;
  text-align: center; font-style: normal; font-size: 7.5px; font-weight: 700; letter-spacing: .1em; color: #4B5872;
}

/* The name step. */
.flow-steps .field input[type="text"] {
  font-size: 1.25rem;
  font-weight: 700;
  padding: 1rem;
  border-radius: 14px;
}

/* The size step: a column of cards, price on the right. */
.flow-steps #orientation { flex-direction: column; gap: .6rem; }
.flow-steps #orientation .choice {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.1rem;
  font-size: 1rem;
  font-weight: 700;
  text-align: left;
  color: var(--glam-navy);
  border-radius: 14px;
}
/* "Most popular" beside a size's name, from ?popular=. */
.size-badge {
  display: inline-block;
  margin-left: .5rem;
  padding: .1rem .5rem;
  border-radius: 999px;
  background: var(--glam-navy);
  color: #fff;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .02em;
  vertical-align: middle;
  white-space: nowrap;
}
.flow-steps #orientation .choice:has(input:checked) {
  background: #fff;
  color: var(--glam-navy);
  border-color: var(--glam-pink);
  box-shadow: 0 6px 16px -8px rgba(240, 78, 154, .6);
}
.flow-steps .size-price { font-weight: 800; }

.flow-steps .step-next,
.flow-steps #generate {
  margin-top: 1.25rem;
  padding: 1rem;
  font-size: 1.05rem;
  font-weight: 700;
  border-radius: 14px;
}

/* GLAM Wholesale: a retailer's earlier designs, to order again. */
.reorder{margin:0 0 1.5rem;padding:1rem;background:#fff;border:1px solid #FCCDE3;border-radius:14px}
.reorder h2{margin:0 0 .25rem;font-size:1.05rem}
.reorder > p{margin:0 0 .75rem;font-size:.9rem}
.reorder-list{display:flex;gap:.75rem;overflow-x:auto;padding-bottom:.25rem}
.reorder-card{flex:0 0 140px;display:flex;flex-direction:column;gap:.4rem}
.reorder-card img{width:140px;height:140px;object-fit:contain;background:#FFF2F8;border-radius:10px}
.reorder-card p{margin:0;font-size:.78rem;line-height:1.3}
/* style.css makes every button full width; these sit in a row. */
.reorder-card button{width:auto;padding:.5rem .75rem;font-size:.85rem}
