archived fan signal // monochrome entry point

RANFREN

Weird little house. Lucids. Human pets. Catmen. Brotherhood, static, and all the wrong kinds of tenderness.

content warning: surreal horror / unsettling imagery / gore themes

press enter, or click the screen and go inside.

fan shrine for captainhowdie's strange little world

RANFREN HOUSE SIGNAL

A big, messy dossier for the Ivory household: the dream-logic suburb, the house order, the pet dynamics, the creature etiquette, and the way the whole comic feels half nightmare and half old web miracle.

Ranfren, also known as Randal's Friends, is a surreal horror webcomic by Captainhowdie. It started as short strips in 2011 and grew into the full Lucids comic format in 2015, all orbiting the cursed calm of the Great Canadian suburbs.

The whole pull is the contrast: grotesque body horror next to dry jokes, domestic rituals next to cosmic weirdness, and household pets who feel equal parts tragic, dangerous, and weirdly sweet.

house disturbance monitor signal stable

The house is quiet for now. That usually means somebody is about to make it worse.

  • lucids, mini-comics, cartoons, scraps
  • old web archive energy turned all the way up
  • drag the pictures around once you're inside

subject locked

high noise

temperament

household tie

vibe check

fan note

the whole site runs on contradiction

Ranfren works because it never behaves like one thing for long. It goes from ugly to tender, funny to menacing, domestic to unreal, and it still somehow feels like a lived-in place.

big pulls

  • surreal suburbia that feels personal instead of random
  • character dynamics that are hostile, clingy, and intimate
  • an archive aesthetic that still feels handmade and alive

site mood

  • monochrome textures with one bruised accent color
  • scratchy screen drift and fake monitor distortion
  • oversized type like a warning pasted on the wall

> signal ready

> the house keeps everybody, one way or another

> waiting for new disturbance...

Ranfren House Signal

archived fan signal // monochrome entry point

RANFREN

Weird little house. Lucids. Human pets. Catmen. Brotherhood, static, and all the wrong kinds of tenderness.

content warning: surreal horror / unsettling imagery / gore themes

press enter, or click the screen and go inside.

fan shrine for captainhowdie's strange little world

RANFREN HOUSE SIGNAL

A big, messy dossier for the Ivory household: the dream-logic suburb, the house order, the pet dynamics, the creature etiquette, and the way the whole comic feels half nightmare and half old web miracle.

Ranfren, also known as Randal's Friends, is a surreal horror webcomic by Captainhowdie. It started as short strips in 2011 and grew into the full Lucids comic format in 2015, all orbiting the cursed calm of the Great Canadian suburbs.

The whole pull is the contrast: grotesque body horror next to dry jokes, domestic rituals next to cosmic weirdness, and household pets who feel equal parts tragic, dangerous, and weirdly sweet.

house disturbance monitor signal stable

The house is quiet for now. That usually means somebody is about to make it worse.

  • lucids, mini-comics, cartoons, scraps
  • old web archive energy turned all the way up
  • drag the pictures around once you're inside

subject locked

high noise

temperament

household tie

vibe check

fan note

the whole site runs on contradiction

Ranfren works because it never behaves like one thing for long. It goes from ugly to tender, funny to menacing, domestic to unreal, and it still somehow feels like a lived-in place.

big pulls

  • surreal suburbia that feels personal instead of random
  • character dynamics that are hostile, clingy, and intimate
  • an archive aesthetic that still feels handmade and alive

site mood

  • monochrome textures with one bruised accent color
  • scratchy screen drift and fake monitor distortion
  • oversized type like a warning pasted on the wall

> signal ready

> the house keeps everybody, one way or another

> waiting for new disturbance...

