/* 1t.ie — index of self-hosted apps.
   The hostname is the hero: each tile is a route, and hovering one lights
   up its shared .1t.ie root in that app's colour. */

@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/plex-mono-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/plex-mono-600.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Plex Sans';
  src: url('/fonts/plex-sans-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Plex Sans';
  src: url('/fonts/plex-sans-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}

:root {
  --ink: #08090c;
  --surface: #101319;
  --surface-hi: #161a22;
  --line: #1e222c;
  --line-hi: #2b303c;
  --text: #e9eaee;
  --dim: #6d7481;
  --dim-2: #464c57;
  --down: #9a6060;

  --mono: 'Plex Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
  --sans: 'Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  color-scheme: dark;
  font-synthesis: none;
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--ink);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--accent, #7c8cf8);
  outline-offset: 3px;
  border-radius: 4px;
}

.page {
  max-width: 1000px;
  margin: 0 auto;
  padding: clamp(48px, 11vh, 120px) clamp(20px, 5vw, 40px) 56px;
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

/* ---------- header ---------- */

.head {
  margin-bottom: clamp(40px, 7vh, 72px);
}

.wordmark {
  display: inline-block;
  font-family: var(--mono);
  font-weight: 600;
  font-size: 22px;
  letter-spacing: -0.02em;
  text-decoration: none;
  line-height: 1;
}

.wordmark-dim { color: var(--dim-2); }

.strap {
  margin: 14px 0 0;
  max-width: 34ch;
  color: var(--dim);
  font-size: 15px;
}

/* ---------- grid ---------- */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 14px;
}

/* Shown in place of the tiles when apps.json can't be read, and to no-JS
   visitors. Spans the grid so it reads as a message, not a stray tile. */
.grid > noscript,
.grid-note {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--dim);
  font-size: 14.5px;
}

.grid-note a { color: var(--text); }

.tile {
  /* Overridden per tile by the "accent" field in apps.json. A neutral default
     means an entry without one stays legible instead of borrowing a colour. */
  --accent: #8b93a3;
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 22px 22px 24px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--surface);
  transition: background-color 0.25s ease, border-color 0.25s ease;
}

.tile:hover,
.tile:focus-within {
  background: var(--surface-hi);
  border-color: var(--line-hi);
}

/* The whole tile navigates; the repo link sits above it in the stack. */
.jump { text-decoration: none; }
.jump::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 14px;
}

/* ---------- status ---------- */

.tile-top {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 22px;
}

.dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--dim-2);
  transition: background-color 0.4s ease, box-shadow 0.4s ease;
}

.stat {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--dim-2);
  transition: color 0.4s ease;
}

/* Without JS the label never fills in, so it takes no space. */
.stat:empty { display: none; }

.tile[data-status='up'] .dot {
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent);
}
.tile[data-status='up'] .stat { color: var(--dim); }

.tile[data-status='down'] .dot { background: var(--down); }
.tile[data-status='down'] .stat { color: var(--down); }

/* ---------- hostname ---------- */

.host {
  margin: 0;
  font-family: var(--mono);
  font-weight: 600;
  font-size: clamp(26px, 3.4vw, 31px);
  letter-spacing: -0.035em;
  line-height: 1.1;
  /* A hostname longer than the tile wraps instead of spilling out of it. */
  overflow-wrap: anywhere;
}

.suffix {
  color: var(--dim-2);
  transition: color 0.3s ease;
}

.tile:hover .suffix,
.tile:focus-within .suffix {
  color: var(--accent);
}

/* ---------- the rule that draws on hover ---------- */

.rule {
  position: relative;
  height: 1px;
  margin-top: 18px;
  background: var(--line-hi);
}

.rule::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  height: 1px;
  width: 0;
  background: var(--accent);
  transition: width 0.5s var(--ease);
}

.tile:hover .rule::after,
.tile:focus-within .rule::after {
  width: 100%;
}

/* ---------- tagline and detail, cross-faded in one cell ----------

   Both sit in the same grid cell, so the cell is as tall as the taller of the
   two and nothing resizes on hover. Growing the tile instead would resize its
   grid row, and every other tile in that row stretches to match — which is why
   one hover used to move the whole row. Only opacity and transform animate. */

.swap {
  display: grid;
  margin-top: 18px;
}

.swap > * {
  grid-area: 1 / 1;
  margin: 0;
}

.tag {
  color: var(--dim);
  font-size: 14.5px;
  transition: opacity 0.22s ease;
}

.tile:hover .tag,
.tile:focus-within .tag { opacity: 0; }

.more {
  align-self: start;
  opacity: 0;
  transform: translateY(5px);
  /* Invisible text must not be clickable: without this, a click aimed at the
     tile could land on the repo link hiding underneath. */
  pointer-events: none;
  transition: opacity 0.28s ease, transform 0.38s var(--ease);
}

.tile:hover .more,
.tile:focus-within .more {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.desc {
  margin: 0;
  color: var(--dim);
  font-size: 14px;
  line-height: 1.6;
}

.meta {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 0;
  margin: 14px 0 0;
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 500;
}

.lang {
  color: var(--accent);
  letter-spacing: 0.02em;
}

.lang + .repo::before {
  content: '·';
  margin: 0 8px;
  color: var(--dim-2);
}

.repo {
  position: relative;
  z-index: 1;
  color: var(--dim);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.repo:hover {
  color: var(--text);
  border-bottom-color: var(--line-hi);
}

/* ---------- footer ---------- */

.foot {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  margin-top: auto;
  padding-top: 56px;
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--dim-2);
}

.foot a {
  color: var(--dim-2);
  text-decoration: none;
  transition: color 0.2s ease;
}

.foot a:hover { color: var(--dim); }

/* ---------- entrance ---------- */

@keyframes riseIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

.head { animation: riseIn 0.5s var(--ease) both; }
.tile { animation: riseIn 0.5s var(--ease) both; }
.tile:nth-child(2) { animation-delay: 60ms; }
.tile:nth-child(3) { animation-delay: 120ms; }
.tile:nth-child(n + 4) { animation-delay: 180ms; }
.foot { animation: riseIn 0.5s var(--ease) 0.24s both; }

/* ---------- no hover (touch): keep everything open ---------- */

/* No hover to reveal with, so show the detail and drop the tagline it
   would otherwise sit on top of. */
@media (hover: none) {
  .more { opacity: 1; transform: none; pointer-events: auto; }
  .tag { display: none; }
  .rule::after { width: 100%; }
  .suffix { color: var(--accent); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
  .more { transform: none; }
}
