/*
 * photo.kantora.ge: the landing page, /paid, /<token> and an event's gallery /e/<token>. Phone first; the desktop layout only widens it.
 *
 * The look is the printed strip's: cream paper, ink, and the brick-red rule that hangs under the wordmark.
 * That rule comes back wherever the page needs a mark (the language in use, under a heading), so it stays the
 * one accent. The colours are the print's: background, ink and accent in
 * booth/src/kantora_booth/assets/frames/kantora-classic/frame.json.
 */

:root {
  --paper: #f2eadb;
  --ink: #1a1614;
  --brick: #b4432e;
  --ink-soft: #6a5e54; /* 5.3:1 on the paper */
  --hair: #d6cab2;
  --lift: #faf5e9; /* a step above the paper: the delete question */
  --shade: #e4d9c3; /* the tone of a photo that has not arrived yet */
  --danger: #b4432e;
  --on-danger: #fff9ee;
  --text:
    'Noto Sans Georgian', 'Noto Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --figures: 'Archivo', 'Noto Sans Georgian', 'Noto Sans', system-ui, sans-serif;
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #1a1614;
    --ink: #f2eadb;
    --brick: #d4604a; /* 4.8:1 on the dark paper; the print's brick is too dim to read there */
    --ink-soft: #b5a995;
    --hair: #3a322c;
    --lift: #251f1b;
    --shade: #2a2420;
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 450 1.0625rem / 1.55 var(--text);
  font-stretch: 100%;
  -webkit-font-smoothing: antialiased;
}

h1,
h2,
p,
figure,
ul {
  margin: 0;
}

ul {
  padding: 0;
  list-style: none;
}

img,
svg {
  display: block;
  max-width: 100%;
}

button {
  font: inherit;
  color: inherit;
}

[hidden] {
  /* biome-ignore lint/complexity/noImportantStyles: `hidden` must win over every display a class sets */
  display: none !important;
}

:focus-visible {
  outline: 3px solid var(--brick);
  outline-offset: 3px;
}

h1:focus {
  outline: none; /* focus lands on a heading when a state changes, for screen readers; nothing to ring */
}

/* ---- languages: each string is in the HTML three times, and the one that matches <html lang> shows ---- */

.t {
  display: none;
}

html:lang(ka) .t:lang(ka),
html:lang(en) .t:lang(en),
html:lang(ru) .t:lang(ru) {
  display: revert;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- type ---- */

h1,
h2 {
  font-weight: 800;
  font-stretch: 84%;
  line-height: 1.08;
  letter-spacing: -0.012em;
  text-wrap: balance;
}

.date {
  font: 500 0.9375rem / 1 var(--figures);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.12em; /* the strip's date line */
}

.rule {
  display: block;
  width: 2.75rem;
  height: 3px;
  background: var(--brick);
}

/* ---- page frame ---- */

.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

@supports (min-height: 100svh) {
  .page {
    min-height: 100svh; /* the screen without the browser's toolbars, so the page does not scroll for nothing */
  }
}

.bar,
main {
  width: 100%;
  max-width: 62rem;
  margin-inline: auto;
  padding-inline: max(1.25rem, env(safe-area-inset-left)) max(1.25rem, env(safe-area-inset-right));
}

.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-block: max(0.5rem, env(safe-area-inset-top)) 0.25rem;
}

.bar--end {
  justify-content: flex-end;
}

main {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding-bottom: 3rem;
}

.brand {
  display: block;
  color: inherit;
  line-height: 0;
}

.wordmark {
  width: auto;
  height: 1.3rem;
}

.langs {
  display: flex;
}

.langs button {
  position: relative;
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0 0.65rem;
  border: 0;
  border-radius: 0.375rem;
  background: none;
  color: var(--ink-soft);
  /* The three labels are always on screen, in three scripts. In the system font they cost no download: a web font
     would pull the Georgian and the Cyrillic file for every visitor, whatever language the page is in. */
  font:
    700 0.8125rem / 1 system-ui,
    -apple-system,
    'Segoe UI',
    Roboto,
    sans-serif;
  letter-spacing: 0.06em;
  cursor: pointer;
}

.langs button[aria-pressed='true'] {
  color: var(--ink);
}