const characters = { randal: { name: "Randal Ivory", role: "titular menace / cryptid protagonist / doll-loving chaos engine", status: "distortion spike / queen of spades energy", threat: "very high noise", relation: "Randal drags the whole house toward disorder, then somehow turns that disorder into a social event.", summary: "Randal is the center of the comic's gremlin gravity: clingy, theatrical, morbid, funny, and always looking for a new game even when the game becomes a problem for everybody else.", temperament: "Childish bravado, black-comedy chaos, and a weird sincere need for connection. He is the kind of character who can feel playful and threatening in the same breath.", household: "Lives under Luther's rule, keeps orbiting Sebastian like a favorite bad idea, and keeps poking at the edges of everybody else's patience.", vibe: "Coffin sleeper. Dreamwalker. Troublemaker in a formal coat. He feels like a haunted toy chest that learned to flirt with danger.", fanNote: "If the site has one heartbeat, it's this guy: theatrical, ugly-cute, and impossible to tone down without losing the whole charm.", facts: [ "main protagonist of Randal's Friends", "older brother conflict with Luther", "obsessed with dolls, games, and weird little plans", "sleeps in a coffin", ], sticker: "https://img.sanishtech.com/u/ee4cc5dac8944da5f064b1c31f12d468.gif", relics: [ "https://img.sanishtech.com/u/4d75c19940f103711764dce3656cf15e.jpeg", "https://img.sanishtech.com/u/9f60872f9e6b0b2299bf50dcdde1d65b.jpeg", "https://img.sanishtech.com/u/ef0ec1e196a8a043abd301be54daf324.jpeg", "https://img.sanishtech.com/u/ee4cc5dac8944da5f064b1c31f12d468.gif", "https://img.sanishtech.com/u/d9deeaff050d299bee098ecb77d85712.jpeg", ], }, luther: { name: "Luther Von Ivory", role: "house king / older brother / eerie domestic authority", status: "order ritual in progress", threat: "moderate to high", relation: "Luther is the architecture of the house itself: he keeps the rules, the pets, the hosting energy, and the unsettling calm together.", summary: "Friendly on the surface, rigid underneath, and never fully trustworthy, Luther gives the Ivory household its haunted etiquette. He feels like a parent, a landlord, and a monster all at once.", temperament: "Controlled, corrective, strangely nurturing, and capable of flipping from helpful to frightening with almost no warning.", household: "Randal's older brother, master of Nyen and Nyon, and the figure who keeps the household functioning even when it has no right to function at all.", vibe: "Formal shirt, eerie smile, cryptic body language, uncanny human act. He is the clean tablecloth thrown over something much stranger.", fanNote: "The whole comic gets ten times weirder because Luther plays host so sincerely. The calm makes the horror hit harder.", facts: [ "deuteragonist and occasional antagonist", "runs the Ivory household", "claims his humanness a little too hard", "guardian, host, and control freak", ], sticker: "https://img.sanishtech.com/u/692ccfb9fdea8cff07ec71c4cb9bbbbb.gif", relics: [ "https://img.sanishtech.com/u/793ecc56611afbdec60c7a988bafa42d.jpeg", "https://img.sanishtech.com/u/683b397a51b330717051f2287eac9f9f.jpeg", "https://img.sanishtech.com/u/01f368e47b3aa18530223cdd69dd12da.jpeg", "https://img.sanishtech.com/u/edc41560a26954d1eb5318a586df1483.jpeg", "https://img.sanishtech.com/u/692ccfb9fdea8cff07ec71c4cb9bbbbb.gif", ], }, sebastian: { name: "Sebastian", role: "Randal's human pet / unwilling grounding wire", status: "flight response active", threat: "human under pressure", relation: "Sebastian is the best reminder that the house is absurd because he keeps reacting like somebody who knows it should not be normal.", summary: "Sebastian brings the sharpest everyman panic to the cast. He is sarcastic, trapped, and constantly measuring the distance between himself and everyone else's creature behavior.", temperament: "Dry, defensive, tired, and more durable than he should have to be. He is always one bad interaction away from bolting.", household: "Bought for Randal, monitored by Luther, bullied by the catmen, and still somehow one of the emotional anchors of the whole setup.", vibe: "Pierrot-ish victim energy with enough bite to keep him from becoming only a prop in everybody else's nonsense.", fanNote: "He gives the world friction. Without Sebastian, the house would feel dreamlike in a flatter way; with him, it feels wrong on purpose.", facts: [ "human living in the Ivory household", "introduced as Randal's pet", "one of the least supernatural presences in the cast", "panic, sarcasm, and survival instincts", ], sticker: "https://img.sanishtech.com/u/16235e2492636233ccfc7f036e8dc2e0.gif", relics: [ "https://img.sanishtech.com/u/3bbd7ab5bf567b3967626480cce24e82.jpeg", "https://img.sanishtech.com/u/f6bfef89bc632adf53d47e6746b2c022.jpeg", "https://img.sanishtech.com/u/d563dc94b404267aa9f6f2877cabed3d.jpeg", "https://img.sanishtech.com/u/16235e2492636233ccfc7f036e8dc2e0.gif", ], }, nyon: { name: "Nyon", role: "catman / quieter pet / eerie deadpan bystander", status: "avoidance pattern detected", threat: "low and uneasy", relation: "Nyon feels like the soft-spoken edge of the household, but never in a way that makes the house feel safer. Just quieter.", summary: "Shy, odd, and more withdrawn than the others, Nyon adds a different kind of tension: not loud aggression, but a vague and watchful unease that still belongs to the same broken house.", temperament: "Timid on the surface, sarcastic underneath, and prone to quietly enduring the nonsense rather than confronting it head-on.", household: "One of Luther's catmen, often caught in Nyen's orbit, and one of the few figures who can feel genuinely low-volume without ever feeling ordinary.", vibe: "Bent posture, tired eyes, weird little hat, and the energy of somebody who knows the room is bad and stays there anyway.", fanNote: "Nyon is the low-hum static of the household. He makes the group dynamic feel sadder and stranger, not just louder.", facts: [ "supporting catman in the Ivory household", "first catman under Luther's rule", "quieter and less confrontational than Nyen", "a good fit for the comic's sad-weird register", ], sticker: "https://img.sanishtech.com/u/0d301db843703f8ffe38378b2d142fb1.gif", relics: [ "https://img.sanishtech.com/u/22dfb02d3ab0f3445c3edc4fd34dd29d.jpeg", "https://img.sanishtech.com/u/3ebc2f20cab74fd2d937c4f260df5d48.jpeg", "https://img.sanishtech.com/u/4be11a73a10bf1cba8c50460a2970d0c.jpeg", "https://img.sanishtech.com/u/0d301db843703f8ffe38378b2d142fb1.gif", ], }, nyen: { name: "Nyen", role: "catman / house enforcer / violent pet with a short fuse", status: "territorial hostility online", threat: "very high", relation: "Nyen is what happens when loyalty curdles into menace. He obeys the house hierarchy, then makes everyone else feel it.", summary: "Nyen is colder, meaner, and more openly dangerous than Nyon, which makes him one of the clearest pressure points in the whole cast. He turns household power into something physical.", temperament: "Calm until he is not, proud, sadistic, sarcastic, and always one disrespect away from becoming the room's immediate problem.", household: "Luther's fiercely loyal catman, Nyon's rival, and a constant intimidation factor for Sebastian and anybody else too close to his mood.", vibe: "Cigarettes, dark sweater, cat-ear silhouette, red-eyed stare. He feels like the house weaponized into a roommate.", fanNote: "Nyen adds the raw edge. He keeps the comic from feeling merely dreamy by making the danger personal and direct.", facts: [ "second catman pet of Luther", "one of the most violent household members", "territorial and controlling", "a big part of the comic's menace", ], sticker: "https://img.sanishtech.com/u/f9960f4a53dab95e6a4b6b4fba4bb33f.gif", relics: [ "https://img.sanishtech.com/u/0eacb5cd609a1c143ae7e2b4fbb99c9b.jpeg", "https://img.sanishtech.com/u/2ee83c193286a89ef6600c4c4a715226.jpeg", "https://img.sanishtech.com/u/e9707924c9cb27139ad8c70a10bc1421.gif", "https://img.sanishtech.com/u/b4d5d06826bc0b12b6dcbf4b85939c23.jpeg", "https://img.sanishtech.com/u/f9960f4a53dab95e6a4b6b4fba4bb33f.gif", ], }, }; const characterOrder = ["randal", "luther", "sebastian", "nyon", "nyen"]; const monitorLines = [ "The wallpaper is listening again. That usually means Randal is awake.", "Luther has restored order, which only makes the room look more suspicious.", "Sebastian spotted an exit and lost it immediately.", "Nyen is in a bad mood. Assume every object in the room can become a warning.", "Nyon is being quiet in the dangerous way, not the peaceful way.", "The house feels domestic for three seconds. Nobody believes it.", ]; const logLines = [ "> static passed through the hallway and left a stain", "> somebody shuffled the family dynamic again", "> this house keeps inventing new kinds of normal", "> the monitor insists this is still a home", "> pet hierarchy remains emotionally unsafe", "> fan shrine running loud and unstable", ]; const buttonsRoot = document.getElementById("characterButtons"); const relationNote = document.getElementById("relationNote"); const roleLine = document.getElementById("roleLine"); const characterName = document.getElementById("characterName"); const characterSummary = document.getElementById("characterSummary"); const factList = document.getElementById("factList"); const temperamentText = document.getElementById("temperamentText"); const householdText = document.getElementById("householdText"); const vibeText = document.getElementById("vibeText"); const fanNoteText = document.getElementById("fanNoteText"); const featureImage = document.getElementById("featureImage"); const featureSticker = document.getElementById("featureSticker"); const threatTag = document.getElementById("threatTag"); const dossierStatus = document.getElementById("dossierStatus"); const galleryCollage = document.getElementById("galleryCollage"); const disturbButton = document.getElementById("disturbButton"); const shuffleButton = document.getElementById("shuffleButton"); const disturbanceLevel = document.getElementById("disturbanceLevel"); const monitorMessage = document.getElementById("monitorMessage"); const terminalWindow = document.getElementById("terminalWindow"); const enterButton = document.getElementById("enterButton"); const bootScreen = document.getElementById("bootScreen"); let activeCharacter = "randal"; let zIndexCounter = 10; function createCharacterButtons() { characterOrder.forEach((key) => { const data = characters[key]; const button = document.createElement("button"); button.type = "button"; button.className = "character-button"; button.dataset.character = key; button.innerHTML = `${data.name}${data.role}`; button.addEventListener("click", () => setActiveCharacter(key)); buttonsRoot.appendChild(button); }); } function renderFacts(facts) { factList.innerHTML = ""; facts.forEach((fact) => { const chip = document.createElement("span"); chip.className = "fact-chip"; chip.textContent = fact; factList.appendChild(chip); }); } function setActiveCharacter(key) { activeCharacter = key; const data = characters[key]; document.querySelectorAll(".character-button").forEach((button) => { button.classList.toggle("active", button.dataset.character === key); }); roleLine.textContent = data.role; characterName.textContent = data.name; characterSummary.textContent = data.summary; temperamentText.textContent = data.temperament; householdText.textContent = data.household; vibeText.textContent = data.vibe; fanNoteText.textContent = data.fanNote; relationNote.textContent = data.relation; threatTag.textContent = data.threat; dossierStatus.textContent = data.status; featureImage.src = data.relics[0]; featureImage.alt = `${data.name} relic portrait`; featureSticker.src = data.sticker; featureSticker.alt = `${data.name} animated sticker`; renderFacts(data.facts); renderGallery(data.relics, data.name); } function randomBetween(min, max) { return Math.random() * (max - min) + min; } function renderGallery(relics, label) { galleryCollage.innerHTML = ""; const isSmallScreen = window.matchMedia("(max-width: 720px)").matches; const positions = relics.map((_, index) => ({ left: isSmallScreen ? randomBetween(8, 42) : randomBetween(4, 64), top: isSmallScreen ? index * 15 + randomBetween(2, 8) : randomBetween(3, 70), rotate: randomBetween(-10, 10), })); relics.forEach((src, index) => { const card = document.createElement("figure"); const isGif = src.endsWith(".gif"); card.className = `relic-card${isGif ? " is-gif" : ""}`; card.style.left = `${positions[index].left}%`; card.style.top = `${positions[index].top}%`; card.style.transform = `rotate(${positions[index].rotate}deg)`; card.style.zIndex = String(zIndexCounter++); card.innerHTML = ` ${label} relic ${index + 1}
${label} frame ${String(index + 1).padStart(2, "0")}
`; card.addEventListener("click", () => { featureImage.src = src; featureImage.alt = `${label} selected relic`; card.style.zIndex = String(zIndexCounter++); pushLog(`> ${label.toLowerCase()} frame ${index + 1} moved to main screen`); }); makeDraggable(card); galleryCollage.appendChild(card); }); } function makeDraggable(element) { let pointerId = null; let startX = 0; let startY = 0; let originX = 0; let originY = 0; element.addEventListener("pointerdown", (event) => { pointerId = event.pointerId; startX = event.clientX; startY = event.clientY; originX = element.offsetLeft; originY = element.offsetTop; element.style.zIndex = String(zIndexCounter++); element.setPointerCapture(pointerId); }); element.addEventListener("pointermove", (event) => { if (pointerId !== event.pointerId) { return; } const deltaX = event.clientX - startX; const deltaY = event.clientY - startY; element.style.left = `${originX + deltaX}px`; element.style.top = `${originY + deltaY}px`; }); function release(event) { if (pointerId !== event.pointerId) { return; } element.releasePointerCapture(pointerId); pointerId = null; } element.addEventListener("pointerup", release); element.addEventListener("pointercancel", release); } function pushLog(line) { const entry = document.createElement("p"); entry.textContent = line; terminalWindow.prepend(entry); while (terminalWindow.children.length > 6) { terminalWindow.removeChild(terminalWindow.lastElementChild); } } function disturbHouse() { const body = document.body; const line = monitorLines[Math.floor(Math.random() * monitorLines.length)]; const log = logLines[Math.floor(Math.random() * logLines.length)]; monitorMessage.textContent = line; disturbanceLevel.textContent = `disturbance ${Math.floor(randomBetween(72, 100))}%`; body.classList.add("disturbed", "glitching", "shake"); pushLog(log); setTimeout(() => body.classList.remove("glitching", "shake"), 360); } function shuffleRelics() { setActiveCharacter(activeCharacter); pushLog(`> ${characters[activeCharacter].name.toLowerCase()} relic wall shuffled`); } function enterHouse() { document.body.classList.add("entered"); bootScreen.setAttribute("aria-hidden", "true"); pushLog("> entry accepted"); } enterButton.addEventListener("click", enterHouse); document.addEventListener("keydown", (event) => { if (event.key === "Enter" && !document.body.classList.contains("entered")) { enterHouse(); } }); bootScreen.addEventListener("click", (event) => { if (event.target === bootScreen) { enterHouse(); } }); disturbButton.addEventListener("click", disturbHouse); shuffleButton.addEventListener("click", shuffleRelics); window.addEventListener("resize", () => setActiveCharacter(activeCharacter)); createCharacterButtons(); setActiveCharacter(activeCharacter); :root { --bg: #050505; --bg-soft: #0d0d0d; --panel: rgba(10, 10, 10, 0.84); --line: rgba(245, 241, 231, 0.2); --line-strong: rgba(245, 241, 231, 0.44); --paper: #f5f1e7; --muted: #b2ada4; --dim: #7c7972; --accent: #8f1f1f; --accent-soft: rgba(143, 31, 31, 0.25); --shadow: rgba(0, 0, 0, 0.45); --title-font: "Rubik Glitch", cursive; --display-font: "Chokokutai", cursive; --body-font: "IBM Plex Mono", monospace; } * { box-sizing: border-box; } html { scroll-behavior: smooth; } body { margin: 0; min-height: 100vh; overflow-x: hidden; background: radial-gradient(circle at top, rgba(143, 31, 31, 0.14), transparent 28%), radial-gradient(circle at 85% 12%, rgba(255, 255, 255, 0.05), transparent 18%), linear-gradient(145deg, #070707, #000000 55%, #090909); color: var(--paper); font-family: var(--body-font); } body.disturbed { background: radial-gradient(circle at top, rgba(143, 31, 31, 0.22), transparent 34%), radial-gradient(circle at 85% 12%, rgba(255, 255, 255, 0.1), transparent 18%), linear-gradient(145deg, #000000, #120808 55%, #050505); } img { display: block; max-width: 100%; } button, a { color: inherit; } .page-noise, .page-scanlines, .page-fog { position: fixed; inset: 0; pointer-events: none; z-index: 5; } .page-noise { opacity: 0.13; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140' viewBox='0 0 140 140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E"); mix-blend-mode: screen; } .page-scanlines { background-image: linear-gradient( to bottom, transparent 0, transparent 3px, rgba(255, 255, 255, 0.03) 4px ); background-size: 100% 4px; opacity: 0.32; } .page-fog { background: radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.06), transparent 22%), radial-gradient(circle at 80% 35%, rgba(255, 255, 255, 0.04), transparent 25%), radial-gradient(circle at 50% 100%, rgba(143, 31, 31, 0.08), transparent 30%); filter: blur(36px); opacity: 0.8; animation: fogDrift 18s linear infinite alternate; } .site-shell { position: relative; z-index: 10; padding: 2rem; opacity: 0; transform: translateY(2rem); transition: opacity 500ms ease, transform 500ms ease; } body.entered .site-shell { opacity: 1; transform: translateY(0); } .boot-screen { position: fixed; inset: 0; z-index: 40; display: grid; place-items: center; padding: 1.5rem; background: radial-gradient(circle at center, rgba(143, 31, 31, 0.16), transparent 30%), rgba(0, 0, 0, 0.9); transition: opacity 500ms ease, visibility 500ms ease; } body.entered .boot-screen { opacity: 0; visibility: hidden; } .boot-panel { width: min(100%, 720px); padding: 2rem; border: 1px solid var(--line-strong); background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), transparent), rgba(8, 8, 8, 0.92); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.04), 0 24px 80px rgba(0, 0, 0, 0.65); text-align: center; } .boot-kicker, .section-tag, .note-label, .detail-label, .role-line, .dossier-status, .gallery-hint, .monitor-header, .boot-warning, .boot-hint { letter-spacing: 0.18em; text-transform: uppercase; font-size: 0.72rem; } .boot-panel h1 { margin: 0.4rem 0 1rem; font-family: var(--title-font); font-size: clamp(2.8rem, 9vw, 6.7rem); line-height: 0.95; } .boot-text { max-width: 38rem; margin: 0 auto 1.2rem; color: var(--muted); line-height: 1.7; } .boot-warning { display: inline-flex; flex-wrap: wrap; justify-content: center; gap: 0.8rem; margin-bottom: 1.6rem; padding: 0.85rem 1rem; border: 1px dashed var(--line); color: var(--paper); background: rgba(255, 255, 255, 0.03); } .enter-button, .monitor-actions button, .character-button { border: 1px solid var(--line-strong); background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), transparent); color: var(--paper); font-family: var(--body-font); cursor: pointer; transition: transform 180ms ease, border-color 180ms ease, background-color 180ms ease, box-shadow 180ms ease; } .enter-button { padding: 0.95rem 1.8rem; font-size: 0.96rem; letter-spacing: 0.2em; text-transform: uppercase; } .enter-button:hover, .monitor-actions button:hover, .character-button:hover, .enter-button:focus-visible, .monitor-actions button:focus-visible, .character-button:focus-visible { transform: translateY(-2px); border-color: var(--paper); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.07), 0 0 26px rgba(143, 31, 31, 0.24); outline: none; } .hero { display: grid; gap: 1.5rem; margin-bottom: 1.5rem; } .hero-copy { position: relative; padding: 1.25rem; border: 1px solid var(--line); background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), transparent 35%), var(--panel); overflow: hidden; } .hero-copy::before, .dossier-panel::before, .gallery-panel::before, .signal-panel::before, .selector-panel::before { content: ""; position: absolute; inset: 0; background: linear-gradient(130deg, rgba(255, 255, 255, 0.05), transparent 34%); pointer-events: none; } .eyebrow { margin: 0 0 0.8rem; color: var(--muted); letter-spacing: 0.22em; text-transform: uppercase; font-size: 0.76rem; } .hero-title { margin: 0; display: grid; gap: 0.25rem; font-family: var(--title-font); font-size: clamp(3.2rem, 12vw, 8.6rem); line-height: 0.88; } .hero-title span:last-child { font-family: var(--display-font); font-size: clamp(2.1rem, 9vw, 6rem); letter-spacing: 0.12em; } .hero-text { max-width: 60rem; margin: 1rem 0 0; font-size: 1rem; line-height: 1.8; color: var(--muted); } .hero-panels { display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); } .info-card, .monitor-card, .selector-panel, .dossier-panel, .gallery-panel, .signal-panel { position: relative; border: 1px solid var(--line); background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent 36%), var(--panel); box-shadow: 0 16px 40px var(--shadow); } .info-card, .monitor-card, .selector-panel, .dossier-panel, .gallery-panel, .signal-panel { padding: 1.25rem; } .info-card p, .signal-copy p, .selector-note p, .detail-card p, .monitor-card p, .monitor-notes li { line-height: 1.75; } .portal-links { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1rem; } .portal-links a { padding: 0.55rem 0.8rem; text-decoration: none; border: 1px solid var(--line); background: rgba(255, 255, 255, 0.03); } .monitor-card { display: grid; gap: 1rem; } .monitor-header { display: flex; justify-content: space-between; gap: 1rem; color: var(--muted); } .monitor-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; } .monitor-actions button, .character-button { padding: 0.75rem 0.9rem; } .monitor-notes { margin: 0; padding-left: 1rem; color: var(--muted); } .main-grid { display: grid; gap: 1rem; grid-template-columns: 280px minmax(0, 1.2fr) minmax(320px, 1fr); align-items: start; } .selector-panel { display: grid; gap: 1.1rem; } .selector-head h2, .gallery-head h2, .signal-copy h2, .dossier-copy h2 { margin: 0.35rem 0 0; font-family: var(--display-font); font-size: clamp(1.6rem, 4vw, 3rem); letter-spacing: 0.08em; } .character-buttons { display: grid; gap: 0.7rem; } .character-button { text-align: left; background: linear-gradient(90deg, rgba(143, 31, 31, 0.18), transparent 80%), rgba(255, 255, 255, 0.03); } .character-button strong { display: block; margin-bottom: 0.2rem; font-size: 1rem; } .character-button span { color: var(--muted); font-size: 0.8rem; } .character-button.active { border-color: var(--paper); background: linear-gradient(90deg, rgba(143, 31, 31, 0.35), transparent 85%), rgba(255, 255, 255, 0.05); transform: translateX(6px); } .selector-note { padding-top: 0.5rem; border-top: 1px solid var(--line); color: var(--muted); } .dossier-panel { min-height: 100%; } .dossier-topline, .gallery-head { display: flex; align-items: start; justify-content: space-between; gap: 1rem; } .dossier-status { color: var(--muted); margin: 0; } .dossier-body { display: grid; gap: 1rem; grid-template-columns: minmax(260px, 0.9fr) minmax(0, 1.1fr); margin-top: 1rem; } .portrait-stage { position: relative; min-height: 520px; padding: 1rem; border: 1px solid var(--line); background: radial-gradient(circle at 50% 20%, rgba(143, 31, 31, 0.2), transparent 22%), linear-gradient(180deg, rgba(255, 255, 255, 0.05), transparent 65%), rgba(0, 0, 0, 0.55); overflow: hidden; } .portrait-stage::before { content: "ivory household // field capture"; position: absolute; left: 1rem; top: 1rem; color: var(--dim); font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase; } #featureImage { width: 100%; height: 100%; min-height: 460px; object-fit: cover; filter: grayscale(1) contrast(1.04); } .feature-sticker { position: absolute; right: -0.6rem; bottom: -0.4rem; width: clamp(110px, 24%, 160px); border: 1px solid var(--line); background: rgba(0, 0, 0, 0.8); box-shadow: 0 16px 36px rgba(0, 0, 0, 0.44); } .threat-tag { position: absolute; left: 1rem; bottom: 1rem; padding: 0.5rem 0.72rem; background: rgba(0, 0, 0, 0.7); border: 1px solid var(--line); letter-spacing: 0.15em; text-transform: uppercase; font-size: 0.72rem; } .dossier-copy { display: grid; gap: 0.9rem; align-content: start; } .role-line { color: var(--muted); margin: 0; } .character-summary { margin: 0; color: var(--muted); line-height: 1.85; } .fact-list { display: flex; flex-wrap: wrap; gap: 0.6rem; } .fact-chip { display: inline-flex; padding: 0.5rem 0.72rem; border: 1px solid var(--line); background: rgba(255, 255, 255, 0.03); font-size: 0.78rem; color: var(--paper); } .detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.8rem; } .detail-card { min-height: 100%; padding: 0.85rem; border: 1px solid var(--line); background: rgba(255, 255, 255, 0.025); } .detail-card p:last-child { margin-bottom: 0; color: var(--muted); } .gallery-panel { display: grid; gap: 1rem; } .gallery-hint { max-width: 18rem; margin: 0; color: var(--muted); text-align: right; } .gallery-collage { position: relative; min-height: 860px; border: 1px dashed var(--line); background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.03), transparent 35%), rgba(0, 0, 0, 0.42); overflow: hidden; } .relic-card { position: absolute; width: clamp(150px, 22vw, 230px); padding: 0.45rem; border: 1px solid var(--line); background: rgba(4, 4, 4, 0.92); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.38); cursor: grab; user-select: none; touch-action: none; transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease; } .relic-card:active { cursor: grabbing; } .relic-card:hover { border-color: var(--paper); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08), 0 18px 36px rgba(0, 0, 0, 0.5); } .relic-card img { width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; filter: grayscale(1) contrast(1.08); } .relic-card figcaption { display: flex; justify-content: space-between; gap: 0.6rem; padding-top: 0.45rem; color: var(--muted); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.16em; } .relic-card.is-gif figcaption::after { content: "gif"; color: var(--paper); } .signal-panel { display: grid; gap: 1rem; grid-column: 2 / span 2; grid-template-columns: minmax(0, 1.2fr) 340px; } .signal-columns { display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 1rem; } .signal-columns h3 { margin: 0 0 0.8rem; font-size: 0.84rem; letter-spacing: 0.16em; text-transform: uppercase; } .signal-columns ul { margin: 0; padding-left: 1rem; color: var(--muted); line-height: 1.7; } .terminal-card { display: grid; align-content: start; gap: 0.75rem; } .terminal-window { min-height: 100%; padding: 1rem; border: 1px solid var(--line); background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent 35%), rgba(0, 0, 0, 0.62); color: var(--muted); line-height: 1.7; } .terminal-window p { margin: 0 0 0.55rem; } .terminal-window p:last-child { margin-bottom: 0; } .glitching .hero-title span:first-child, .glitching #characterName, .glitching .boot-panel h1 { text-shadow: 2px 0 rgba(255, 255, 255, 0.35), -2px 0 rgba(143, 31, 31, 0.65); } .shake .portrait-stage, .shake .monitor-card, .shake .hero-copy { animation: panelShake 220ms linear 2; } @keyframes fogDrift { from { transform: translate3d(-2%, -1%, 0) scale(1); } to { transform: translate3d(2%, 2%, 0) scale(1.06); } } @keyframes panelShake { 0% { transform: translateX(0); } 25% { transform: translateX(-4px); } 50% { transform: translateX(4px); } 75% { transform: translateX(-2px); } 100% { transform: translateX(0); } } @media (max-width: 1100px) { .hero-panels, .main-grid, .signal-panel, .dossier-body, .signal-columns, .detail-grid { grid-template-columns: 1fr; } .signal-panel { grid-column: auto; } .gallery-hint { max-width: none; text-align: left; } .gallery-collage { min-height: 980px; } } @media (max-width: 720px) { .site-shell { padding: 1rem; } .boot-panel, .info-card, .monitor-card, .selector-panel, .dossier-panel, .gallery-panel, .signal-panel { padding: 1rem; } .hero-title span:last-child, .selector-head h2, .gallery-head h2, .signal-copy h2, .dossier-copy h2 { letter-spacing: 0.04em; } .monitor-header, .gallery-head, .dossier-topline { flex-direction: column; } .portrait-stage { min-height: 420px; } #featureImage { min-height: 360px; } .gallery-collage { min-height: 1180px; } .relic-card { width: min(72vw, 220px); } }