/* ==========================================================
   Jesse Becker - Personal Site  |  components.css
   The reusable vocabulary: paper sheets, tape, buttons, chips,
   badges, ledger rows, and the scroll-reveal states.
   Depends on: tokens.css, base.css
   ========================================================== */

/* ==========================================================
   Paper
   .sheet  - a single sheet of paper (replaces all glass cards)
   .stack  - two more sheets fanned out behind it
   .tape   - a strip of tape holding something down
   .fold   - a turned-up corner
   ========================================================== */

.sheet {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sheet);
}

.stack { position: relative; z-index: 0; }

.stack::before,
.stack::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: inherit;
  transition: transform var(--dur-mid) var(--ease-spring);
}

.stack::before { transform: rotate(-1.4deg) translate(-5px, 4px); }
.stack::after  { transform: rotate(1.1deg) translate(6px, -2px); opacity: .65; }

.stack:hover::before { transform: rotate(-2.2deg) translate(-8px, 6px); }
.stack:hover::after  { transform: rotate(1.9deg) translate(9px, -4px); }

.tape {
  position: absolute;
  z-index: 3;
  width: 5.2rem;
  height: 1.5rem;
  background: var(--tape);
  border-left: 2px dashed rgba(255,255,255,.35);
  border-right: 2px dashed rgba(255,255,255,.35);
  box-shadow: 0 1px 2px rgba(23,19,16,.08);
}

.fold {
  position: absolute;
  top: 0;
  right: 0;
  width: 30px;
  height: 30px;
  background: linear-gradient(225deg, var(--bg) 0 49%, var(--accent-tint) 50% 100%);
  border-top-right-radius: var(--r-lg);
  border-bottom-left-radius: var(--r-md);
  box-shadow: -2px 2px 3px rgba(23,19,16,.07);
}

/* ==========================================================
   Buttons
   ========================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur-mid) var(--ease-spring),
              box-shadow var(--dur-mid) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}

.btn-primary {
  background: var(--ink);
  color: var(--bg);
  box-shadow: 3px 3px 0 var(--accent);
}

/* The fill deliberately stays ink on hover. Filling with accent
   put an accent shadow behind an accent button, so the offset
   dissolved into the fill. The lift and the deeper shadow are
   the feedback; the shadow needs the ink fill to read against. */
.btn-primary:hover {
  transform: translate(-3px, -3px);
  box-shadow: 7px 7px 0 var(--accent);
}

.btn-primary:active {
  transform: translate(1px, 1px);
  box-shadow: 2px 2px 0 var(--accent);
}

.btn-ghost {
  background: var(--surface);
  border-color: var(--hairline-strong);
  color: var(--ink);
}

.btn-ghost:hover {
  transform: translate(-1px, -2px);
  border-color: var(--accent);
  color: var(--accent-deep);
}

.btn-sm { padding: var(--sp-2) var(--sp-4); font-size: var(--fs-xs); }

.btn .arrow { transition: transform var(--dur-fast) var(--ease-out); }
.btn:hover .arrow { transform: translateX(4px); }

/* ==========================================================
   Back to top
   Injected by site.js rather than sitting in all eight pages,
   since it is a JavaScript-only affordance anyway.

   Sits above the grain overlay (z 90) but below the navbar
   (z 100) and the lightbox (z 300), so an open dialog covers it.
   Hidden state uses visibility, not just opacity, so the button
   leaves the tab order when it is off screen.
   ========================================================== */

/* Glass rather than the hard offset shadow the buttons use: this
   floats over the content instead of sitting in the layout, so it
   should read as part of the chrome, like the navbar. */
.to-top {
  position: fixed;
  right: var(--sp-5);
  bottom: var(--sp-5);
  z-index: 95;
  display: grid;
  place-items: center;
  width: 2.9rem;
  height: 2.9rem;
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--surface) 62%, transparent);
  backdrop-filter: blur(12px) saturate(1.3);
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
  color: var(--ink-soft);
  box-shadow: var(--shadow-sheet);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity var(--dur-mid) var(--ease-out),
              transform var(--dur-mid) var(--ease-spring),
              visibility var(--dur-mid),
              box-shadow var(--dur-mid) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}