.langs button[aria-pressed='true']::after {
  content: '';
  position: absolute;
  inset: auto 0.65rem 0.6rem;
  height: 3px;
  background: var(--brick); /* the strip's rule marks the language in use */
}

/* ---- buttons ---- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  min-height: 3rem;
  padding: 0.55rem 1.1rem;
  border: 1.5px solid var(--ink);
  border-radius: 0.375rem;
  background: transparent;
  color: var(--ink);
  font: 650 1rem / 1.2 var(--text);
  text-align: center;
  cursor: pointer;
  transition:
    background-color 0.15s,
    color 0.15s,
    transform 0.08s;
}

.btn:active {
  transform: translateY(1px);
}

.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.btn[aria-busy='true'] {
  opacity: 0.7;
  cursor: progress;
}

.btn--solid {
  background: var(--ink);
  color: var(--paper);
}

.btn--danger {
  border-color: var(--danger);
  background: var(--danger);
  color: var(--on-danger);
}

.btn--quiet {
  min-height: 2.75rem;
  padding-inline: 0;
  border-color: transparent;
  font-weight: 600;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.3em;
}

.icon {
  flex: none;
  width: 1.25em;
  height: 1.25em;
}

@media (hover: hover) {
  .btn:not(.btn--solid, .btn--danger, .btn--quiet):hover {
    background: var(--ink);
    color: var(--paper);
  }

  .btn--solid:hover {
    /* the danger red, not --brick: light type on it is 5.6:1 in both colour schemes, on the lighter dark-mode brick 3.6:1 */
    background: var(--danger);
    border-color: var(--danger);
    color: var(--on-danger);
  }
}

/* ---- /paid ---- */

.paid,
.landing,
.state {
  margin-block: auto;
  display: grid;
  align-content: center; /* .paid and .landing are the <main> itself, which grows: keep the rows together */
  justify-items: center;
  text-align: center;
}

.paid {
  gap: 1.1rem;
  padding-bottom: 1.5rem;
}

/*
 * /paid says one of three things. site.js puts data-outcome="failed" on <html> when the address says the payment
 * was declined or expired, and data-outcome="pending" for any other status but approved; these groups (display:
 * contents: their children are the page's own grid items) swap: no seal, no date, no word "Paid" for someone whose
 * payment did not happen, or is not known to have.
 */
.outcome {
  display: contents;
}

.outcome[data-for='failed'],
.outcome[data-for='pending'],
html[data-outcome] .outcome[data-for='paid'] {
  display: none;
}

html[data-outcome='failed'] .outcome[data-for='failed'],
html[data-outcome='pending'] .outcome[data-for='pending'] {
  display: contents;
}

/* "Paid" is one word on one line, sized so that even the longest (Georgian, below) fits a 320 px phone. */
[data-for='paid'] h1 {
  font-size: clamp(3rem, 17vw, 5.75rem);
}

[data-for='failed'] h1,
[data-for='pending'] h1 {
  font-size: clamp(2rem, 9vw, 2.75rem);
}

.lede {
  max-width: 22ch;
  font-size: 1.4rem;
  font-weight: 650;
  line-height: 1.3;
  text-wrap: balance;
}

.note {
  max-width: 30ch;
  color: var(--ink-soft);
  font-size: 1rem;
}

.colophon {
  display: grid;
  justify-items: center;
  gap: 0.9rem;
  margin-top: 1.25rem;
}

.seal {
  width: min(62vw, 15rem);
  height: auto;
  margin-bottom: 0.5rem;
  color: var(--brick);
  transform: rotate(-7deg);
}

.seal__text {
  font: 700 21px / 1 var(--figures);
  font-stretch: 75%;
  fill: currentColor;
}

.seal__check {
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}

/* The one moment of motion on the page: the stamp lands, then the tick is drawn. */
.seal {
  animation: stamp 0.55s cubic-bezier(0.2, 0.9, 0.3, 1.15) both;
}

.seal__check {
  animation: draw 0.45s 0.3s ease-out both;
}

@keyframes stamp {
  from {
    opacity: 0;
    transform: rotate(-13deg) scale(1.2);
  }
}

@keyframes draw {
  from {
    stroke-dashoffset: 1;
  }
}

/* ---- landing ---- */

