/* ─────────────────────────────────────────────
   styles.css
   Landing page (index.html) visual framework
   Depends on variables.css for design tokens
───────────────────────────────────────────── */

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

html, body {
  background: var(--bg-base);
  color: var(--text-primary);
  font-family: var(--font-body);
  min-height: 100vh;
  overflow-x: hidden;
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  padding: var(--space-lg);
  position: relative;
}

body::before {
  content: '';
  position: fixed;
  inset: 0;
  background-image:
          linear-gradient(var(--locked-border) 1px, transparent 1px),
          linear-gradient(90deg, var(--locked-border) 1px, transparent 1px);
  background-size: 32px 32px;
  opacity: 0.25;
  pointer-events: none;
  z-index: 0;
}

header {
  position: relative;
  z-index: var(--z-base);
  text-align: center;
  padding: var(--space-lg) 0 var(--space-2xl);
  min-height: 200px;
}

header h1 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.5rem, 4vw, 2.5rem);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-primary);
  position: relative;
  display: inline-block;
  padding: 0 var(--space-xl);
  min-height: 1.3em;
}

header h1::before,
header h1::after {
  content: '';
  position: absolute;
  top: 50%;
  width: var(--space-xl);
  height: 1px;
  background: var(--accent-teal);
  opacity: 0.6;
}

header h1::before { left: -10px; }
header h1::after { right: -10px; }

header p {
  font-size: 0.963rem;
  letter-spacing: var(--tracking-tight);
  color: var(--text-secondary);
  margin-top: var(--space-sm);
  min-height: 1.4em;
}

header p:first-of-type {
  color: var(--text-primary);
  font-weight: 600;
  margin-top: var(--space-lg);
  text-transform: uppercase;
  letter-spacing: var(--tracking-normal);
}

main {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 120px;
  flex-wrap: wrap;
  position: relative;
  z-index: var(--z-base);
}

.card {
  position: relative;
  background: var(--bg-panel);
  border: 1px solid var(--border-default);
  padding: var(--space-lg);
  width: 260px;
  min-height: 360px;
  outline: none;
  transition: var(--transition-default);
}

/* Card role streaming cursor RETIRED for PASS L1 -- its PAINT, not its layout.
   The reveal now runs across eight keys per card, so a role-anchored caret is
   misleading once the active key has moved on, and it would add a second moving
   paint layer over the region the no-ink differential oracle measures.

   The production caret sat at opacity 0 but still occupied 1px + 2px margin of
   inline layout, and the adopted baseline was measured with it there. Card 01's
   two authored role text nodes also lay out exactly one LayoutUnit (1/64 px) wider
   than production's single node. At 430 and 900 -- the witness widths at which the
   L1 differential was measured, no mechanism asserted -- the measured candidate
   offset was -2.984375 px, and this invisible footprint cancels it EXACTLY:
     0.984375 px (63/64) + 2 px margin = 2.984375 px.
   No background, no opacity, no animation, no visible property. */
.card-role::after {
  content: '';
  display: inline-block;
  width: 0.984375px;
  height: 0.9em;
  margin-left: 2px;
  vertical-align: middle;
}


/* ── Active card shell ── */
.card-active {
  background: var(--bg-panel-raised);
  border-color: transparent;
  border: none;
  width: 515px;
  min-height: 425px;
  display: flex;
  flex-direction: row;
  position: relative;
  overflow: hidden;
  box-shadow: 0 0 32px rgba(45, 212, 236, 0.08);
}

/* GROUP R: hidden reveal state is a JS-only enhancement (see profile.css). */
.js .card-active {
  clip-path: inset(50% 0 50% 0);
}

/* ─────────────────────────────────────────────
   Dual border system — outer wrapper + inner frame.
   Matches Engineering Portal card architecture.
───────────────────────────────────────────── */

.card-glow-wrapper {
  position: relative;
  border: 1px solid transparent;
  transition: var(--transition-default), border-color 250ms ease 150ms;
  /* clip-path on the inner .card-active bleeds outside overflow:hidden
     so the wrapper must not clip itself */
  overflow: visible;
}

.card-glow-wrapper.frame-visible {
  border-color: var(--accent-teal-dim);
}

.card-glow-wrapper:hover {
  border-color: var(--accent-teal);
  box-shadow: 0 0 12px var(--accent-teal), 0 0 40px var(--accent-teal-glow);
}