.to-top.is-visible { opacity: 1; visibility: visible; transform: none; }

.to-top:hover {
  transform: translateY(-2px);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  border-color: var(--accent);
  color: var(--accent-deep);
  box-shadow: var(--shadow-lift);
}

.to-top:active { transform: translateY(0); box-shadow: var(--shadow-sheet); }

.to-top svg { width: 1.05rem; height: 1.05rem; fill: currentColor; }

@media (max-width: 480px) {
  .to-top { right: var(--sp-4); bottom: var(--sp-4); width: 2.6rem; height: 2.6rem; }
}

/* ==========================================================
   Chips, tags, badges
   Rectangles, not pills - shape stays available for emphasis.
   ========================================================== */

.tags { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.tag {
  display: inline-block;
  padding: .3rem .7rem;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink-soft);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .02em;
}

.tag-accent {
  border-color: var(--accent);
  background: var(--accent-tint);
  color: var(--accent-deep);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: .25rem .65rem;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
  color: var(--ink-soft);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .1em;
  line-height: 1.3;
  text-transform: uppercase;
  white-space: nowrap;
}

.badge-accent {
  border-color: var(--accent);
  background: var(--accent-tint);
  color: var(--accent-deep);
}

.badge-deep-dive::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}

/* The kicker above a card or section title */
.kicker {
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent-deep);
}

/* Pill is reserved for this one mark: the location stamp */
.stamp {
  display: inline-block;
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-sm);
  font-weight: 600;
  box-shadow: var(--shadow-sheet);
}

/* ==========================================================
   Ledger rows
   Hairline-separated list rows that replace the uniform card
   grid for expertise / skills. Hover lifts a row onto paper.
   ========================================================== */

.ledger { margin-top: var(--sp-2); }

/* Rows carry different numbers of tag chips, so they wrap to
   different heights and the list reads as ragged. Sizing the
   implicit rows at 1fr makes every row match the tallest, and
   it re-measures itself if the tag lists change.

   Only above the stacking breakpoint: once the name and tags
   sit on separate lines the raggedness stops reading as uneven,
   and equalising would just pad every short row out to the
   tallest one, adding a few hundred pixels of dead scroll. */
@media (min-width: 821px) {
  .ledger {
    display: grid;
    grid-auto-rows: 1fr;
  }
}

.ledger-row {
  display: grid;
  grid-template-columns: 3.2rem minmax(11rem, .8fr) minmax(0, 1.6fr);
  gap: var(--sp-5);
  align-items: center;
  padding: var(--sp-5) var(--sp-4);
  border-top: 1px solid var(--hairline);
  transition: background var(--dur-fast) var(--ease-out),
              padding-left var(--dur-mid) var(--ease-spring),
              border-radius var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}

.ledger-row:last-child { border-bottom: 1px solid var(--hairline); }

.ledger-row:hover {
  padding-left: var(--sp-5);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sheet);
}

/* Separators are drawn as each row's own border-top, so the rule
   sitting against a hovered row's bottom edge belongs to the row
   below it. Both that one and the hovered row's own top rule have
   to go, or they cut straight across the rounded corners. */
.ledger-row:hover,
.ledger-row:hover + .ledger-row { border-top-color: transparent; }
.ledger-row:last-child:hover { border-bottom-color: transparent; }

.ledger-idx {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-base);
  color: var(--ink-faint);
}

.ledger-name {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 560;
  transition: color var(--dur-fast) var(--ease-out);
}

.ledger-row:hover .ledger-name { color: var(--accent-deep); }

.ledger-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  justify-content: flex-end;
}

.ledger-row:hover .tag { border-color: var(--accent); color: var(--ink); }

/* These tags are the only thing on the site that runs hard against the
   container's right edge, so they are the only thing the fixed .to-top
   button can cover. Once the viewport is narrow enough for the button to
   reach into the container, keep a lane clear for it. Above that width
   the container's own margin is already wider than the button. */
@media (max-width: 1224px) {
  .ledger-tags { padding-right: 3.5rem; }
}