.landing {
  gap: 1.1rem;
  max-width: 30rem;
  padding-bottom: 1.5rem;
}

.landing .wordmark {
  width: min(78vw, 22rem);
  height: auto;
  margin-bottom: 0.9rem;
}

.landing h1 {
  font-size: clamp(1.9rem, 8vw, 2.4rem);
}

/* ---- /<token>: the states other than the photos ---- */

.state {
  gap: 1rem;
  width: min(100%, 26rem);
  margin-inline: auto; /* narrower than <main> on a wide screen: in the middle of it, not against its left edge */
  padding-bottom: 3rem;
}

.state h1 {
  font-size: clamp(2rem, 9vw, 2.75rem);
}

.state > .btn {
  margin-top: 0.75rem;
}

.js .nojs {
  display: none; /* only for visitors whose scripts do not run */
}

/* A link the function does not know may only be too new: the booth shows the QR code while it still uploads. */
.soon {
  display: grid;
  justify-items: center;
  gap: 0.75rem;
  margin-top: 0.75rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--hair);
}

/* ---- /<token>: the photos ---- */

.gallery {
  display: grid;
  gap: 1.75rem;
  grid-template-areas:
    'intro'
    'strip'
    'grid'
    'dock'
    'trust';
  padding-top: 1rem;
}

.gallery--loading {
  display: none; /* without scripts there is nothing to wait for */
}

.js .gallery--loading {
  display: grid;
}

.intro {
  grid-area: intro;
  display: grid;
  gap: 0.7rem;
  align-content: start;
}

.intro h1 {
  font-size: clamp(2.5rem, 12vw, 3.5rem);
}

.meta,
.hint {
  max-width: 36ch;
  color: var(--ink-soft);
  font-size: 0.9375rem;
}

.meta time {
  color: var(--ink);
  font-family: var(--figures);
  font-weight: 600;
  letter-spacing: 0.05em;
  font-variant-numeric: tabular-nums;
}

.strip-slot {
  grid-area: strip;
  justify-self: center;
  width: min(66vw, 16rem);
}

.strip {
  display: grid;
  gap: 0.75rem;
}

.frame {
  position: relative;
  overflow: hidden;
  background: var(--shade);
}

.frame--strip {
  aspect-ratio: 31 / 96; /* the printed strip: 620 x 1920 */
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--ink) 10%, transparent),
    0 1px 2px rgb(26 22 20 / 0.16),
    0 18px 32px -14px rgb(26 22 20 / 0.45);
}

.frame--photo {
  aspect-ratio: 3 / 2;
}

.frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.35s;
}

.frame--strip img {
  object-fit: contain;
}

.frame--photo img {
  object-fit: cover;
}

.frame.is-loaded img {
  opacity: 1;
}

/* A photo that would not load says so where it should be, instead of leaving an empty frame. */
.broken {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 0.4rem;
  padding: 0.75rem;
  color: var(
    --ink
  ); /* on --shade the softer ink is 4.49:1 in the light scheme, a hair under what small text needs */
  font-size: 0.8125rem;
  line-height: 1.3;
  text-align: center;
}

.broken .icon {
  width: 1.75rem;
  height: 1.75rem;
  color: var(--ink-soft); /* a picture, not text: it needs 3:1 */
}

