/* DjinnStudio — one stylesheet for every page.
   Palette is taken from the mascot art: plum night, djinn teal, brass topknot, parchment, ember. */

:root {
  --night: #17112a;
  --dusk: #231a3b;
  --dusk-edge: #3a2d58;
  --parchment: #eee4ce;
  --parchment-dim: #b9ae9a;
  --brass: #d6ae5c;
  --brass-deep: #b58c3c;
  --teal: #5fb0a5;
  --ember: #f0753a;

  --display: "Caprasimo", "Cooper Black", Georgia, serif;
  --body: "Alegreya", Georgia, "Times New Roman", serif;

  --measure: 34em;
  --gutter: clamp(1rem, 4vw, 3rem);
  --wide: 72rem;

  /* Motion. Strong curves; the built-in ones are too soft to read as deliberate. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);       /* things arriving, press feedback */
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);   /* things moving across the screen */
  --ease-sine: cubic-bezier(0.37, 0, 0.63, 1);      /* constant gentle drift (hovering) */
  --dim: #9a90a8;                                   /* inactive labels; 5.9:1 on --night */
}

*, *::before, *::after { box-sizing: border-box; }

html { color-scheme: dark; }

body {
  margin: 0;
  background: var(--night);
  color: var(--parchment);
  font-family: var(--body);
  font-size: 1.1875rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }

a {
  color: var(--brass);
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
  transition: color 150ms ease;
}
a:hover { color: var(--parchment); }

h1, h2, h3 { text-wrap: balance; }
p, li, figcaption { text-wrap: pretty; }

:focus-visible {
  outline: 3px solid var(--teal);
  outline-offset: 3px;
  border-radius: 4px;
}

.wrap {
  width: 100%;
  max-width: var(--wide);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

/* ---------- Site header ---------- */

.site-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 1.5rem;
  padding-bottom: 1.5rem;
}

.site-head .mark {
  font-family: var(--display);
  font-size: 1.375rem;
  color: var(--parchment);
  text-decoration: none;
}

.site-head nav { display: flex; gap: 1.5rem; font-size: 1.0625rem; }
.site-head nav a { color: var(--parchment-dim); text-decoration: none; }
.site-head nav a:hover, .site-head nav a[aria-current="page"] { color: var(--parchment); }
.site-head nav a[aria-current="page"] {
  text-decoration: underline 2px var(--brass);
  text-underline-offset: 0.4em;
}

/* ---------- Home hero: the smoke trail ---------- */

.hero {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  align-items: center;
  gap: 2rem;
  min-height: min(44rem, 88vh);
  padding-bottom: 3rem;
}

.hero h1 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(3.25rem, 9.5vw, 7.5rem);
  line-height: 0.92;
  letter-spacing: -0.015em;
  margin: 0 0 1.75rem;
}

.hero h1 span { display: block; }
.hero h1 span:last-child { padding-left: 0.6em; }

.hero .lede {
  max-width: 24em;
  font-size: clamp(1.25rem, 2.2vw, 1.5rem);
  line-height: 1.45;
  margin: 0;
  color: var(--parchment);
}

.stage {
  justify-self: end;
  width: 100%;
  max-width: 34rem;
}

.lamp {
  position: relative;
  aspect-ratio: 5 / 6;
  width: 100%;
}