.card-glow-wrapper:hover .card-visual {
  filter: brightness(1.10) saturate(1.05);
}

/* Inner frame — appears once card materializes */
.card-glow-wrapper::after {
  content: '';
  position: absolute;
  inset: 8px;
  pointer-events: none;
  z-index: 30;
  border: 1px solid rgba(45, 212, 236, 0.28);
  opacity: 0;
  transition: opacity 250ms ease 150ms;
  background-image:
          linear-gradient(var(--accent-teal), var(--accent-teal)),
          linear-gradient(var(--accent-teal), var(--accent-teal)),
          linear-gradient(var(--accent-teal), var(--accent-teal)),
          linear-gradient(var(--accent-teal), var(--accent-teal));
  background-repeat: no-repeat;
  background-size:
          1px 18px,
          18px 1px,
          1px 18px,
          18px 1px;
  background-position:
          0 0,
          0 0,
          100% 100%,
          100% 100%;
}

.card-glow-wrapper.frame-visible::after {
  opacity: 1;
}

/* ── Portrait panel (Card 01) ── */
.card-active::before {
  content: none;
}

.card-active:hover::before {
  content: none;
}

/* ── On-hover wave/scan sweep (ported from profile.html image-rail hover-scan) ──
   A translucent teal band sweeps top→bottom across the whole panel on hover.
   Set z-index to 0 instead of 2 if you'd rather the sweep pass behind the text. */
.card-active::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  background: linear-gradient(
          to bottom,
          transparent 0%,
          rgba(45, 212, 236, 0.03) 46%,
          rgba(45, 212, 236, 0.18) 49%,
          rgba(45, 212, 236, 0.28) 50%,
          rgba(45, 212, 236, 0.18) 51%,
          rgba(45, 212, 236, 0.03) 54%,
          transparent 100%
  );
  background-size: 100% 300%;
  background-position: 0 -100%;
}

.card-active:hover::after {
  animation: hover-scan 1400ms ease-in-out 1 forwards;
}

@keyframes hover-scan {
  0%   { opacity: 0;   background-position: 0 -100%; }
  10%  { opacity: 0.8; background-position: 0 -70%;  }
  88%  { opacity: 0.6; background-position: 0  170%; }
  100% { opacity: 0;   background-position: 0  200%; }
}

@media (prefers-reduced-motion: reduce) {
  .card-active::after { display: none; }
}

.card-active:hover {
  cursor: pointer;
}

.card-active:hover .card-portrait-img {
  filter: contrast(1.05) saturate(0.95) brightness(1.10);
}

.card-portrait {
  position: absolute;
  top: 0;
  right: -60px;
  bottom: 0;
  left: 0;
  pointer-events: none;
  opacity: 1;
}

/* Required by the responsive source: without display:contents the <picture>
   becomes a layout box between .card-portrait and the image, and the
   image's 100%/100% would resolve against IT rather than the positioned
   container. This preserves the existing image-box behaviour; it is not
   art direction. */
.card-portrait picture {
  display: contents;
}

.card-portrait-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 85% bottom;
  display: block;
  filter: contrast(1.05) saturate(0.85) brightness(0.90);
  padding-top: 25px;
  box-sizing: border-box;
  transition: filter 350ms ease;
}

.card-portrait-overlay {
  position: absolute;
  inset: 0;
  background:
          linear-gradient(
                  to right,
                  var(--bg-panel-raised) 0%,
                  rgba(5, 10, 18, 0.92) 20%,
                  rgba(5, 10, 18, 0.55) 50%,
                  rgba(5, 10, 18, 0.1) 75%,
                  rgba(5, 10, 18, 0.0) 100%
          ),
          linear-gradient(
                  to left,
                  rgba(5, 10, 18, 0.98) 0%,
                  rgba(5, 10, 18, 0.82) 15%,
                  transparent 45%
          ),
          linear-gradient(
                  to top,
                  rgba(5, 10, 18, 0.95) 0%,
                  rgba(5, 10, 18, 0.55) 20%,
                  transparent 45%
          );
}

/* ── Artwork panel (Card 02 — Engineering Portal) ── */
.card-visual {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  pointer-events: none;
  opacity: 1;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  filter: brightness(0.85) saturate(0.90);
  transition: filter 350ms ease;
}

.card-visual--portal {
  background-image: url('../../assets/images/engineering-cybersec-portal-background.webp?v=1.4.0');
}