/* The strip comes out of the slot: it slides down into place once it has arrived. */
.frame--strip.is-loaded {
  animation: emerge 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

@keyframes emerge {
  from {
    opacity: 0;
    transform: translateY(-1.5rem);
  }
}

.grid {
  grid-area: grid;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem 0.75rem;
  align-content: start;
}

.cell,
.strip {
  grid-template-columns: minmax(
      0,
      1fr
    ); /* an implicit auto column would grow to its widest label instead of shrinking */
}

.cell {
  display: grid;
  gap: 0.6rem;
  align-content: start;
}

/* Two columns on a 320 px phone leave 134 px, which holds the Georgian "ჩამოტვირთვა" but not that and the arrow. */
@media (max-width: 22rem) {
  .cell .icon {
    display: none;
  }
}

.cell .btn,
.strip .btn {
  min-height: 2.75rem;
  padding-inline: 0.5rem;
  font-size: 0.9375rem;
}

.dock {
  grid-area: dock;
  position: sticky;
  bottom: 0;
  z-index: 2;
  margin-inline: -1.25rem;
  padding: 1.25rem 1.25rem calc(0.75rem + env(safe-area-inset-bottom));
  background: linear-gradient(to bottom, transparent, var(--paper) 1.25rem);
}

.dock .btn {
  width: 100%;
  min-height: 3.5rem;
  font-size: 1.0625rem;
}

.trust {
  grid-area: trust;
  display: grid;
  gap: 0.5rem;
  justify-items: start;
  padding-top: 1.25rem;
  border-top: 1px solid var(--hair);
  color: var(--ink-soft);
  font-size: 0.9375rem;
}

.trust .btn {
  color: var(--ink);
}

.hint {
  display: none;
}

@media (pointer: coarse) {
  .hint {
    display: block;
  }
}

@media (min-width: 56rem) {
  .gallery {
    grid-template-columns: 17rem minmax(0, 1fr);
    grid-template-rows: auto auto auto 1fr;
    grid-template-areas:
      'strip intro'
      'strip grid'
      'strip dock'
      'strip trust';
    column-gap: 3.5rem;
    row-gap: 2rem;
    padding-top: 1.5rem;
  }

  .strip-slot {
    position: sticky;
    top: 1.5rem;
    align-self: start;
    justify-self: start;
    width: min(100%, calc((100svh - 9.5rem) * 31 / 96));
  }

  .dock {
    position: static;
    margin: 0;
    padding: 0;
    background: none;
  }

  .dock .btn {
    width: auto;
    min-width: 17rem;
  }
}

/* ---- the delete question ---- */

dialog {
  width: min(100%, 30rem);
  max-width: none;
  margin: auto auto 0;
  padding: 1.75rem 1.25rem calc(1.25rem + env(safe-area-inset-bottom));
  border: 0;
  border-radius: 1rem 1rem 0 0;
  background: var(--lift);
  color: var(--ink);
  box-shadow: 0 -8px 40px rgb(0 0 0 / 0.3);
}

dialog::backdrop {
  background: rgb(26 22 20 / 0.62);
}

dialog[open] {
  display: grid;
  gap: 0.75rem;
  animation: sheet 0.24s cubic-bezier(0.2, 0.8, 0.2, 1);
}

dialog h2 {
  font-size: 1.6rem;
}

.alert {
  padding: 0.7rem 0.9rem;
  border-left: 3px solid var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  font-size: 0.9375rem;
}

.choices {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
  margin-top: 0.75rem;
}

.btn .busy,
.btn[aria-busy='true'] .idle {
  display: none;
}

.btn[aria-busy='true'] .busy {
  display: inline;
}

@keyframes sheet {
  from {
    opacity: 0;
    transform: translateY(1.5rem);
  }
}

@media (min-width: 40rem) {
  dialog {
    margin: auto;
    padding: 2rem;
    border-radius: 0.75rem;
  }
}

/* ---- /e/<token>: an event's gallery ---- */

.event {
  display: grid;
  gap: 2.25rem;
  align-content: start;
  padding-top: 1rem;
}

.event--loading {
  display: none; /* without scripts there is nothing to wait for */
}

.js .event--loading {
  display: grid;
}

/* v1's pieces carry grid areas of the photo page's grid; in these grids they are ordinary items */
.event .trust,
.session .grid {
  grid-area: auto;
}

.event-head {
  display: grid;
  gap: 0.6rem;
}

.eyebrow {
  color: var(--brick);
}

.event-head h1 {
  font-size: clamp(2.25rem, 11vw, 3.75rem);
  overflow-wrap: anywhere; /* a name is whatever the host typed, up to 80 characters */
}

.mine h2,
.everyone h2 {
  font-size: 1.6rem;
}

/* The guest's own session: pinned on top, under the strip's brick rule. */
.mine {
  display: grid;
  gap: 1.25rem;
  padding: 1.25rem 1.25rem 1.5rem;
  border-top: 3px solid var(--brick);
  border-radius: 0 0 0.75rem 0.75rem;
  background: var(--lift);
}

.mine-actions {
  display: grid;
  gap: 0.5rem;
  justify-items: center;
}

.mine-actions .btn--solid {
  width: 100%;
  min-height: 3.5rem;
  font-size: 1.0625rem;
}

.done {
  padding: 0.8rem 1rem;
  border-left: 3px solid var(--brick);
  background: var(--lift);
  font-weight: 600;
}

/* One session at full size: the strip, then its photos two by two; side by side on a wider screen. */
.session {
  display: grid;
  gap: 1.5rem;
}

.session .strip {
  justify-self: center;
  width: min(58vw, 15rem);
}

@media (min-width: 40rem) {
  .session {
    grid-template-columns: minmax(9rem, 14rem) minmax(0, 1fr);
    align-items: start;
    column-gap: 2rem;
  }

  .session .strip {
    width: 100%;
  }
}

.everyone {
  display: grid;
  gap: 1.25rem;
}

/* Everyone's strips, side by side like a contact sheet. */
.sheet {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
  gap: 1.5rem 0.75rem;
}

@media (min-width: 40rem) {
  .sheet {
    grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
    gap: 2rem 1.25rem;
  }
}

.tile {
  position: relative;
}

.tile-open {
  display: grid;
  gap: 0.5rem;
  width: 100%;
  padding: 0;
  border: 0;
  border-radius: 0.25rem;
  background: none;
  color: inherit;
  text-align: center;
  cursor: pointer;
}

.tile .frame {
  display: block;
  transition: transform 0.15s;
}

.frame--strip.is-photo img {
  object-fit: cover; /* a session without a strip shows its first photo in the strip's place */
}

.tile time {
  color: var(--ink-soft);
  font-size: 0.875rem;
  letter-spacing: 0.08em;
}

@media (hover: hover) {
  .tile-open:hover .frame {
    transform: translateY(-3px);
  }
}

.tile.is-mine .frame {
  outline: 3px solid var(--brick);
  outline-offset: 3px;
}

.badge {
  display: none;
}

.tile.is-mine .badge {
  display: block;
  position: absolute;
  top: -0.6rem;
  left: 50%;
  z-index: 1;
  transform: translateX(-50%);
  padding: 0.3rem 0.55rem;
  border-radius: 999px;
  background: var(--danger); /* light type on it is 5.6:1 in both schemes */
  color: var(--on-danger);
  font: 700 0.75rem / 1 var(--text);
  white-space: nowrap;
}

.tile .broken {
  font-size: 0.75rem;
}

.more {
  display: grid;
  gap: 0.75rem;
  justify-items: center;
  text-align: center;
}

.done:focus {
  outline: none; /* focus lands here after a delete, for screen readers; the notice itself is the mark */
}

.more .btn {
  min-width: 12rem;
}

/* ---- the viewer: one session, full size; the whole screen on a phone ---- */

dialog.viewer {
  width: 100%;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0 1.25rem calc(1.5rem + env(safe-area-inset-bottom));
  border-radius: 0;
  background: var(--paper);
  align-content: start;
}

.viewer-bar {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: max(0.75rem, env(safe-area-inset-top)) 0 0.75rem;
  border-bottom: 1px solid var(--hair);
  background: var(--paper);
}

.viewer-bar h2 {
  font-size: 1.35rem;
}

.viewer-bar .btn {
  min-height: 2.75rem;
}

@media (min-width: 40rem) {
  dialog.viewer {
    width: min(100% - 3rem, 60rem);
    height: auto;
    max-height: calc(100% - 3rem);
    margin: auto;
    padding-inline: 2rem;
    border-radius: 0.75rem;
  }
}

/* ---- Georgian: its letters run taller and wider than Latin and Cyrillic ones ---- */

html:lang(ka) h1,
html:lang(ka) h2 {
  line-height: 1.2; /* Mkhedruli ascenders and descenders run long */
  letter-spacing: 0;
}

/* "გადახდილია" is 6.6 em wide at this weight: 13vw is the most a 390 px phone holds. */
html:lang(ka) [data-for='paid'] h1 {
  font-size: clamp(2.4rem, 12.5vw, 4.75rem);
}

/* ---- visitors who ask for less motion get the finished page ---- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    /* biome-ignore lint/complexity/noImportantStyles: whatever sets an animation, this person asked for none */
    animation: none !important;
    /* biome-ignore lint/complexity/noImportantStyles: likewise for transitions */
    transition: none !important;
  }
}