.lamp svg.smoke {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.smoke path {
  fill: none;
  stroke: var(--parchment);
  stroke-linecap: round;
  opacity: 0.18;
}
.smoke path.thin { opacity: 0.1; }

/* Three fixed places: a large one top left, and two small ones on the right. The djinns
   take turns in the large place. Each turn they all move one place round the circle
   (top left to bottom right, bottom right to top right, top right to top left).
   Every djinn is laid out at the large place's size (left 0, top 4%, width 54%), and the
   small places are reached with transform: translate + scale from the top-left corner.
   That keeps the move on the compositor (no layout per frame). cqi is 1% of the lamp's
   width; the lamp is 5:6, so 1% of its height is 1.2cqi.
     slot-1: left 63%, top 4%,  width 31%  ->  translate(63cqi, 0)       scale(31/54)
     slot-2: left 59%, top 50%, width 33%  ->  translate(59cqi, 55.2cqi) scale(33/54)
   The move owns transform and filter. The entrance and the idle float own opacity and
   the separate translate property, so they never fight.

   The move uses a spring (soft start, 1.5% overshoot, settled by about 0.8s), sampled
   into linear(); browsers without linear() fall back to --ease-in-out. The three
   djinns leave 70ms apart, the one taking the large place first, so they don't move
   in lockstep. */
.lamp {
  container-type: inline-size;
  --spring: linear(0, 0.042, 0.143, 0.271, 0.405, 0.532, 0.645, 0.741, 0.819, 0.88, 0.926,
    0.96, 0.983, 0.999, 1.008, 1.013, 1.015, 1.015, 1.014, 1.012, 1.01, 1.008, 1.006, 1.005,
    1.003, 1.002, 1.001, 1.001, 1);
}
.lamp .djinn {
  position: absolute;
  z-index: 1;
  left: 0; top: 4%; width: 54%;
  transform-origin: 0 0;
  filter: drop-shadow(0 1.75rem 2.25rem rgba(5, 2, 14, 0.6)) brightness(0.8) saturate(0.85);
  transition: transform 0.9s var(--ease-in-out), filter 0.6s ease;
  transition: transform 0.9s var(--spring), filter 0.6s ease;
  will-change: transform;
}
.lamp .djinn.slot-0 {
  z-index: 3;
  transform: none;
  filter: drop-shadow(0 1.75rem 2.25rem rgba(5, 2, 14, 0.6)) brightness(1) saturate(1);
}
.lamp .djinn.slot-1 { transform: translate(63cqi, 0) scale(0.5741); transition-delay: 70ms; }
.lamp .djinn.slot-2 { transform: translate(59cqi, 55.2cqi) scale(0.6111); transition-delay: 140ms; }

.spot-names {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.25rem 1.5rem;
  margin: 0.5rem 0 0;
  font-family: var(--display);
  font-size: 1.25rem;
}
.spot-names a {
  color: var(--dim);
  text-decoration: underline 2px transparent;
  text-underline-offset: 0.35em;
  transition: color 300ms ease, text-decoration-color 300ms ease;
}
.spot-names a.is-spot { color: var(--brass); text-decoration-color: currentColor; }
@media (hover: hover) and (pointer: fine) {
  .spot-names a:hover { color: var(--brass); }
}

@media (prefers-reduced-motion: no-preference) {
  .smoke path {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: draw 1.2s var(--ease-out) forwards;
  }
  .smoke path:nth-child(2) { animation-delay: 0.1s; }
  .smoke path:nth-child(3) { animation-delay: 0.2s; }
  /* Each djinn rises out of the smoke, then keeps hovering: a slow bob on translate that
     starts the moment the entrance ends, so there is no jump between them. Different
     periods keep the three out of step, the way things floating in air are. */
  .lamp .djinn {
    opacity: 0;
    translate: 0 1.5rem;
  }
  .lamp .djinn.fire {
    animation: appear 0.7s var(--ease-out) 0.25s forwards,
               float 4.2s var(--ease-sine) 0.95s infinite alternate;
  }
  .lamp .djinn.help {
    animation: appear 0.7s var(--ease-out) 0.35s forwards,
               float 4.8s var(--ease-sine) 1.05s infinite alternate;
  }
  .lamp .djinn.profit {
    animation: appear 0.7s var(--ease-out) 0.45s forwards,
               float 5.4s var(--ease-sine) 1.15s infinite alternate;
  }
}

/* Reduced motion: the djinns change places without travelling. Color changes stay,
   since they are what tells you which app is in the large place. */
@media (prefers-reduced-motion: reduce) {
  .lamp .djinn { transition: none; }
}

@keyframes draw { to { stroke-dashoffset: 0; } }
@keyframes appear { to { opacity: 1; translate: 0 0; } }
@keyframes float { from { translate: 0 0; } to { translate: 0 -0.6rem; } }

/* ---------- App rows on the home page ---------- */

.apps { padding-block: 2rem 1rem; }

.apps > h2, .about h2 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2rem, 4vw, 2.75rem);
  line-height: 1.1;
  margin: 0 0 2.5rem;
}

.app {
  display: grid;
  grid-template-columns: 13rem minmax(0, 1fr);
  gap: 2.5rem;
  align-items: start;
  padding: 2.5rem 0;
  border-top: 1px solid var(--dusk-edge);
}

.app:last-child { border-bottom: 1px solid var(--dusk-edge); }

.app .portrait { width: 100%; max-width: 13rem; }

.app h3 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2rem, 3.5vw, 2.5rem);
  line-height: 1;
  margin: 0 0 0.25rem;
}

.app h3 a { color: inherit; text-decoration: none; }
@media (hover: hover) and (pointer: fine) {
  .app h3 a:hover { color: var(--brass); }
}