@media (max-width: 820px) {
  .ledger-row { grid-template-columns: 2rem 1fr; }
  .ledger-tags { grid-column: 2; justify-content: flex-start; }
}

/* ==========================================================
   Work cards
   A lead card and supporting cards, differentiated by size,
   tilt, and ornament rather than all being the same card.
   ========================================================== */

.work-grid {
  display: grid;
  grid-template-columns: 1.15fr .85fr;
  gap: var(--sp-6);
  align-items: stretch;
}

/* One lead card beside a stacked pair of supporting cards */
.work-grid--three { grid-template-rows: auto auto; }
.work-grid--three > .work-card--lead { grid-row: 1 / span 2; }

.work-card {
  display: flex;
  flex-direction: column;
  position: relative;
  padding: var(--sp-6);
  transition: transform var(--dur-mid) var(--ease-spring),
              box-shadow var(--dur-mid) var(--ease-out);
}

.work-card--lead  { transform: rotate(-.4deg) translateY(1.25rem); }
.work-card--minor { transform: rotate(.5deg); }

.work-card:hover { box-shadow: var(--shadow-lift); }
.work-card--lead:hover  { transform: rotate(0deg) translateY(calc(1.25rem - 5px)); }
.work-card--minor:hover { transform: rotate(0deg) translateY(-5px); }

.work-card h3 {
  margin-bottom: var(--sp-3);
  font-size: 1.55rem;
  line-height: 1.2;
  letter-spacing: -.015em;
}

/* Only the body copy absorbs the slack - the kicker must not,
   or the lead card's title floats away from it. */
.work-card > p:not(.kicker) { flex: 1; font-size: .97rem; color: var(--ink-soft); }
.work-card .kicker { flex: 0 0 auto; margin-bottom: var(--sp-4); }

/* The lead card spans both rows, so it is taller than its content no
   matter what it says. Letting the body copy swallow all that slack
   opened a visible hole between the paragraph and the tags; centring
   the block spreads the same space above and below instead, which
   reads as a generous featured card rather than an unfinished one. */
.work-card--lead { justify-content: center; }
.work-card--lead > p:not(.kicker) { flex: 0 1 auto; }

/* Oversized display glyph, low opacity, bleeding off the corner */
.work-glyph {
  position: absolute;
  top: -1.8rem;
  right: .5rem;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 600;
  font-size: 6.5rem;
  line-height: 1;
  color: var(--accent);
  opacity: calc(.1 * var(--ghost-damp));
  pointer-events: none;
  user-select: none;
}

.card-link {
  align-self: flex-start;
  margin-top: var(--sp-5);
  padding-bottom: 2px;
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  border-bottom: 2px solid var(--accent);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 600;
  transition: color var(--dur-fast) var(--ease-out),
              gap var(--dur-mid) var(--ease-spring);
}

.card-link:hover { color: var(--accent-deep); gap: .85rem; }

.work-card .tags { margin-top: var(--sp-4); }

@media (max-width: 820px) {
  .work-grid,
  .work-grid--three { grid-template-columns: 1fr; }
  .work-grid--three > .work-card--lead { grid-row: auto; }
  .work-card--lead { transform: rotate(-.4deg); }
}

/* ==========================================================
   Scroll reveal
   site.js adds .reveal, then .is-visible when in view.
   Cards that sit at a resting tilt settle into it.
   ========================================================== */

.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
}

.reveal.is-visible { opacity: 1; transform: none; }

.work-card.reveal {
  transition: opacity var(--dur-slow) var(--ease-out),
              transform .45s var(--ease-spring),
              box-shadow var(--dur-mid) var(--ease-out);
}

.work-card--lead.reveal  { transform: rotate(-1.6deg) translateY(2.5rem); }
.work-card--minor.reveal { transform: rotate(1.6deg) translateY(1.5rem); }

.work-card--lead.reveal.is-visible  { transform: rotate(-.4deg) translateY(1.25rem); }
.work-card--minor.reveal.is-visible { transform: rotate(.5deg); }

@media (max-width: 820px) {
  .work-card--lead.reveal.is-visible { transform: rotate(-.4deg); }
}