.card-visual-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
          linear-gradient(to bottom, rgba(5, 10, 18, .95) 0%, transparent 15%),
          linear-gradient(to top,    rgba(5, 10, 18, .95) 0%, transparent 15%),
          linear-gradient(to right,  rgba(5, 10, 18, .95) 0%, transparent 50%),
          linear-gradient(to left,   rgba(5, 10, 18, .95) 0%, transparent 15%);
}

/* ── Card content layer ── */
.card-content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  padding: var(--space-lg);
  width: 100%;
  min-height: 100%;
}

/* ── Materialize animation ── */
@keyframes card-materialize {
  0% {
    clip-path: inset(50% 0 50% 0);
    box-shadow: 0 0 0 rgba(45, 212, 236, 0);
    border-color: var(--accent-teal);
  }
  10% {
    clip-path: inset(49% 0 49% 0);
    box-shadow: 0 0 20px rgba(45, 212, 236, 0.35);
    border-color: var(--accent-teal);
  }
  65% {
    clip-path: inset(0% 0 0% 0);
    box-shadow: 0 0 36px rgba(45, 212, 236, 0.22);
    border-color: var(--accent-teal);
  }
  85% {
    clip-path: inset(0% 0 0% 0);
    box-shadow: 0 0 44px rgba(45, 212, 236, 0.28);
    border-color: var(--accent-teal);
  }
  100% {
    clip-path: inset(0% 0 0% 0);
    box-shadow: 0 0 32px rgba(45, 212, 236, 0.08);
    border-color: var(--accent-teal-dim);
  }
}

@keyframes card-content-fade {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}

.card-active.materialized {
  animation: card-materialize 380ms ease-out forwards;
}

.card-active.materialized .card-number,
.card-active.materialized .card-role,
.card-active.materialized .card-name,
.card-active.materialized .card-tags,
.card-active.materialized .enter-system-btn {
  animation: card-content-fade 180ms ease-out 300ms both;
}

@media (prefers-reduced-motion: reduce) {
  .card-active {
    clip-path: none;
  }
  .card-active.materialized {
    animation: none;
    clip-path: none;
  }
  .card-active.materialized .card-number,
  .card-active.materialized .card-role,
  .card-active.materialized .card-name,
  .card-active.materialized .card-tags,
  .card-active.materialized .enter-system-btn {
    animation: none;
    opacity: 1;
  }
}

/* Fail-open final state. card-content-fade has `both` fill, so a re-materialized
   card would put an ALREADY-REACHABLE button at opacity 0 for ~480ms — an A-1
   SAFETY window. Mirrors the reduced-motion block above. */
.rv-failed .card-active {
  clip-path: none;
}
.rv-failed .card-active.materialized {
  animation: none;
  clip-path: none;
}
.rv-failed .card-active.materialized .card-number,
.rv-failed .card-active.materialized .card-role,
.rv-failed .card-active.materialized .card-name,
.rv-failed .card-active.materialized .card-tags,
.rv-failed .card-active.materialized .enter-system-btn {
  animation: none;
  opacity: 1;
}
/* Belt and braces. failOpen() removes .js, which already unmatches the pending
   rule; this covers a .rv-failed state reached without that removal. */
.rv-failed .enter-system-btn {
  visibility: visible;
}

/* ── Card content typography ── */
.card-number {
  font-family: var(--font-mono);
  font-size: 1.874rem;
  color: var(--accent-teal-dim);
  line-height: 1;
}

.card-role {
  font-family: var(--font-mono);
  font-size: 0.749rem;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--accent-teal);
  margin-top: var(--space-xs);
  /* Wrap long role lines before they overlay the portrait
     (breaks "SECURITY OPERATIONS & INFRASTRUCTURE" onto two lines) */
  max-width: 300px;
  line-height: 1.5;
}

/* PASS L1 reveal -- INVERTED Custom Highlight.
   Paint properties only. Nothing here participates in layout: every governed key
   keeps its complete text, natural wrapping and intrinsic width throughout.

   The element keeps its ORDINARY colour. The unrevealed suffix is covered by a
   per-card Highlight painted transparent, and the reveal shrinks that range.
   Deleting a card's registry entry therefore REVEALS its text, which is why every
   failure path ends visible instead of risking stranded transparency.

   Card 01's role carries two adjacent INLINE spans. They exist to give the chain a
   stable node boundary for the authored 80ms pause; they must not take ownership
   of layout, so nothing here changes their inline behaviour or their colour. */