.app .kind { margin: 0 0 1rem; color: var(--parchment-dim); font-style: italic; }
.app p { max-width: var(--measure); margin: 0 0 1rem; }

.app.fire h3 { color: #f6a072; }
.app.help h3 { color: #8fd0c6; }
.app.profit h3 { color: #a9d38a; }

.actions { display: flex; flex-wrap: wrap; gap: 0.75rem 1.5rem; align-items: center; margin-top: 1.5rem; }

.button {
  display: inline-block;
  padding: 0.7rem 1.4rem 0.75rem;
  background: var(--brass);
  color: var(--night);
  font-family: var(--body);
  font-weight: 700;
  font-size: 1.0625rem;
  text-decoration: none;
  border-radius: 999px;
  box-shadow: inset 0 -3px 0 var(--brass-deep);
  transition: background-color 150ms ease, color 150ms ease, transform 160ms var(--ease-out);
}
.button:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  .button:hover { background: var(--parchment); color: var(--night); }
}

/* ---------- About ---------- */

.about {
  display: grid;
  grid-template-columns: 13rem minmax(0, 1fr);
  gap: 2.5rem;
  padding-block: 4rem 5rem;
}
.about h2 { margin: 0; font-size: 1.75rem; }
.about p { max-width: var(--measure); margin: 0 0 1rem; }

/* ---------- App detail pages ---------- */

.app-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 16rem;
  gap: 3rem;
  align-items: end;
  padding-block: 2rem 3rem;
  border-bottom: 1px solid var(--dusk-edge);
}

.app-hero h1 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(3rem, 8vw, 5.5rem);
  line-height: 0.95;
  margin: 0 0 1rem;
}
.app-hero.fire h1 { color: #f6a072; }
.app-hero.help h1 { color: #8fd0c6; }
.app-hero.profit h1 { color: #a9d38a; }

.app-hero .lede { max-width: 26em; font-size: clamp(1.2rem, 2vw, 1.4rem); line-height: 1.5; margin: 0; }
.app-hero .portrait { justify-self: end; width: 100%; filter: drop-shadow(0 1.25rem 2rem rgba(5, 2, 14, 0.55)); }

.fine { color: var(--parchment-dim); font-size: 1rem; margin: 0.75rem 0 0; max-width: var(--measure); }

.detail { display: grid; grid-template-columns: 13rem minmax(0, 1fr); gap: 2.5rem; padding: 3rem 0; border-bottom: 1px solid var(--dusk-edge); }
.detail h2 { font-family: var(--display); font-weight: 400; font-size: 1.625rem; line-height: 1.15; margin: 0; }
.detail p, .detail ul, .detail ol { max-width: var(--measure); margin: 0 0 1rem; }
.detail ul, .detail ol { padding-left: 1.1em; }
.detail li { margin-bottom: 0.5rem; }
.detail li::marker { color: var(--brass); }
.detail ol li::marker { font-family: var(--display); }
.detail strong { color: #fff6e3; }

.shots { display: grid; gap: 2rem; }
.shots figure { margin: 0; }
.shots button {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--dusk-edge);
  border-radius: 10px;
  overflow: hidden;
  background: var(--dusk);
  cursor: zoom-in;
  transition: border-color 150ms ease, transform 160ms var(--ease-out);
}
.shots button:active { transform: scale(0.99); }
@media (hover: hover) and (pointer: fine) {
  .shots button:hover { border-color: var(--brass-deep); }
}
.shots figcaption { margin-top: 0.6rem; color: var(--parchment-dim); font-size: 1rem; }

/* Contact form */
.contact-hero { grid-template-columns: minmax(0, 1fr); }
.contact-form { max-width: var(--measure); }
.contact-form .field { margin: 0 0 1.25rem; }
.contact-form label { display: block; margin: 0 0 0.35rem; font-weight: 700; }
.contact-form input, .contact-form select, .contact-form textarea {
  display: block;
  width: 100%;
  padding: 0.65rem 0.8rem;
  font: inherit;
  font-size: 1.0625rem;
  color: var(--parchment);
  background: var(--dusk);
  border: 1px solid var(--dusk-edge);
  border-radius: 8px;
  transition: border-color 150ms ease;
}
.contact-form textarea { resize: vertical; min-height: 9rem; line-height: 1.5; }
.contact-form select { appearance: none; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--brass) 50%),
                    linear-gradient(135deg, var(--brass) 50%, transparent 50%);
  background-position: calc(100% - 1.15rem) 55%, calc(100% - 0.8rem) 55%;
  background-size: 0.35rem 0.35rem;
  background-repeat: no-repeat;
  padding-right: 2.25rem;
}
.contact-form input:focus-visible, .contact-form select:focus-visible, .contact-form textarea:focus-visible {
  outline: 3px solid var(--teal);
  outline-offset: 1px;
  border-color: var(--teal);
}
.contact-form .hint { margin: 0.35rem 0 0; color: var(--parchment-dim); font-size: 1rem; }
.contact-form .trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.contact-form .actions { margin-top: 0.5rem; }
.contact-form button.button { border: 0; cursor: pointer; }
.contact-form button.button:disabled { opacity: 0.6; cursor: progress; }
.form-status { margin: 1rem 0 0; min-height: 1.6em; color: #a9d38a; }
.form-status.is-error { color: #f6a072; }
.form-status:empty { margin: 0; min-height: 0; }

/* Embedded videos. Two sit side by side with a gap; on a phone they stack. */
.videos { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2rem; }
.videos.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.video { margin: 0; }
.video iframe {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--dusk-edge);
  border-radius: 10px;
  background: var(--dusk);
}
.video figcaption { margin-top: 0.6rem; color: var(--parchment-dim); font-size: 1rem; }
@media (max-width: 40rem) {
  .videos.two { grid-template-columns: minmax(0, 1fr); }
}

/* Lightbox for screenshots. It fades in with the picture growing slightly from the
   centre, and closes faster than it opens. The dialog itself only fades: a transform on
   it would make the fixed Close button position against the dialog, not the window. */
dialog.viewer {
  max-width: min(96vw, 110rem);
  max-height: 94vh;
  padding: 0;
  border: 0;
  background: transparent;
  opacity: 0;
  transition: opacity 150ms ease, overlay 150ms allow-discrete, display 150ms allow-discrete;
}
dialog.viewer[open] { opacity: 1; transition-duration: 220ms; }
dialog.viewer::backdrop {
  background: rgba(10, 6, 20, 0.88);
  opacity: 0;
  transition: opacity 150ms ease, overlay 150ms allow-discrete, display 150ms allow-discrete;
}
dialog.viewer[open]::backdrop { opacity: 1; transition-duration: 220ms; }
dialog.viewer img {
  max-height: 90vh; width: auto; margin: 0 auto; border-radius: 8px;
  transform: scale(0.96);
  transition: transform 150ms var(--ease-out);
}
dialog.viewer[open] img { transform: none; transition-duration: 220ms; }
@starting-style {
  dialog.viewer[open], dialog.viewer[open]::backdrop { opacity: 0; }
  dialog.viewer[open] img { transform: scale(0.96); }
}
@media (prefers-reduced-motion: reduce) {
  dialog.viewer img, dialog.viewer[open] img { transform: none; }
}
dialog.viewer button {
  position: fixed;
  top: 1rem;
  right: 1rem;
  font: 700 1rem var(--body);
  padding: 0.5rem 1rem;
  border: 0;
  border-radius: 999px;
  background: var(--parchment);
  color: var(--night);
  cursor: pointer;
  transition: transform 160ms var(--ease-out);
}
dialog.viewer button:active { transform: scale(0.97); }

/* ---------- Footer ---------- */

.site-foot {
  padding-top: 2rem;
  padding-bottom: 3rem;
  color: var(--parchment-dim);
  font-size: 1rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem 2rem;
}
.site-foot p { margin: 0; }
.site-foot a { color: var(--parchment-dim); }
.site-foot a:hover { color: var(--parchment); }
.site-foot .links { display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; }

/* ---------- Narrow screens ---------- */

@media (max-width: 52rem) {
  .hero { grid-template-columns: 1fr; min-height: 0; gap: 1rem; padding-top: 1rem; }
  .stage { justify-self: center; max-width: 24rem; order: -1; }
  .app, .about, .detail { grid-template-columns: 1fr; gap: 1.25rem; }
  .app .portrait { max-width: 8.5rem; }
  .app-hero { grid-template-columns: 1fr; gap: 1.5rem; }
  .app-hero .portrait { justify-self: start; max-width: 10rem; order: -1; }
}

@media (max-width: 34rem) {
  .site-head { flex-wrap: wrap; row-gap: 0.5rem; }
  .site-head nav { gap: 1.25rem; }
  .stage { max-width: 20rem; }
  .spot-names { font-size: 1.0625rem; gap: 0.25rem 1rem; }
}

@media (max-width: 30rem) {
  body { font-size: 1.0625rem; }
}
