You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
541 lines
18 KiB
541 lines
18 KiB
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>PF2e Perception & Stealth</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
|
<style>
|
|
:root {
|
|
--bg: #161210;
|
|
--surface: #211a13;
|
|
--surface2: #2e2318;
|
|
--border: #7a5c18;
|
|
--text: #e8d5b0;
|
|
--text-muted: #9a7e56;
|
|
--accent: #c89030;
|
|
|
|
--c-observed: #193a14;
|
|
--c-observed-hi: #5ec454;
|
|
--c-observed-border:#3a7c32;
|
|
|
|
--c-hidden: #3e2e06;
|
|
--c-hidden-hi: #f0c040;
|
|
--c-hidden-border: #a07a10;
|
|
|
|
--c-undetected: #3e1a06;
|
|
--c-undetected-hi: #f07830;
|
|
--c-undetected-border:#a04010;
|
|
|
|
--c-unnoticed: #300c0c;
|
|
--c-unnoticed-hi: #f04040;
|
|
--c-unnoticed-border:#8a1010;
|
|
|
|
--c-concealed: #0e2234;
|
|
--c-concealed-hi: #50a8e8;
|
|
--c-concealed-border:#2060a0;
|
|
|
|
--c-invisible: #180e30;
|
|
--c-invisible-hi: #9070e0;
|
|
--c-invisible-border:#5030b0;
|
|
}
|
|
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: Georgia, 'Times New Roman', serif;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* ── Header ── */
|
|
header {
|
|
background: var(--surface);
|
|
border-bottom: 2px solid var(--border);
|
|
padding: 14px 24px;
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 16px;
|
|
flex-shrink: 0;
|
|
}
|
|
header h1 {
|
|
font-size: 1.5rem;
|
|
color: var(--accent);
|
|
letter-spacing: 0.06em;
|
|
}
|
|
header p {
|
|
color: var(--text-muted);
|
|
font-size: 0.85rem;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* ── Body layout ── */
|
|
.layout {
|
|
display: flex;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* ── Sidebar ── */
|
|
aside {
|
|
width: 270px;
|
|
min-width: 270px;
|
|
background: var(--surface);
|
|
border-right: 1px solid var(--border);
|
|
overflow-y: auto;
|
|
padding: 16px 14px;
|
|
}
|
|
aside h2 {
|
|
color: var(--accent);
|
|
font-size: 0.75rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.12em;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.ccard {
|
|
border-radius: 5px;
|
|
padding: 10px 12px;
|
|
margin-bottom: 9px;
|
|
border: 1px solid;
|
|
}
|
|
.ccard h3 {
|
|
font-size: 0.8rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.07em;
|
|
margin-bottom: 5px;
|
|
}
|
|
.ccard p {
|
|
font-size: 0.76rem;
|
|
line-height: 1.55;
|
|
color: var(--text);
|
|
}
|
|
.ccard .tag {
|
|
display: inline-block;
|
|
margin-top: 6px;
|
|
font-size: 0.72rem;
|
|
font-weight: bold;
|
|
padding: 2px 7px;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.ccard.observed { background: var(--c-observed); border-color: var(--c-observed-border); }
|
|
.ccard.observed h3{ color: var(--c-observed-hi); }
|
|
.ccard.observed .tag { background: #0d2a0a; color: var(--c-observed-hi); }
|
|
|
|
.ccard.hidden { background: var(--c-hidden); border-color: var(--c-hidden-border); }
|
|
.ccard.hidden h3 { color: var(--c-hidden-hi); }
|
|
.ccard.hidden .tag{ background: #2a1e04; color: var(--c-hidden-hi); }
|
|
|
|
.ccard.undetected { background: var(--c-undetected); border-color: var(--c-undetected-border); }
|
|
.ccard.undetected h3 { color: var(--c-undetected-hi); }
|
|
.ccard.undetected .tag{ background: #2a1004; color: var(--c-undetected-hi); }
|
|
|
|
.ccard.unnoticed { background: var(--c-unnoticed); border-color: var(--c-unnoticed-border); }
|
|
.ccard.unnoticed h3 { color: var(--c-unnoticed-hi); }
|
|
.ccard.unnoticed .tag{ background: #200808; color: var(--c-unnoticed-hi); }
|
|
|
|
.ccard.concealed { background: var(--c-concealed); border-color: var(--c-concealed-border); }
|
|
.ccard.concealed h3 { color: var(--c-concealed-hi); }
|
|
.ccard.concealed .tag{ background: #081422; color: var(--c-concealed-hi); }
|
|
|
|
.ccard.invisible { background: var(--c-invisible); border-color: var(--c-invisible-border); }
|
|
.ccard.invisible h3 { color: var(--c-invisible-hi); }
|
|
.ccard.invisible .tag{ background: #100820; color: var(--c-invisible-hi); }
|
|
|
|
.cover-box {
|
|
margin-top: 14px;
|
|
padding: 10px 12px;
|
|
background: var(--surface2);
|
|
border: 1px solid var(--border);
|
|
border-radius: 5px;
|
|
font-size: 0.76rem;
|
|
line-height: 1.7;
|
|
}
|
|
.cover-box h3 {
|
|
color: var(--accent);
|
|
font-size: 0.75rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
margin-bottom: 6px;
|
|
}
|
|
.cover-box strong { color: var(--accent); }
|
|
|
|
/* ── Main content ── */
|
|
main {
|
|
flex: 1;
|
|
overflow: auto;
|
|
padding: 20px 24px;
|
|
}
|
|
|
|
/* ── Tabs ── */
|
|
.tab-bar {
|
|
display: flex;
|
|
gap: 6px;
|
|
margin-bottom: 0;
|
|
}
|
|
.tab-btn {
|
|
padding: 8px 22px;
|
|
background: var(--surface2);
|
|
border: 1px solid var(--border);
|
|
border-bottom-color: var(--border);
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: 0.9rem;
|
|
border-radius: 6px 6px 0 0;
|
|
transition: color 0.15s, background 0.15s;
|
|
}
|
|
.tab-btn.active {
|
|
background: var(--surface);
|
|
color: var(--accent);
|
|
border-color: var(--accent);
|
|
border-bottom-color: var(--surface);
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
.tab-btn:hover:not(.active) { color: var(--text); }
|
|
|
|
.tab-panel {
|
|
display: none;
|
|
background: var(--surface);
|
|
border: 1px solid var(--accent);
|
|
border-radius: 0 6px 6px 6px;
|
|
padding: 24px;
|
|
}
|
|
.tab-panel.active { display: block; }
|
|
|
|
.tab-panel h2 {
|
|
color: var(--accent);
|
|
font-size: 1.1rem;
|
|
margin-bottom: 6px;
|
|
}
|
|
.tab-panel .subtitle {
|
|
color: var(--text-muted);
|
|
font-size: 0.82rem;
|
|
font-style: italic;
|
|
margin-bottom: 22px;
|
|
}
|
|
|
|
/* ── Mermaid container ── */
|
|
.diagram-wrap {
|
|
overflow-x: auto;
|
|
padding: 10px 0;
|
|
}
|
|
.diagram-wrap .mermaid {
|
|
display: flex;
|
|
justify-content: center;
|
|
min-width: 600px;
|
|
}
|
|
|
|
/* legend strip */
|
|
.legend {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin-bottom: 18px;
|
|
font-size: 0.77rem;
|
|
}
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
.legend-dot {
|
|
width: 13px;
|
|
height: 13px;
|
|
border-radius: 50%;
|
|
border: 1px solid;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.note-box {
|
|
background: var(--surface2);
|
|
border: 1px solid var(--border);
|
|
border-radius: 5px;
|
|
padding: 12px 16px;
|
|
margin-top: 20px;
|
|
font-size: 0.8rem;
|
|
line-height: 1.7;
|
|
color: var(--text);
|
|
}
|
|
.note-box h3 {
|
|
color: var(--accent);
|
|
font-size: 0.78rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
margin-bottom: 8px;
|
|
}
|
|
.note-box ul {
|
|
padding-left: 16px;
|
|
}
|
|
.note-box li { margin-bottom: 4px; }
|
|
.note-box strong { color: var(--accent); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<h1>PF2e Perception & Stealth</h1>
|
|
<p>Flow of Control Reference — Player Core</p>
|
|
</header>
|
|
|
|
<div class="layout">
|
|
|
|
<!-- ── Sidebar ── -->
|
|
<aside>
|
|
<h2>Detection Conditions</h2>
|
|
|
|
<div class="ccard observed">
|
|
<h3>Observed</h3>
|
|
<p>You perceive the creature clearly with a precise sense. You can target it normally.</p>
|
|
<span class="tag">No flat check to target</span>
|
|
</div>
|
|
|
|
<div class="ccard concealed">
|
|
<h3>Concealed</h3>
|
|
<p>In mist, dim light, or similar. Still <em>observed</em>, but harder to affect. Does not change the main detection category.</p>
|
|
<span class="tag">DC 5 flat check to affect</span>
|
|
</div>
|
|
|
|
<div class="ccard hidden">
|
|
<h3>Hidden</h3>
|
|
<p>You know the creature’s space but can’t see it. You are off-guard to it. The creature cannot take non-unobtrusive actions without being observed.</p>
|
|
<span class="tag">DC 11 flat check to affect</span>
|
|
</div>
|
|
|
|
<div class="ccard undetected">
|
|
<h3>Undetected</h3>
|
|
<p>You don’t know which space it’s in. You are off-guard. You may know it is nearby (undetected) or have no clue at all (unnoticed).</p>
|
|
<span class="tag">Pick a square — GM rolls secretly</span>
|
|
</div>
|
|
|
|
<div class="ccard unnoticed">
|
|
<h3>Unnoticed</h3>
|
|
<p>You have no idea the creature even exists nearby. Certain abilities only work on totally unaware targets.</p>
|
|
<span class="tag">Cannot target at all</span>
|
|
</div>
|
|
|
|
<div class="ccard invisible">
|
|
<h3>Invisible</h3>
|
|
<p>Automatically <em>undetected</em> against creatures relying on sight alone. Precise senses other than vision still work. Seek can reveal location, making it hidden.</p>
|
|
<span class="tag">Undetected to sight-only perceivers</span>
|
|
</div>
|
|
|
|
<div class="cover-box">
|
|
<h3>Cover Bonuses (Stealth)</h3>
|
|
<strong>Standard Cover:</strong> +2 to Stealth<br>
|
|
<strong>Greater Cover:</strong> +4 to Stealth
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- ── Main ── -->
|
|
<main>
|
|
<div class="tab-bar">
|
|
<button class="tab-btn active" onclick="showTab('stealth', this)">Stealth Actions</button>
|
|
<button class="tab-btn" onclick="showTab('detection', this)">Detection & Targeting</button>
|
|
</div>
|
|
|
|
<!-- Stealth Tab -->
|
|
<div id="stealth" class="tab-panel active">
|
|
<h2>Stealth Actions Flow</h2>
|
|
<p class="subtitle">From the <strong>stealther’s</strong> perspective — how detection states change based on actions you take</p>
|
|
|
|
<div class="legend">
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--c-observed);border-color:var(--c-observed-hi)"></div> Observed</div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--c-hidden);border-color:var(--c-hidden-hi)"></div> Hidden</div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--c-undetected);border-color:var(--c-undetected-hi)"></div> Undetected</div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--c-unnoticed);border-color:var(--c-unnoticed-hi)"></div> Unnoticed</div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:#1a2840;border-color:#4080b0"></div> Action / Roll</div>
|
|
</div>
|
|
|
|
<div class="diagram-wrap">
|
|
<div class="mermaid" id="stealth-diagram">
|
|
%%{init: {'theme':'dark','themeVariables':{'background':'#161210','primaryColor':'#193a14','primaryTextColor':'#e8d5b0','primaryBorderColor':'#3a7c32','lineColor':'#9a7e56','secondaryColor':'#3e2e06','tertiaryColor':'#1a2840','edgeLabelBackground':'#211a13','clusterBkg':'#211a13'}}}%%
|
|
flowchart LR
|
|
|
|
classDef obs fill:#193a14,stroke:#3a7c32,color:#b8e8b0,rx:30
|
|
classDef hid fill:#3e2e06,stroke:#a07a10,color:#f0e060
|
|
classDef und fill:#3e1a06,stroke:#a04010,color:#f0a868
|
|
classDef unn fill:#300c0c,stroke:#8a1010,color:#f07070
|
|
classDef act fill:#1a2840,stroke:#4080b0,color:#90c0e8
|
|
classDef roll fill:#201828,stroke:#6040a0,color:#c0a0e8
|
|
|
|
OBS(["OBSERVED"]):::obs
|
|
HID(["HIDDEN"]):::hid
|
|
UND(["UNDETECTED"]):::und
|
|
UNN(["UNNOTICED"]):::unn
|
|
|
|
HIDE["HIDE
|
|
— needs cover or concealment —
|
|
secret Stealth roll vs Perception DC
|
|
Standard cover +2 · Greater cover +4"]:::act
|
|
|
|
SNEAK["SNEAK
|
|
— cover at START and END —
|
|
move ≤ half Speed
|
|
secret Stealth roll vs Perception DC"]:::act
|
|
|
|
OBS --> HIDE
|
|
HIDE -->|"✓ Success"| HID
|
|
HIDE -->|"✗ Failure"| OBS
|
|
|
|
HID --> SNEAK
|
|
HID -->|"Non-stealthy action
|
|
or Strike"| OBS
|
|
HID -->|"STEP
|
|
with cover / concealment"| HID
|
|
HID -->|"Unobtrusive action
|
|
GM may require Stealth"| HID
|
|
|
|
SNEAK -->|"✓ Success"| UND
|
|
SNEAK -->|"✗ Failure"| HID
|
|
SNEAK -->|"✗✗ Crit Failure
|
|
(not invisible)"| OBS
|
|
|
|
UND -->|"SNEAK ✓ Success"| UND
|
|
UND -->|"Speak or
|
|
make loud noise"| OBS
|
|
|
|
UNN -.->|"may also be"| UND
|
|
UNN -->|"Speak, loud noise
|
|
or non-stealthy action"| OBS
|
|
</div>
|
|
</div>
|
|
|
|
<div class="note-box">
|
|
<h3>Key Rules</h3>
|
|
<ul>
|
|
<li><strong>HIDE</strong> only rolls against creatures that are currently <em>observing</em> you. If a creature already considers you hidden or undetected, you simply retain that condition — HIDE does not improve your state from undetected.</li>
|
|
<li><strong>SNEAK</strong> requires cover or concealment at <em>both the start and end</em> of your movement. You move up to half your Speed.</li>
|
|
<li>Taking any non-<em>unobtrusive</em> action while hidden immediately makes you <strong>Observed</strong>.</li>
|
|
<li>A <strong>Strike</strong> from a hidden or undetected position makes you <strong>Observed</strong> after the attack (target now knows your location).</li>
|
|
<li>An <strong>Invisible</strong> creature that critically fails a Sneak roll becomes <strong>Hidden</strong> rather than Observed, since it cannot be seen.</li>
|
|
<li><strong>AVOID NOTICE</strong> (Exploration): Roll Stealth to start an encounter Hidden or Undetected rather than Observed.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Detection Tab -->
|
|
<div id="detection" class="tab-panel">
|
|
<h2>Detection & Targeting Flow</h2>
|
|
<p class="subtitle">From the <strong>detector’s</strong> perspective — how to determine a creature’s condition and what you can do about it</p>
|
|
|
|
<div class="legend">
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--c-observed);border-color:var(--c-observed-hi)"></div> Observed</div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--c-hidden);border-color:var(--c-hidden-hi)"></div> Hidden</div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--c-undetected);border-color:var(--c-undetected-hi)"></div> Undetected</div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:var(--c-unnoticed);border-color:var(--c-unnoticed-hi)"></div> Unnoticed</div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:#1a2840;border-color:#4080b0"></div> Action / Roll</div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:#201828;border-color:#6040a0"></div> Decision</div>
|
|
</div>
|
|
|
|
<div class="diagram-wrap">
|
|
<div class="mermaid" id="detection-diagram">
|
|
%%{init: {'theme':'dark','themeVariables':{'background':'#161210','primaryColor':'#193a14','primaryTextColor':'#e8d5b0','primaryBorderColor':'#8b6914','lineColor':'#a08060','secondaryColor':'#3e2e06','tertiaryColor':'#1a2840','edgeLabelBackground':'#211a13','clusterBkg':'#211a13'}}}%%
|
|
flowchart TD
|
|
|
|
classDef obs fill:#193a14,stroke:#3a7c32,color:#b8e8b0
|
|
classDef hid fill:#3e2e06,stroke:#a07a10,color:#f0e060
|
|
classDef und fill:#3e1a06,stroke:#a04010,color:#f0a868
|
|
classDef unn fill:#300c0c,stroke:#8a1010,color:#f07070
|
|
classDef act fill:#1a2840,stroke:#4080b0,color:#90c0e8
|
|
classDef dec fill:#2a2010,stroke:#806040,color:#d0c0a0
|
|
|
|
START(["What can you perceive?"]):::dec
|
|
Q1["Can your precise sense detect it?"]:::dec
|
|
Q2["Do you know it is nearby?"]:::dec
|
|
Q3["Do you know which space?"]:::dec
|
|
|
|
OBS(["OBSERVED
|
|
Target normally
|
|
If concealed: DC 5 flat check"]):::obs
|
|
|
|
HID(["HIDDEN
|
|
You know the space
|
|
You are off-guard to it
|
|
DC 11 flat check to target"]):::hid
|
|
|
|
UND(["UNDETECTED
|
|
You do not know the space
|
|
You are off-guard to it
|
|
Guess a square - GM rolls secretly"]):::und
|
|
|
|
UNN(["UNNOTICED
|
|
No idea it exists
|
|
Cannot target at all"]):::unn
|
|
|
|
SEEK["SEEK
|
|
Perception vs Stealth DC
|
|
rolled secretly by GM"]:::act
|
|
|
|
START --> Q1
|
|
Q1 -->|YES| OBS
|
|
Q1 -->|NO| Q2
|
|
Q2 -->|NO| UNN
|
|
Q2 -->|YES| Q3
|
|
Q3 -->|YES| HID
|
|
Q3 -->|NO| UND
|
|
|
|
HID -->|Use SEEK| SEEK
|
|
UND -->|Use SEEK| SEEK
|
|
|
|
SEEK -->|Critical Success| OBS
|
|
SEEK -->|Success precise sense| OBS
|
|
SEEK -->|Success imprecise or invisible| HID
|
|
SEEK -->|Failure| HID
|
|
</div>
|
|
</div>
|
|
|
|
<div class="note-box">
|
|
<h3>Targeting Summary</h3>
|
|
<ul>
|
|
<li><strong>Observed:</strong> Attack normally. If also <em>concealed</em>, attempt a DC 5 flat check before rolling — fail means no effect.</li>
|
|
<li><strong>Hidden:</strong> Attempt a DC 11 flat check before rolling. On a fail, your actions (and spell slots, resources) are wasted. You remain off-guard to the creature regardless.</li>
|
|
<li><strong>Undetected:</strong> Declare a square to attack. The GM secretly rolls both the flat check and your attack roll. The GM will not say whether you missed due to the flat check, the attack roll, or choosing the wrong square.</li>
|
|
<li><strong>Unnoticed:</strong> You cannot target the creature directly. Area effects still work normally against undetected creatures.</li>
|
|
<li><strong>Invisible creature</strong> starts as <em>Hidden</em> if you were already observing it when it turned invisible (you last know where it was). It can then Sneak to become Undetected.</li>
|
|
<li><strong>Off-guard:</strong> When a creature is hidden or undetected from you, <em>you</em> are off-guard to <em>it</em> — you take a −2 circumstance penalty to your AC against its attacks. The hidden/undetected creature is not automatically off-guard to you.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
mermaid.initialize({
|
|
startOnLoad: false,
|
|
theme: 'dark',
|
|
flowchart: { curve: 'basis', padding: 20, useMaxWidth: false },
|
|
securityLevel: 'loose',
|
|
});
|
|
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
|
const diagrams = document.querySelectorAll('.mermaid');
|
|
for (let i = 0; i < diagrams.length; i++) {
|
|
const el = diagrams[i];
|
|
const id = el.id || ('mermaid-' + i);
|
|
try {
|
|
const { svg } = await mermaid.render(id + '-svg', el.textContent.trim());
|
|
el.innerHTML = svg;
|
|
} catch (e) {
|
|
console.error('Mermaid error in #' + id + ':', e);
|
|
el.innerHTML = '<pre style="color:#f07070;font-size:0.75rem;white-space:pre-wrap;padding:12px">' +
|
|
'Diagram error in #' + id + ':\n' + (e.message || e) + '</pre>';
|
|
}
|
|
}
|
|
});
|
|
|
|
function showTab(name, btn) {
|
|
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
document.getElementById(name).classList.add('active');
|
|
btn.classList.add('active');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|