::highlight(rv-hide-01),
::highlight(rv-hide-02) {
  color: transparent;
}

/* CTA reveal lifecycle.
   .js gates the pending state on purpose: the bootstrap failsafe removes .js, so
   the rule simply stops matching and the control becomes visible with no
   specificity contest to win. This selector is (0,5,0): an override must beat
   that, or match it and appear LATER in this sheet. One that does neither looks
   like a fix and silently does nothing.

   Reduced motion needs no override here. initStream() returns before
   primeChain() on that path, so .cta-pending is never applied on an initial
   reduced-motion load. A mid-session change of prefers-reduced-motion is out of
   contract: the media query would go live while the chain is still running, and
   no CSS-only rule settles that -- it needs a whole-chain decision.

   The animation name differs from card-content-fade because the bootstrap's
   release listener filters on that name and is not once-only. */
.js .card-active.materialized .enter-system-btn.cta-pending {
  visibility: hidden;
}

.card-active.materialized .enter-system-btn.cta-revealing {
  animation: cta-reveal-fade 300ms ease forwards;
}

.card-active.materialized .enter-system-btn.cta-settled {
  animation: none;
  opacity: 1;
}

@keyframes cta-reveal-fade {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}

.card-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 2.035rem;
  line-height: 1.1;
  letter-spacing: var(--tracking-tight);
  text-transform: uppercase;
  color: var(--text-primary);
  margin-top: var(--space-sm);
  padding-bottom: var(--space-sm);
}

.card-tags {
  list-style: none;
  margin-top: var(--space-md);
  flex: 1;
}

.card-tags li {
  font-size: 0.910rem;
  color: var(--text-secondary);
  padding: var(--space-xs) 0;
  letter-spacing: var(--tracking-tight);
}

/* PASS L2 F3: the '// ' prefix is DOM text in span.tag-prefix, inside the tag's
   hide Range, so it streams with the tag. Generated content (the former
   li::before) cannot be covered by a Range and painted before its tag began. */
.card-tags .tag-prefix {
  color: var(--accent-teal-dim);
}

.enter-system-btn {
  background: transparent;
  border: 1px solid var(--accent-teal);
  color: var(--accent-teal);
  font-family: var(--font-mono);
  font-size: 0.8925rem;
  letter-spacing: var(--tracking-normal);
  text-transform: uppercase;
  padding: var(--space-sm) var(--space-md);
  cursor: pointer;
  transition: var(--transition-default);
  margin-top: var(--space-md);
  width: fit-content;
}

@media (hover: hover) and (pointer: fine) {
  .enter-system-btn:hover {
    background: var(--accent-teal);
    color: var(--bg-base);
  }
}

.enter-system-btn:focus-visible {
  background: var(--accent-teal);
  color: var(--bg-base);
  outline: 2px solid rgba(45, 212, 236, 0.8);
  outline-offset: 4px;
}

.enter-system-btn:active {
  background: var(--accent-teal);
  color: var(--bg-base);
  box-shadow: inset 0 0 0 3px var(--bg-base);
}

/* ── Footer ── */
footer {
  position: relative;
  z-index: var(--z-base);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-lg);
  padding: var(--space-lg) 0 0;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: var(--tracking-tight);
  text-transform: uppercase;
  color: var(--text-muted);
}

footer p:nth-child(1)::before,
footer p:nth-child(2)::before,
footer p:nth-child(3)::before {
  content: '● ';
  color: var(--status-green);
}

