/* Swamp Box.

   The palette, font stack and section idiom are taken from Demo Locker's
   brochure (~/webdev/demolocker/site/style.css) so the two pages read as work
   from the same shop. The dot grid is fldl.space's, so the page reads as
   sitting on the Florida DL surface. Deliberately a copy of both rather than a
   shared build: this is three static files with no pipeline. */

:root {
  --bg: #0a0a0a;
  --fg: #ffffff;
  --fg-dim: #cccccc;
  /* Swamp Box neon green, carried over from the React app's
     `--primary: 112 100% 51%`. Fixed, not cycled: Demo Locker's hue cycle is
     Demo Locker's signature, and a studio wants one recognisable colour. */
  --accent: #0aff1f;
  --accent-dim: color-mix(in srgb, var(--accent) 8%, transparent);
  --border: #282828;
  /* the coral the "DL" is set in on fldl.space */
  --fldl-coral: #e8937c;
  /* swamp flowers under the wordmark — the one warm note on the page */
  --flower: #f6a;
  --flower-core: #fc0;
  --font: "Berkeley Mono", "JetBrains Mono", "Fira Code", "SF Mono", monospace;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  background: var(--bg);
}

body {
  background-color: var(--bg);
  color: var(--fg);
  font-family: var(--font);
  font-size: 13px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;

  /* fldl.space's graph-paper dot grid. Fixed attachment so it stays put while
     the page scrolls — it is the surface the site sits on, not a parallax
     layer. fldl runs 0.12 white on pure #000; this page's ground is #0a0a0a,
     so the dots are pulled to 0.10 to land at the same contrast against it. */
  background-image: radial-gradient(rgba(255, 255, 255, 0.10) 1px, transparent 1px);
  background-size: 24px 24px;
  background-attachment: fixed;
}

::selection {
  background: var(--accent);
  color: var(--bg);
}

a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

a:hover,
a:focus-visible {
  border-bottom-color: var(--accent);
}

.wrap {
  max-width: 880px;
  margin: 0 auto;
  padding: 2.5rem 1.625rem 4rem;
}

/* ── wordmark ──────────────────────────────────────────────────────────────
   An <svg> of cells on an 82 x 7 viewBox, not a <pre> of block characters.
   Block-glyph art rendered as text depends on the space and the block glyph
   resolving to the same font; on Android they do not, and every row lands at
   its own x offset. An SVG has no glyphs to mis-advance and scales exactly.

   Generated from `figlet -f banner3 "SWAMP BOX"`. To change the wordmark,
   regenerate rather than hand-editing the rects.

   738px is the width Demo Locker's mark caps at, kept so the two pages have
   marks of the same physical size on a desktop. --fit is the fraction of the
   column the art may occupy; the mobile query below pulls it in so the logo
   does not swallow a phone screen. */
.wordmark {
  --fit: 1;
  display: block;
  width: min(calc(100% * var(--fit)), 738px);
  height: auto;
  margin-inline: auto;
  color: var(--accent);
  user-select: none;
}

/* ── wet wordmark ──────────────────────────────────────────────────────────
   Condensation runs off the letterforms and swamp flowers bloom underneath.
   Both are drawn as cells on the same grid as the mark, and every transform is
   a whole-cell step, so nothing ever lands on a half pixel — the animation
   reads as pixels moving rather than as art sliding around.

   These are ambient: nothing here is wired to scroll or pointer position, so
   the mark behaves like a wet object rather than a meter responding to the
   visitor.

   The per-element offset comes from a --d custom property set inline in the
   markup, so respacing the loop is a markup edit and never a CSS one. */

.drips rect,
.flower rect {
  transform-box: fill-box;
}

/* the run: a column of water that creeps down one cell at a time from the
   bottom edge of a letter, hangs, then lets go. */
.drip {
  transform-origin: 50% 0;
  animation: drip 4.4s steps(1, end) var(--d, 0s) infinite;
}

@keyframes drip {
  0%       { transform: scaleY(0); }
  9%       { transform: scaleY(1); }
  18%      { transform: scaleY(2); }
  27%      { transform: scaleY(3); }
  55%      { transform: scaleY(3); }
  /* the drop detaches here — the run snaps back to the letter edge */
  57%, 100% { transform: scaleY(1); }
}

/* the drop that detaches, falling the rest of the way and fading out */
.drop {
  transform-origin: 50% 50%;
  opacity: 0;
  animation: drop 4.4s steps(1, end) var(--d, 0s) infinite;
}

@keyframes drop {
  0%, 56%   { transform: translateY(2px); opacity: 0; }
  57%       { transform: translateY(2px); opacity: 1; }
  66%       { transform: translateY(3px); opacity: 1; }
  75%       { transform: translateY(4px); opacity: 0.6; }
  84%, 100% { transform: translateY(4px); opacity: 0; }
}

/* ── flowers ───────────────────────────────────────────────────────────────
   Pink, so they read as something growing out of the swamp rather than as
   more of the mark. They bloom cell by cell — stem, then core, then petals —
   which is why each part animates on its own delay instead of the group being
   scaled: a scaled group would land the cells off the pixel grid. */
/* The colour is resolved on the individual .flower, not on the .flowers group,
   so a variant can override --flower on itself: `color` computes against the
   element's own custom-property value, and the petals inherit the result.
   Setting it on the group instead would freeze one colour for all of them. */
.flower {
  color: var(--flower);
  fill: currentColor;
}