/* ── Responsive ── */
@media (max-width: 900px) {
  /* Mobile portrait composition. Card 01 uses a dedicated art-directed source
     below 901px. Card 02 has no portrait image, so the scoped rules below
     cannot reach it.

     The previous composition widened the card-top-to-role window to 50px and
     lowered the shared desktop source into it, because that source had no
     horizontal travel left to clear the subject from the right card edge at
     320px. The dedicated 1427x2512 source carries that clearance in its own
     canvas instead, so the window returns to the design token and the
     composition is expressed as a cover crop.

       margin-top var(--space-xs)   window returns to the 4px token
       object-fit cover             the canvas, not the offset, supplies the
                                    subject clearance
       object-position 50% 5.8904%  frozen Route B composition
       translateY(-2px)             retained from the shared mobile rule

     Route B under production typography: 320px right gap 4.00px, no head/role
     collision at any measured width, and bottom geometry at or better than the
     accepted baseline at 390/397/414/430/900. The lower subject edge descends
     into the established vignette by design; do not reinterpret that as a
     containment failure.

     The vertical percentage resolves against the content box, which is inset
     by the 25px padding-top on .card-portrait-img. Re-run the Card 01
     clearance checks before changing the padding, the canvas, or either
     object- value. */
  #card-profile .card-role {
    margin-top: var(--space-xs);
  }

  #card-profile .card-portrait-img {
    object-fit: cover;
    object-position: 50% 5.8904%;
  }

  .card-portrait-img {
    object-position: 90% bottom;
    transform: translateY(-2px);
  }

  main {
    flex-direction: column;
    /* Base gap is 120px, tuned for the side-by-side desktop pair. Stacked, it
       reads as two disconnected pages rather than one module list, so use the
       design token instead of an arbitrary literal. */
    gap: var(--space-2xl);
  }

  .card-active {
    /* Previously a flat max-width:320px, which left 70px of dead margin on a
       390px viewport while every other page used ~93% of available width.
       Now uses the site's responsive width language, capped for tablets. */
    width: min(100% - 28px, 560px);
    max-width: none;
  }

  .enter-system-btn {
    /* The label wraps below ~430px, which is fine -- but across two 20px bands
       (Portal 390-409, Dossier 410-429) the break landed so that the chevron
       was left alone on the second line. Balancing the line boxes keeps the
       separator with its label at every width without changing type size,
       padding, or the button box. */
    text-wrap: balance;
    /* <button> defaults to text-align:center. On a wrapped label that centres
       the shorter second line, and the chevron makes it read as weighted to
       one side. Left is also what the rest of the card content does. The
       button is width:fit-content, so on an unwrapped label there is no spare
       width for this to redistribute -- it only takes visible effect where the
       label actually wraps. */
    text-align: left;
    /* WCAG 2.5.5 control floor. A single-line CTA settles at 34px; wrapped it
       is already 50px. This is a FLOOR, not a fixed height -- the compliant
       two-line state must not be squeezed. inline-flex + align-items:center
       keeps the label optically centred once the box is taller than its text;
       justify-content:flex-start preserves the L2b flush-left contract. */
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: flex-start;
  }
}

@media (min-width: 393px) and (max-width: 900px) {
  /* Normalize the mobile CTA pair without altering its typography. The labels
     differ by two rendered character cells, so the shorter Portal control gets
     the equivalent width as right-side box chrome. This keeps both controls
     wrapped through 393-410 and gives them the same one-line transition at 411.

     Reconstruct the measured two-cell difference from the existing typography
     tokens instead of hardcoding a pixel width: ch supplies the glyph advance
     and the existing tracking token supplies the spacing component.

     Scope begins at 393 because 320-392 is already normalized by the card
     constraint, and ends at 900 because desktop remains intentionally unequal. */
  #card-portal .enter-system-btn {
    padding-right: calc(var(--space-md) + 2ch + 2 * var(--tracking-normal));
  }
}

/* Streaming geometry reservation -- footer status lines.

   Measured defect: `footer` is a centred wrapping flex row. While the three
   status lines stream, their intrinsic widths grow every tick, which re-runs
   the flex line assignment. hud-status3 crosses to the second flex line
   mid-stream and re-centres, relocating already-painted text by up to 150px
   horizontally. That is why its own text never re-wrapped (rectcount 0) while
   it moved 143px.

   Their final widths are known before the first character paints, so reserving
   them removes the moving geometry without changing how anything streams.

   Derived, not hardcoded. footer runs var(--font-mono), so 1ch is the glyph
   advance -- but `ch` EXCLUDES letter-spacing, and these lines carry
   var(--tracking-tight). Reserving Nch alone leaves ~7.5px short per line,
   which only relocates the flex crossing rather than removing it. The tracking
   term is therefore required, not cosmetic:
     15 chars -> 15ch + 15 * tracking = 108.53px against a 108.5px final width.

   Deliberately unconditional. A breakpoint here would need a defect boundary
   that has not been measured, and the reservation equals the natural width at
   every width, so it changes no settled geometry anywhere. */
footer p:nth-child(1) { min-width: calc(15ch + 15 * var(--tracking-tight)); }
footer p:nth-child(2),
footer p:nth-child(3) { min-width: calc(16ch + 16 * var(--tracking-tight)); }