.flower.is-white  { --flower: #ffffff; }
.flower.is-purple { --flower: #a6f; }

/* Yellow is the one variant that cannot keep the gold core: #fc0 inside #fe3
   is barely a shade apart, the middle cell disappears, and the blossom
   collapses into a solid 3x3 square. Swapping the core to the page's ink keeps
   the ring reading as petals — the contrast is what does the work, not the
   particular colour. */
.flower.is-yellow {
  --flower: #fe3;
  --flower-core: var(--fg);
}

/* The core is gold on every other variant. Holding it constant is what makes
   pink, white and purple read as the same flower rather than three shapes;
   --flower-core is a variable precisely so the yellow can opt out. */
.flower .f-core {
  fill: var(--flower-core);
}

/* Each part gets its own keyframes rather than one shared set on staggered
   delays. A shared set staggers the close in the same direction as the open,
   so the stem vanished first and left the head hanging in the air for a
   second. Nesting the windows instead — stem outermost, petal innermost —
   opens bottom-up and closes top-down, so the flower wilts the way it grew. */
.flower rect {
  opacity: 0;
  animation-duration: 11s;
  animation-timing-function: steps(1, end);
  animation-iteration-count: infinite;
  animation-delay: var(--d, 0s);
}

.flower .f-stem  { animation-name: bloom-stem; }
.flower .f-core  { animation-name: bloom-core; }
.flower .f-petal { animation-name: bloom-petal; }

@keyframes bloom-stem {
  0%        { opacity: 0; }
  5%, 84%   { opacity: 1; }
  85%, 100% { opacity: 0; }
}

@keyframes bloom-core {
  0%, 8%    { opacity: 0; }
  9%, 80%   { opacity: 1; }
  81%, 100% { opacity: 0; }
}

@keyframes bloom-petal {
  0%, 12%   { opacity: 0; }
  13%, 76%  { opacity: 1; }
  77%, 100% { opacity: 0; }
}

/* An ambient loop with no off switch is exactly what this query is for. The
   resting state is deliberately the bloomed one — flowers open and a short run
   of water on each letter — so reduced-motion users get the wet swamp mark as
   a still image rather than a bare wordmark with empty space under it. */
@media (prefers-reduced-motion: reduce) {
  .drip {
    animation: none;
    transform: scaleY(2);
  }
  .drop {
    animation: none;
    opacity: 0;
  }
  .flower rect {
    animation: none;
    opacity: 1;
  }
}

.tagline {
  margin-top: 1.75rem;
  font-size: 15px;
  line-height: 1.7;
}

.tagline em {
  color: var(--accent);
  font-style: normal;
}

/* ── sections ──────────────────────────────────────────────────────────── */

section {
  margin-top: 3rem;
}

.label {
  color: var(--fg-dim);
  font-size: 11px;
  font-weight: normal;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.25rem;
}

/* ── booking ───────────────────────────────────────────────────────────── */

.cta-lead {
  color: var(--fg-dim);
  margin-bottom: 0.9rem;
}

/* the hinge between the two ways in, matching Demo Locker's `.or` */
.or {
  color: var(--fg-dim);
  margin: 1.75rem 0 0.9rem;
}

.form-box {
  border: 1px solid var(--border);
  background: var(--accent-dim);
  padding: 1rem;
  max-width: 700px;
}

.field {
  margin-bottom: 0.9rem;
}

.field label {
  display: block;
  color: var(--fg-dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  margin-bottom: 0.35rem;
}

.field input,
.field textarea {
  font-family: var(--font);
  font-size: 13px;
  line-height: 1.6;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--border);
  padding: 0.5rem 0.6rem;
  width: 100%;
  display: block;
}

.field textarea {
  min-height: 7.5rem;
  resize: vertical;
}

.field input:focus,
.field textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}

/* :placeholder-shown keeps the invalid style off an untouched field — :invalid
   alone paints every required field red before the visitor has typed. */
.field input:not(:placeholder-shown):invalid,
.field textarea:not(:placeholder-shown):invalid {
  border-color: var(--fldl-coral);
}

.field input::placeholder,
.field textarea::placeholder {
  color: #666;
}

.btn {
  font-family: var(--font);
  font-size: 13px;
  color: var(--accent);
  background: none;
  border: 1px solid var(--border);
  padding: 0.5rem 1rem;
  cursor: pointer;
}

.btn:hover:not(:disabled),
.btn:focus-visible:not(:disabled) {
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}

.btn:disabled {
  color: var(--fg-dim);
  cursor: default;
}

/* The submit result. aria-live in the markup announces it; this only styles
   it. Empty until contact.js writes to it, and .status:empty has no margin so
   it takes no space on a page nobody has submitted. */
.status {
  margin-top: 0.9rem;
  font-size: 12px;
  line-height: 1.7;
}

.status:empty {
  margin-top: 0;
}

.status[data-state="ok"] {
  color: var(--accent);
}

.status[data-state="error"] {
  color: var(--fldl-coral);
}

/* ── footer ────────────────────────────────────────────────────────────── */

footer {
  margin-top: 4rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

.colophon {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.colophon .where {
  color: var(--fg-dim);
  font-size: 12px;
}

/* the fldl.space backlink. Set in the page's own mono rather than the Press
   Start 2P / Space Mono pair the React footer used — this page has one font. */
.fldl {
  color: var(--fg-dim);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border-bottom: 1px solid transparent;
}

.fldl:hover,
.fldl:focus-visible {
  color: var(--fg);
  border-bottom-color: var(--fldl-coral);
}

.fldl b {
  color: var(--fldl-coral);
  font-weight: normal;
}

@media (max-width: 640px) {
  .wrap {
    padding: 1.75rem 1rem 3rem;
  }
  .tagline {
    font-size: 14px;
  }
  .wordmark {
    --fit: 0.92;
  }
}
