Compare commits
20 Commits
feature/ta
...
awq
| Author | SHA1 | Date | |
|---|---|---|---|
| c4bbe95b08 | |||
| b74dfe3186 | |||
| ce885d8eae | |||
| 0bca3573f7 | |||
| b93f846156 | |||
| 5cb4bd5782 | |||
| 3a79f59f03 | |||
| 70e226057a | |||
| 8f78c26b78 | |||
| e4b72005f2 | |||
| 2e30b1d6ed | |||
| ae6da529cf | |||
| fdcfc774fb | |||
| 054d450273 | |||
| 0111cd71fe | |||
| 9f10721104 | |||
| bfbe687b63 | |||
| 1773687814 | |||
| 6e2ab0a88b | |||
| d80c4917de |
88
content/awq/articles/checks.mdoc
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Checks
|
||||
summary: How we roll dice and why
|
||||
path: rules/general
|
||||
cover:
|
||||
showInHeader: false
|
||||
meta:
|
||||
publicationDate: 2025-10-19T22:16:00.000Z
|
||||
status: draft
|
||||
isFeatured: false
|
||||
tags:
|
||||
- crunch
|
||||
- how-and-what
|
||||
author: dave-damage
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
- Determine the outcome of an action
|
||||
- Use a **Obstacle (Ob.)** to determine **Outcome**
|
||||
- **AWQ** exclusively uses **D6** for checks
|
||||
|
||||
## Outcomes{% Sidenote #outcome-fallbacks marker="§" content="Not all actions ship with the complete set of outcomes, so if a specific outcome is missing, use the following fallbacks: **Boons** become **Failures**, **Complications** become **Successes**, and a missing **Boon** is considered a **Success**" type="crunch" /%}
|
||||
|
||||
- **Bane(⊗):** Action fails spectacular
|
||||
- **Failure(⊖):** Action simply fails
|
||||
- **Complications(⊜):** Action succeeds but with a *Twist*
|
||||
- **Success(⊕):** Actions succeeds
|
||||
- **Boon(⊛):** Actions succeeds with additional benefits
|
||||
|
||||
## Check Modifiers
|
||||
|
||||
### Favour & Peril
|
||||
|
||||
- **Favour (⥣):** Increase chance of success
|
||||
- **Peril (⥥):** Increase chance of failure
|
||||
- **Resolution:** Multiple instances cancel each other
|
||||
- `Peril > Favour`: Roll is *perilous*
|
||||
- `Peril = Favour`: Normal roll
|
||||
- `Favour > Peril`: Roll is *favourable*
|
||||
|
||||
### Re-Rolls(⧆)
|
||||
|
||||
- Re-rolled result is always final result
|
||||
- One attempt per check (Exception: *Dark Deals*)
|
||||
- **Sources:**
|
||||
- *Luck Points:* Spend 1 **LP**
|
||||
- *Dark Deals:* Suffer 1 **Corruption**
|
||||
- *Pushing:* Suffer 1 **Fatigue** or **Stress**
|
||||
|
||||
#### Pushing
|
||||
|
||||
- If initial roll was a *Bane* and succeeds after the *pushing*, the outcome becomes a *Complication*
|
||||
- Cost of a *Push,* depends on the check's **Characteristic**{% Sidenote #push-cost marker="" content="**Fatigue:** **WS**, **BS**, **S**, **T**, **Ag**<br/>\n**Stress:** **I**, **Dex**, **Int**, **WP**, **Fel**" type="crunch" /%}
|
||||
|
||||
## Anatomy
|
||||
|
||||
### Reference Value (RV)
|
||||
Denotes the **Skill** or **Characteristic** used for calculating the **Obtacle**
|
||||
|
||||
### Check Type
|
||||
Denotes the roll-type
|
||||
- **Basic**
|
||||
- **Complex**
|
||||
- **Extended**
|
||||
|
||||
### Check Mode
|
||||
- **Unopposed[⥤]:** RV based on _Self_
|
||||
- **Opposed[]** RV based on _Target_
|
||||
|
||||
### Difficulty
|
||||
Always modifies _RV,_ never _Ob._
|
||||
- Very Easy [○]
|
||||
- Easy [◔ ]
|
||||
- Routine [◑]
|
||||
- Challenging [●]
|
||||
- Difficult [∶]
|
||||
- Hard [∵]
|
||||
- Very Hard [∷]
|
||||
- Impossible [∺]
|
||||
|
||||
### Notation Format
|
||||
`[Check Type] [Check Difficulty] [Check Mode]`
|
||||
|
||||
**Examples:**
|
||||
- `Basic[∶] Cool` (unopposed marker usually omitted)
|
||||
- `Extended[∶] Charm Perception`
|
||||
|
||||
|
||||
17
content/awq/articles/general.mdoc
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: General Crunch
|
||||
path: rules
|
||||
cover:
|
||||
showInHeader: false
|
||||
meta:
|
||||
publicationDate: 2025-10-19T22:14:00.000Z
|
||||
status: draft
|
||||
isFeatured: false
|
||||
tags:
|
||||
- how-and-what
|
||||
- crunch
|
||||
author: dave-damage
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
Rules that apply in EVERY situation, maybe add some test you lazy bastard
|
||||
823
content/awq/articles/rules-summary.md
Normal file
@@ -0,0 +1,823 @@
|
||||
## Core Concepts
|
||||
|
||||
### Roles
|
||||
|
||||
**Players**
|
||||
Every natural person playing the game.
|
||||
|
||||
**Adventurers**
|
||||
|
||||
- Players who undergo Expeditions
|
||||
- Have access to Momentum [✠]
|
||||
- Optional roles: Cartographer, Quartermaster, Archivist, Shotcaller, Marshall, Torchbearer
|
||||
|
||||
**Dungeon Master/Mistress (DM)**
|
||||
|
||||
- Controls Adversaries and Hazards
|
||||
- Has access to Threat [✦]
|
||||
|
||||
**Models**
|
||||
Any entity on the Game Board controlled by a Player, represented by a miniature.
|
||||
|
||||
### Model Types
|
||||
|
||||
**Party Models** (controlled by Adventurers)
|
||||
|
||||
- Characters: One per Adventurer; access to most Activities; evolve via Advances
|
||||
- Followers: Follow a single Character; use Obedience to track loyalty
|
||||
- Henchmen: Specialized hired help; use Morale to track loyalty
|
||||
|
||||
**Adversaries** (controlled by DM)
|
||||
|
||||
- Minion [⁎]: Basic Activities
|
||||
- Elite [⁑]: Situational Activities
|
||||
- Champion [⁂]: Wide array of Activities; can use Threat directly; can become Nemesis
|
||||
- Nemesis [⋇]: Returning adversary; evolves like Characters
|
||||
|
||||
**Non-Player Characters (NPCs)**
|
||||
|
||||
- Spawned by Events, Hazards, or Scenarios
|
||||
- Represent neutral parties or quest objectives
|
||||
|
||||
### Resources
|
||||
|
||||
**Character Resources**
|
||||
|
||||
- Luck Points: Re-roll checks
|
||||
- Fate Points: Escape certain death
|
||||
|
||||
**Party Resources**
|
||||
|
||||
- Momentum [✠]: Used during Encounters; fuels Manoeuvres and tactical options; fluctuates throughout combat
|
||||
|
||||
**DM Resources**
|
||||
|
||||
- Threat [✦]: Used during Expeditions and Encounters; powers Adversary abilities and escalates Encounters
|
||||
|
||||
**Momentum Uses**
|
||||
|
||||
- Varies: Perform an Action
|
||||
- 1pt: Decrease Check Difficulty by 2
|
||||
- 1pt: Automatically perform Change Place Manoeuvre
|
||||
- 2pt: Automatically perform Disengage Manoeuvre
|
||||
- 2pt: Activate Weapon Quality requiring Critical Hit
|
||||
- 2pt: Gain 1 additional Reaction until start of next Model's activation
|
||||
- 4pt: Increase Attacks by 1 for next Action
|
||||
|
||||
### Time Structure
|
||||
|
||||
Nested scales from largest to smallest:
|
||||
|
||||
**Season**
|
||||
|
||||
- Consumed by Endeavours and Expeditions
|
||||
- Represents weeks of activity
|
||||
|
||||
**Event**
|
||||
|
||||
- Immediately resolved incident
|
||||
|
||||
**Endeavour**
|
||||
|
||||
- Downtime activity characters undertake
|
||||
|
||||
**Expedition**
|
||||
|
||||
- Single delve into a Dungeon
|
||||
- Contains one or more Encounters
|
||||
|
||||
**Encounter**
|
||||
|
||||
- Isolated interaction with Adversaries or Hazards
|
||||
- Consists of one or more Rounds
|
||||
|
||||
**Round**
|
||||
|
||||
- Each Model acts once
|
||||
- Consists of structured phases
|
||||
|
||||
**Activity**
|
||||
|
||||
- Single action taken by Model
|
||||
- Interaction with location, Hazard, or other Models
|
||||
|
||||
### Locations
|
||||
|
||||
**Dungeon Structure**
|
||||
|
||||
- Dungeon: Confined locality where Expeditions take place; can have multiple Dungeon Levels
|
||||
- Dungeon Level: Single floor/area; represented by Game Board; danger increases with depth
|
||||
- Board Section: Physical tiles representing rooms and corridors
|
||||
|
||||
**Overland Locations**
|
||||
|
||||
- Region: Large area containing multiple location types
|
||||
- Settlement: Safe haven for trading and Endeavours
|
||||
- Wilderness: Dangerous areas connecting Settlements and Dungeons; contains Encounters and Hazards
|
||||
|
||||
**Hazards**
|
||||
Catch-all term for interactive environmental dangers and obstacles.
|
||||
|
||||
**Keywords**
|
||||
Used to signify synergies and antipodes; multiple occurrences usually beneficial for cost or outcome.
|
||||
|
||||
---
|
||||
|
||||
## Checks System
|
||||
|
||||
### Overview
|
||||
|
||||
- Always use D6
|
||||
- Determines outcome of activities
|
||||
- Rolled against Obstacle (Ob.)
|
||||
- Three check types: Basic, Complex, Extended
|
||||
|
||||
### Check Components
|
||||
|
||||
**Reference Value**
|
||||
Denotes the Skill or Characteristic used for calculating Target Number.
|
||||
|
||||
**Check Mode**
|
||||
|
||||
- Unopposed [⥤]: Reference Value based on Model's Skill
|
||||
- Opposed [⇌]: Reference Value based on Target's skill
|
||||
|
||||
**Difficulty**
|
||||
Always modifies Reference Value, never Obstacle:
|
||||
|
||||
- Very Easy [○]
|
||||
- Easy [◔]
|
||||
- Routine [◑]
|
||||
- Challenging [●]
|
||||
- Difficult [∶]
|
||||
- Hard [∵]
|
||||
- Very Hard [∷]
|
||||
- Impossible [∺]
|
||||
|
||||
**Notation Format**
|
||||
`[Check Type] [Check Difficulty] [Check Mode]`
|
||||
|
||||
Examples:
|
||||
|
||||
- `Basic[∶] Cool` (unopposed marker usually omitted)
|
||||
- `Extended[∷] Charm⇌Perception`
|
||||
|
||||
### Check Outcomes
|
||||
|
||||
**Bane [⊗]**
|
||||
Check fails with dire consequences. If not specified, treat as Failure.
|
||||
|
||||
**Failure [⊖]**
|
||||
Check simply fails.
|
||||
|
||||
**Complication [⊜]**
|
||||
Check succeeds but with consequences. If not specified, treat as Success.
|
||||
|
||||
**Success [⊕]**
|
||||
Check succeeds as intended.
|
||||
|
||||
**Boon [⊛]**
|
||||
Check succeeds with additional benefits. If not specified, treat as Success.
|
||||
|
||||
### Favour & Peril
|
||||
|
||||
**Favour [⥣]**
|
||||
Increases chances of success.
|
||||
|
||||
**Peril [⥥]**
|
||||
Reduces chances of success.
|
||||
|
||||
**Resolution**
|
||||
Multiple instances cancel each other:
|
||||
|
||||
- Peril > Favour: Roll is perilous
|
||||
- Peril = Favour: Normal roll
|
||||
- Favour > Peril: Roll is favourable
|
||||
|
||||
### Re-Rolls
|
||||
|
||||
**Limitation**
|
||||
One attempt per check (except Dark Deal).
|
||||
Re-rolled result is always final.
|
||||
|
||||
**Sources**
|
||||
|
||||
- Luck Points: Spend 1 LP
|
||||
- Dark Deal: Suffer 1 Corruption
|
||||
- Push: Suffer 1 Stress or Fatigue
|
||||
|
||||
### Basic Checks
|
||||
|
||||
**Procedure**
|
||||
Single D6 roll against Obstacle.
|
||||
|
||||
**Outcomes**
|
||||
|
||||
- Bane: Roll = 1
|
||||
- Failure: Roll < Obstacle
|
||||
- Complication: Roll = Obstacle
|
||||
- Success: Roll > Obstacle
|
||||
- Boon: Roll = 6
|
||||
|
||||
**Note**
|
||||
For Obstacle ≥6, Boon requires confirmation by rolling success again.
|
||||
|
||||
### Complex Checks
|
||||
|
||||
**Procedure**
|
||||
Roll multiple D6 (number equals Reference Value).
|
||||
Each die ≥ Target generates one Mark.
|
||||
|
||||
**Evaluation**
|
||||
|
||||
- Rolling 1: -1 Mark
|
||||
- Rolling 6: Roll again for bonus Mark
|
||||
|
||||
**Resolution**
|
||||
Compare total Marks to Obstacle for Outcome.
|
||||
|
||||
**Obstacle Determination**
|
||||
|
||||
- Unopposed: Fixed number (1-10)
|
||||
- Opposed: Target's Reference Value
|
||||
|
||||
### Extended Checks
|
||||
|
||||
**Procedure**
|
||||
Use Complex Check rules.
|
||||
Multiple rolls allowed until Obstacle reached.
|
||||
|
||||
**Time Cost**
|
||||
Each roll consumes 1 unit of Time.
|
||||
|
||||
**Failure Condition**
|
||||
If Marks reach 0 or below, Check ends as Bane.
|
||||
|
||||
---
|
||||
|
||||
## Abilities
|
||||
|
||||
### Main Characteristics
|
||||
|
||||
Innate abilities of a Model. Each has 2 associated Skills.
|
||||
|
||||
**Weapon Skill (WS)**
|
||||
Close combat aptitude. Governs Melee and Defense.
|
||||
|
||||
**Ballistic Skill (BS)**
|
||||
Hand-eye coordination. Governs Shooting and Throwing.
|
||||
|
||||
**Strength (S)**
|
||||
Brute force and stamina. Governs Brawn and Toil.
|
||||
|
||||
**Toughness (T)**
|
||||
Physical resistance. Governs Consume Alcohol and Endurance.
|
||||
|
||||
**Initiative (I)**
|
||||
Speed of thought and perception. Governs Perception and Dodge.
|
||||
|
||||
**Dexterity (Dex)**
|
||||
Fine manual tasks. Governs Streetwise and Crafting.
|
||||
|
||||
**Agility (Ag)**
|
||||
Physical coordination and athleticism. Governs Stealth and Athletics.
|
||||
|
||||
**Intelligence (Int)**
|
||||
Analytical thinking. Governs Intuition and Education.
|
||||
|
||||
**Willpower (WP)**
|
||||
Mental strength. Governs Cool and Animal Handling.
|
||||
|
||||
**Fellowship (Fel)**
|
||||
Social aptitude. Governs Leadership and Charm.
|
||||
|
||||
### Secondary Characteristics
|
||||
|
||||
**Wounds (W)**
|
||||
Number of Wounds before knocked out.
|
||||
|
||||
**Movement (M)**
|
||||
Movement distance under normal conditions.
|
||||
|
||||
**Attacks (A)**
|
||||
Number of attacks per round.
|
||||
|
||||
**Magic (Mag)**
|
||||
Wizard level; determines Magic Pool for spellcasting.
|
||||
|
||||
**Corruption Threshold (CT)**
|
||||
Ability to resist Corruption.
|
||||
|
||||
**Insanity Threshold (IT)**
|
||||
Ability to resist Insanity.
|
||||
|
||||
**Fate Points (FP)**
|
||||
Used to avoid death and dark fates.
|
||||
|
||||
**Luck Points (LP)**
|
||||
Used for re-rolls.
|
||||
|
||||
### Skills
|
||||
|
||||
Skills are used in Checks. Each associated with a Main Characteristic.
|
||||
|
||||
**Melee (WS)**
|
||||
Make Melee Attack actions.
|
||||
|
||||
**Defense (WS)**
|
||||
Defend against Melee Attack actions.
|
||||
|
||||
**Shooting (BS)**
|
||||
Make Ranged Attack actions with Missile weapons.
|
||||
|
||||
**Throwing (BS)**
|
||||
Make Ranged Attack actions with Throwing weapons.
|
||||
|
||||
**Brawn (S)**
|
||||
Immediate feats of strength.
|
||||
|
||||
**Toil (S)**
|
||||
Prolonged manual labor.
|
||||
|
||||
**Consume Alcohol (T)**
|
||||
Resist short-term hazards like alcohol and poison.
|
||||
|
||||
**Endurance (T)**
|
||||
Endure hardship, withstand deprivation, survive harsh environments.
|
||||
|
||||
**Perception (I)**
|
||||
Notice things.
|
||||
|
||||
**Dodge (I)**
|
||||
Evade attacks and immediate hazards like traps.
|
||||
|
||||
**Stealth (Ag)**
|
||||
Move quietly and conceal.
|
||||
|
||||
**Athletics (Ag)**
|
||||
Running, jumping, climbing, swimming.
|
||||
|
||||
**Streetwise (Dex)**
|
||||
Lockpicking, pickpocketing, disarming traps, sleight of hand.
|
||||
|
||||
**Crafting (Dex)**
|
||||
Create trappings.
|
||||
|
||||
**Intuition (Int)**
|
||||
Detect subterfuge and determine value of objects.
|
||||
|
||||
**Education (Int)**
|
||||
Recall relevant information.
|
||||
|
||||
**Cool (WP)**
|
||||
Remain calm under stress, resist fear and psychological coercion.
|
||||
|
||||
**Animal Handling (WP)**
|
||||
Charm, train, and care for animals.
|
||||
|
||||
**Leadership (Fel)**
|
||||
Intimidation, command, coerce obedience. Often resisted by Discipline.
|
||||
|
||||
**Charm (Fel)**
|
||||
Deceiving, blathering, haggling, gossiping. Usually resisted by Intuition.
|
||||
|
||||
### Traits
|
||||
|
||||
Inherent abilities based on Species.
|
||||
Examples: Flier, Dark Vision, Mutation.
|
||||
|
||||
### Lores
|
||||
|
||||
Define what a character knows and specialist skills.
|
||||
|
||||
**Categories**
|
||||
|
||||
- Academic: Various fields like Accountancy, Anatomy, History, Law
|
||||
- Cultural: Knowledge of social groups and languages
|
||||
- Enemy: Knowledge of adversaries and combat tactics against them
|
||||
- Environment: Surviving in hazardous environments
|
||||
- Magic Lores: Knowledge of magic forms; enables spell casting
|
||||
- Specialist Weapon Groups: Training with special weapons (Polearms, Two-Handed Swords, Blackpowder Guns)
|
||||
- Trade Lores: Knowledge to create trappings or work in fields (Blacksmith, Weaver, Engineer, Artist)
|
||||
- Vehicle Lores: Operating vehicles and mounts
|
||||
|
||||
### Talents
|
||||
|
||||
Represent knacks, tricks, and innate abilities.
|
||||
Examples: Ambidextrous, Aetheric Attunement, Menacing, Acute Senses.
|
||||
|
||||
### Manoeuvres
|
||||
|
||||
Special combat actions and tactics.
|
||||
Examples: Formation Fighting, Disarm, Shield Bash.
|
||||
|
||||
### Careers
|
||||
|
||||
Building blocks of a character providing Skills, Lores, Talents, Manoeuvres, etc.
|
||||
Only applicable for Player Characters.
|
||||
|
||||
---
|
||||
|
||||
## Encounters & Combat
|
||||
|
||||
### Overview
|
||||
|
||||
Encounter is a conflict between Adventurers and Adversaries.
|
||||
Triggered by Event or Location.
|
||||
|
||||
### Turn Sequence
|
||||
|
||||
**1. Encounter Setup (First Round Only)**
|
||||
|
||||
_I. Determine Encounter Parameters_
|
||||
|
||||
- Encounters may have specific Effects
|
||||
- DM spends Threat to modify duration/severity or buy additional Effects
|
||||
|
||||
_II. Determine Surprise_
|
||||
|
||||
Company Surprised:
|
||||
|
||||
- No Momentum in first round
|
||||
- Henchmen and Followers receive no Activation
|
||||
- Declare all actions before Adversaries
|
||||
|
||||
Adversaries Surprised:
|
||||
|
||||
- No Adversary Pool in first round
|
||||
- Common Adversaries lose Activation
|
||||
- Elite and Champion Adversaries use Basic Actions only
|
||||
- Declare all actions after Company
|
||||
|
||||
_III. Calculate Initial Momentum_
|
||||
Starting Momentum equals Company's current Fortune Pool.
|
||||
|
||||
_IV. Prepare Adversaries_
|
||||
|
||||
- DM selects Adversary Group and Models
|
||||
- Spend Threat for Talents, better models, or additional Pool values
|
||||
- Prepare Adversary Pool by summing A/C/E values of each Model
|
||||
- Position Adversary Models on board
|
||||
|
||||
**2. Determine Winds of Magic**
|
||||
See Magic Systems section.
|
||||
|
||||
**3. Declare Actions**
|
||||
|
||||
- Roll D6: Even = Explorers declare first, Odd = DM declares first
|
||||
- Alternate declaring actions
|
||||
- DM declares Adversary actions in Groups
|
||||
|
||||
**4. Determine Initiative**
|
||||
Roll 1D6 + Initiative Characteristic - Action modifier
|
||||
Resolve in descending order (highest first).
|
||||
|
||||
**5. Resolve Actions**
|
||||
Each Model receives Activation in Initiative order.
|
||||
|
||||
**6. Bookkeeping**
|
||||
|
||||
_Update Momentum_
|
||||
|
||||
1. Torchbearer modifies Pool:
|
||||
- +1 per Elite Adversary defeated
|
||||
- +2 per Champion Adversary defeated
|
||||
- +5 Nemesis Adversary defeated
|
||||
- +2 if Company outnumbers Adversaries
|
||||
- Varies for Action/Talent effects
|
||||
2. Subtract Action costs from Pool
|
||||
3. Result is Momentum Pool for next turn
|
||||
4. If below 0: All Company Models gain Banes equal to deficit; set Pool to 0
|
||||
|
||||
_Refresh Adversary Pool_
|
||||
Sum A/C/E values of remaining non-routed Adversaries.
|
||||
|
||||
_Apply Global Effects_
|
||||
Resolve effects discreetly.
|
||||
Reduce Duration trackers by 1.
|
||||
Remove effects with Duration 0 after applying.
|
||||
|
||||
_Check Morale_
|
||||
See Psychology & Leadership rules.
|
||||
|
||||
_End Condition_
|
||||
Combat ends when no Adversaries remain on Board Section.
|
||||
|
||||
### Activation
|
||||
|
||||
**Structure**
|
||||
Each Model receives one discrete Activation per Round.
|
||||
Activation must finish before next begins.
|
||||
|
||||
**Exception**
|
||||
Interrupts can occur during another Activation.
|
||||
|
||||
**Activation Sequence**
|
||||
|
||||
_1. Start Phase_
|
||||
Apply condition effects.
|
||||
|
||||
_2. Action Phase_
|
||||
|
||||
- Perform one Major Action OR gain 2 free Minor Actions
|
||||
- Perform one free Minor Action (before or after Major Action)
|
||||
- Main Characters: Additional Minor Actions cost 1 Fatigue each
|
||||
|
||||
_3. End Phase_
|
||||
|
||||
- Reduce condition tracker by 1 (remove if zero)
|
||||
- Gain Interrupts equal to Attacks Characteristic
|
||||
|
||||
### Actions
|
||||
|
||||
**Minor Actions [◇]**
|
||||
Standard activities like Movement.
|
||||
|
||||
**Major Actions [⬡]**
|
||||
Complex combat actions like Attacks and Spell Casting.
|
||||
|
||||
**Reactions [▽]**
|
||||
Reactive activities that break normal turn order.
|
||||
|
||||
### Minor Actions
|
||||
|
||||
**Assist**
|
||||
Aid another Model.
|
||||
|
||||
**Interact**
|
||||
Open doors, grab items, use hazards/trappings.
|
||||
|
||||
**Movement**
|
||||
|
||||
- Advance: Move up to Movement (M)
|
||||
- Drag: Move adjacent prone Model up to ½M
|
||||
- Disengage: Challenging Opposed Athletics vs WS to leave enemy Danger Zone
|
||||
- Change Place: Challenging Opposed Athletics vs WS to swap with adjacent engaged ally
|
||||
- Reposition: Move to adjacent square within enemy Danger Zone
|
||||
|
||||
**Manage Equipment**
|
||||
Switch weapon, sling/unsling shield, drink potion, draw from container.
|
||||
|
||||
**Recuperate**
|
||||
|
||||
- Recover Stress: Challenging Cool check to recover 1 Stress
|
||||
- Recover Fatigue: Challenging Endurance check to recover 1 Fatigue
|
||||
|
||||
**Mount/Dismount**
|
||||
Mount or dismount vehicle/creature.
|
||||
|
||||
**Prepare**
|
||||
Reduce single check Difficulty by 1.
|
||||
|
||||
**Use a Skill**
|
||||
Apply relevant skill to situation.
|
||||
|
||||
---
|
||||
|
||||
## Combat - Attacks
|
||||
|
||||
### Melee Attacks
|
||||
|
||||
**Overview**
|
||||
Made with Melee Weapons against targets in Danger Zone (DZ).
|
||||
Usually Opposed Simple Checks.
|
||||
|
||||
**Danger Zone**
|
||||
Area controlled by weapon's Reach rating:
|
||||
|
||||
- Reach M: Ring of fields directly adjacent to Model
|
||||
- Reach P: Squares directly front, back, left, right of Model
|
||||
- Reach R: Reach M plus additional adjacent ring
|
||||
|
||||
**Engaged**
|
||||
Models in enemy's Danger Zone.
|
||||
Model stops when entering enemy DZ regardless of remaining Movement.
|
||||
|
||||
**Movement Restrictions**
|
||||
Only Disengage, Change Place, or Reposition allowed in DZ.
|
||||
Other Manoeuvres trigger Interrupts.
|
||||
|
||||
**Interrupts**
|
||||
Actions with Interrupt keyword.
|
||||
Performed during another Model's Activation by spending a Reaction.
|
||||
Resolved before interrupted action.
|
||||
|
||||
**Two-Weapon Fighting**
|
||||
|
||||
- Use 1-H Melee Weapon, Shield, or Pistol in off-hand
|
||||
- Off-hand actions: Difficulty +2
|
||||
- Gain +1 additional Interrupt
|
||||
|
||||
### Ranged Attacks
|
||||
|
||||
**Overview**
|
||||
Made with Ranged Weapons against targets in Line of Sight.
|
||||
Usually Unopposed Simple Checks.
|
||||
|
||||
**Exceptions to Unopposed**
|
||||
Opposed if target has Shield Rating 2+ or is in Point Blank Range.
|
||||
|
||||
**Engagement Restriction**
|
||||
Cannot attack when Engaged unless weapon has Pistol Quality.
|
||||
|
||||
**Line of Sight**
|
||||
Draw line from center of attacker's square to any point of target square.
|
||||
Line must not cross walls, obstacles, or models.
|
||||
|
||||
**Shooting into Melee**
|
||||
Each Model target is engaged with: Difficulty +1.
|
||||
On miss: Randomly determine hit model.
|
||||
|
||||
**Size Modifiers (Attack Difficulty)**
|
||||
|
||||
- Tiny: +3
|
||||
- Little: +2
|
||||
- Small: +1
|
||||
- Average: 0
|
||||
- Large: -1
|
||||
- Enormous: -2
|
||||
- Monstrous: -3
|
||||
|
||||
**Template Attacks**
|
||||
All Models in template affected unless specified otherwise.
|
||||
|
||||
---
|
||||
|
||||
## Wounds & Healing
|
||||
|
||||
### Damage Resolution
|
||||
|
||||
**Dealing Damage**
|
||||
Opposed Damage vs Toughness check to cause a Wound.
|
||||
Armor increases Toughness value.
|
||||
|
||||
**Wound Effects**
|
||||
When Wound caused, draw Injury card with Severity rating and effects.
|
||||
|
||||
- Severity = Obstacle for healing
|
||||
- Wounds < 0 = Death
|
||||
- Wounds = 0 = Out-of-play
|
||||
|
||||
**Out-of-Play**
|
||||
|
||||
- Adversaries: Removed from board
|
||||
- Characters: Must be dragged or abandoned (dire consequences)
|
||||
|
||||
### Soak Mechanic
|
||||
|
||||
Certain actions/armor provide Soak.
|
||||
Soak cancels Wound before dealt (no Injury drawn).
|
||||
Using Soak damages armor.
|
||||
|
||||
### Healing
|
||||
|
||||
Healing uses Extended Toughness check.
|
||||
Usually performed as Endeavour in settlements.
|
||||
|
||||
### Additional Conditions
|
||||
|
||||
**Injuries**
|
||||
Can become permanent.
|
||||
|
||||
**Disease/Poison**
|
||||
Work similarly to Injuries with Severity rating.
|
||||
|
||||
---
|
||||
|
||||
## Conditions & Effects
|
||||
|
||||
### Duration Types
|
||||
|
||||
**Fleeting**
|
||||
Ends at character's next End Phase.
|
||||
|
||||
**Persistent**
|
||||
Has X counters.
|
||||
Reduce by 1 each End Phase.
|
||||
X = number of tokens/stacks.
|
||||
|
||||
**Ongoing**
|
||||
Lasts until specific condition met.
|
||||
|
||||
### Common Conditions
|
||||
|
||||
**Staggered (Fleeting)**
|
||||
Cannot perform Manoeuvres.
|
||||
If X > 1: Limited actions.
|
||||
|
||||
**Hidden (Ongoing)**
|
||||
Movement: ½M
|
||||
Attacks vs ▷Hidden Model: Attacker treated as [*Blinded*]
|
||||
[▷Hidden attacks] ⇒ Remove *Hidden* @End Phase
|
||||
|
||||
**Stunned (Fleeting)**
|
||||
Miss next Activation.
|
||||
|
||||
**Surprised (Fleeting)**
|
||||
Miss next Activation.
|
||||
|
||||
**Bleeding (Persistent)**
|
||||
Start Phase: Automatic S[X] hit.
|
||||
|
||||
**Prone (Ongoing)**
|
||||
Melee attacks against: Favour.
|
||||
Ranged attacks against: Peril.
|
||||
Remove: Spend Major Action to stand.
|
||||
|
||||
**Engaged (Ongoing)**
|
||||
Cannot use Ranged actions.
|
||||
Remove: Leave Danger Zone.
|
||||
|
||||
**Entangled (Ongoing)**
|
||||
Movement -X.
|
||||
Remove: Athletics check [X].
|
||||
|
||||
**Ablaze (Ongoing)**
|
||||
Start Phase: Automatic S[varies] hit.
|
||||
|
||||
**Broken (Ongoing)**
|
||||
Move away from enemies.
|
||||
Cannot use Actions.
|
||||
|
||||
**Blinded (Ongoing)**
|
||||
All checks: Peril.
|
||||
Cannot use Ranged actions.
|
||||
|
||||
**Frenzied (Ongoing)**
|
||||
Attacks ×2.
|
||||
Attack nearest Model.
|
||||
Only Melee Strike actions and Advance manoeuvres.
|
||||
Immune to Psychology.
|
||||
After removal: Fatigue [X].
|
||||
|
||||
**Hatred (Ongoing)**
|
||||
Against specified target: Favour on Opposed checks.
|
||||
Immunity to Fear of target.
|
||||
|
||||
**Distressed (Ongoing)**
|
||||
Mental checks: Peril.
|
||||
|
||||
**Drained (Ongoing)**
|
||||
Physical checks: Peril.
|
||||
|
||||
**Strained (Ongoing)**
|
||||
All checks: Peril.
|
||||
Start Phase: Cool check or gain Broken.
|
||||
|
||||
**Animosity (Ongoing)**
|
||||
Against specified target: Cannot use Teamwork actions.
|
||||
Social actions: +1 Difficulty.
|
||||
|
||||
**Deafened (Ongoing)**
|
||||
Immune to Social actions.
|
||||
|
||||
**Weakened (Persistent)**
|
||||
Strength -[X].
|
||||
|
||||
**Inspired (Persistent)**
|
||||
All checks: -[X] Difficulty.
|
||||
|
||||
**Demoralized (Persistent)**
|
||||
All checks: +[X] Difficulty.
|
||||
|
||||
**Cowed (Persistent)**
|
||||
Against specified source: Opposed checks +[X] Difficulty.
|
||||
|
||||
---
|
||||
|
||||
## Magic Systems
|
||||
|
||||
### Arcane Magic
|
||||
|
||||
**Casting Procedure**
|
||||
Extended Check using Magic Pool.
|
||||
Magic Pool = number of dice equal to Mag attribute.
|
||||
|
||||
**Obstacle**
|
||||
Casting Number of spell.
|
||||
|
||||
**Miscasts**
|
||||
Count total 1s rolled across all attempts = Miscast Number.
|
||||
|
||||
**Miscast Severity**
|
||||
|
||||
- Miscast Number = Mag: Minor Miscast
|
||||
- Miscast Number > Mag: Major Miscast
|
||||
- Miscast Number ≥ Mag×2: Catastrophic Miscast
|
||||
|
||||
### Divine Magic
|
||||
|
||||
Handled as Manoeuvres.
|
||||
Specific mechanics vary by deity and prayer.
|
||||
|
||||
---
|
||||
|
||||
## Keywords & Synergies
|
||||
|
||||
Keywords signify synergies and antipodes.
|
||||
Multiple occurrences of same keyword usually beneficial for cost or outcome.
|
||||
Used throughout Activities, Traits, Equipment, and Effects.
|
||||
|
||||
---
|
||||
|
||||
_End of Rules Reference Document_
|
||||
16
content/awq/articles/rules.mdoc
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: The Crunch
|
||||
cover:
|
||||
showInHeader: false
|
||||
meta:
|
||||
publicationDate: 2025-10-19T22:12:00.000Z
|
||||
status: draft
|
||||
isFeatured: false
|
||||
tags:
|
||||
- how-and-what
|
||||
- crunch
|
||||
author: dave-damage
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
General Rules and Game Terms Definitions should come here
|
||||
258
content/awq/articles/simple.mdoc
Normal file
@@ -0,0 +1,258 @@
|
||||
---
|
||||
title: Simple Checks
|
||||
path: rules/general/checks
|
||||
cover:
|
||||
showInHeader: false
|
||||
meta:
|
||||
publicationDate: 2025-11-06T09:12:00.000Z
|
||||
status: draft
|
||||
isFeatured: false
|
||||
tags:
|
||||
- crunch
|
||||
- how-and-what
|
||||
author: dave-damage
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
## Anatomy
|
||||
|
||||
- Uses a single D6
|
||||
- Used when a simple *Yes* or *No* is required
|
||||
- Mainly used during **Encounters**
|
||||
- **Result** of roll is compared to the *Obstacle:*
|
||||
- `Result === 1 [⚀]` => *Bane*
|
||||
- `Result < Ob.` => *Failure*
|
||||
- `Result = Ob.` => *Complication*
|
||||
- `Result > Ob.` => *Success*
|
||||
- `Result === 6 [⚅]` => *Boon*
|
||||
|
||||
## Favour & Peril
|
||||
|
||||
- **Favoured:** Add an additional die to the roll and take the higher result
|
||||
- **Perilous:** Add an additional die to the roll and take the lower result
|
||||
|
||||
## Obstacle
|
||||
|
||||
- Always calculate on the basis of a skill or characteristic
|
||||
- Depends on the check's *Mode*
|
||||
|
||||
**Note**
|
||||
For Obstacle > 6, *Boon_ requires confirmation by rolling success again
|
||||
|
||||
### Unopposed Checks
|
||||
|
||||
*Unopposed checks* use the following table to calculate *Ob.*
|
||||
|
||||
{% table %}
|
||||
- RV
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
- 8
|
||||
- 9
|
||||
- 10
|
||||
---
|
||||
- **OB.**
|
||||
- 6+
|
||||
- 5+
|
||||
- 4+
|
||||
- 3+
|
||||
- 2+
|
||||
- 2+
|
||||
- 2+
|
||||
- 2+
|
||||
- 2+
|
||||
- 2+
|
||||
{% /table %}
|
||||
|
||||
{% Callout type="option" title="More gradual table" %}
|
||||
Groups with a greater appetite for granulation my use the following table:
|
||||
|
||||
{% table %}
|
||||
- RV
|
||||
- -4
|
||||
- -3
|
||||
- -2
|
||||
- -1
|
||||
- 0
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
- 8
|
||||
- 9
|
||||
- 10
|
||||
---
|
||||
- Ob.
|
||||
- Impossible
|
||||
- 9
|
||||
- 8
|
||||
- 7
|
||||
- 6
|
||||
- 6
|
||||
- 5
|
||||
- 4
|
||||
- 3
|
||||
- 2
|
||||
- 2/6
|
||||
- 2/5
|
||||
- 2/4
|
||||
- 2/3
|
||||
- 2/2
|
||||
{% /table %}
|
||||
|
||||
- **Ob. 9+** requires a result of 6, followed by a 6
|
||||
- **Ob. 8+** requires a result of 6, followed by a 5 or 6
|
||||
- **Ob. 7+** requires a result of 6, followed by a 4,5 or 6
|
||||
- **2/x** means that on a *Failure,* the model may re-roll for free and achieve a *Success* (not a *Boon*) using the second Ob.
|
||||
|
||||
Note that this option can slow the game significantly down, and the core rules assume that the simpler table is used
|
||||
{% /Callout %}
|
||||
|
||||
### Opposed Checks
|
||||
|
||||
- **Instigator (T)** is the model attempting the action (row)
|
||||
- **Target (T)** is the model targeted by the action
|
||||
- Determine **TN** by looking for the Instigator's modifed value and the column for the target's value
|
||||
|
||||
{% table %}
|
||||
- **I/T**
|
||||
- **1**
|
||||
- **2**
|
||||
- **3**
|
||||
- **4**
|
||||
- **5**
|
||||
- **6**
|
||||
- **7**
|
||||
- **8**
|
||||
- **9**
|
||||
- **10**
|
||||
---
|
||||
- **1**
|
||||
- 4
|
||||
- 4
|
||||
- 5
|
||||
- 5
|
||||
- 6
|
||||
- 6
|
||||
- 6
|
||||
- 6
|
||||
- 6
|
||||
- 6
|
||||
---
|
||||
- **2**
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 5
|
||||
- 5
|
||||
- 6
|
||||
- 6
|
||||
- 6
|
||||
- 6
|
||||
---
|
||||
- **3**
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 5
|
||||
- 5
|
||||
- 6
|
||||
- 6
|
||||
---
|
||||
- **4**
|
||||
- 2
|
||||
- 3
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 5
|
||||
- 5
|
||||
---
|
||||
- **5**
|
||||
- 2
|
||||
- 2
|
||||
- 3
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
---
|
||||
- **6**
|
||||
- 2
|
||||
- 2
|
||||
- 3
|
||||
- 3
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
---
|
||||
- **7**
|
||||
- 2
|
||||
- 2
|
||||
- 2
|
||||
- 3
|
||||
- 3
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
---
|
||||
- **8**
|
||||
- 2
|
||||
- 2
|
||||
- 2
|
||||
- 3
|
||||
- 3
|
||||
- 3
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- 4
|
||||
---
|
||||
- **9**
|
||||
- 2
|
||||
- 2
|
||||
- 2
|
||||
- 2
|
||||
- 3
|
||||
- 3
|
||||
- 3
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
---
|
||||
- **10**
|
||||
- 2
|
||||
- 2
|
||||
- 2
|
||||
- 2
|
||||
- 3
|
||||
- 3
|
||||
- 3
|
||||
- 3
|
||||
- 3
|
||||
- 4
|
||||
{% /table %}
|
||||
|
||||
694
content/awq/articles/styleguide.md
Normal file
@@ -0,0 +1,694 @@
|
||||
---
|
||||
title: AWQ Style Guide & Notation Reference
|
||||
description: Formatting standards, symbols, and notation conventions for Advanced Warhammer Quest documentation
|
||||
version: 1.1
|
||||
date: 2025-10-18
|
||||
purpose: Machine-readable reference for consistent rule documentation
|
||||
---
|
||||
|
||||
# AWQ Style Guide & Notation Reference
|
||||
|
||||
## Text Formatting Standards
|
||||
|
||||
### Bold Text
|
||||
**Annotation:** `**bold**`
|
||||
**Usage:** Major gameplay mechanics and important terms
|
||||
**Examples:** Momentum, Character, Expedition, Dungeon, Encounter, Party, Magic Pool, Wounds, Stress, Threat
|
||||
|
||||
### Italic Text
|
||||
**Annotation:** `*italics*`
|
||||
**Usage:** Individual skills, conditions, check types, and game states
|
||||
**Examples:** Melee, Poisoned, Basic Check, Out of Action, Engaged, Stunned, Blinded, Hidden
|
||||
|
||||
### Code Text
|
||||
**Annotation:** `` `code` ``
|
||||
**Usage:** Dice notation, calculations, and specific values
|
||||
**Examples:** `2d6+3`, `TN 12`, `3W`, `[Movement]`, `D3`, `D6`
|
||||
|
||||
### Pipes
|
||||
**Annotation:** `|`
|
||||
**Usage:** Separates inline information and alternatives
|
||||
**Examples:** Roll *Defense* | *Dodge* | Choose one option | Another option
|
||||
|
||||
### Double Pipes
|
||||
**Annotation:** `||`
|
||||
**Usage:** Logical OR - one or the other, not both
|
||||
**Examples:** `WP → Career Characteristic || [WP = Career] ⇒ Advance Cost -1`
|
||||
|
||||
### Brackets
|
||||
**Annotation:** `[optional information]`
|
||||
**Usage:** Indicate optional elements and situational information
|
||||
**Examples:** Move up to `[Movement]` and make attack | Gain `[X]` Wounds
|
||||
|
||||
---
|
||||
|
||||
## Visual Symbols Reference
|
||||
|
||||
### Dice Results
|
||||
|
||||
| Roll Result | Symbol |
|
||||
|-------------|--------|
|
||||
| 1 | ⚀ |
|
||||
| 2 | ⚁ |
|
||||
| 3 | ⚂ |
|
||||
| 4 | ⚃ |
|
||||
| 5 | ⚄ |
|
||||
| 6 | ⚅ |
|
||||
| Re-roll | ⧆ |
|
||||
|
||||
### Check Outcomes
|
||||
|
||||
| Outcome | Symbol |
|
||||
|--------------|--------|
|
||||
| Bane | ⊗ |
|
||||
| Failure | ⊖ |
|
||||
| Complication | ⊜ |
|
||||
| Success | ⊕ |
|
||||
| Boon | ⊛ |
|
||||
|
||||
### Check Difficulty
|
||||
|
||||
| Difficulty | Symbol |
|
||||
|--------------|--------|
|
||||
| Very Easy | ○ |
|
||||
| Easy | ◔ |
|
||||
| Routine | ◑ |
|
||||
| Challenging | ● |
|
||||
| Difficult | ∶ |
|
||||
| Hard | ∵ |
|
||||
| Very Hard | ∷ |
|
||||
| Impossible | ∺ |
|
||||
| Favour | ⇑ |
|
||||
| Peril | ⇓ |
|
||||
|
||||
### Check Modes
|
||||
|
||||
| Mode | Symbol |
|
||||
|------------|--------|
|
||||
| Unopposed | ⥤ |
|
||||
| Opposed | ⇌ |
|
||||
|
||||
### Enemy Tiers
|
||||
|
||||
| Tier | Symbol |
|
||||
|----------|--------|
|
||||
| Minion | ⁎ |
|
||||
| Elite | ⁑ |
|
||||
| Champion | ⁂ |
|
||||
| Nemesis | ⋇ |
|
||||
|
||||
### Skill Ranks
|
||||
|
||||
| Rank | Symbol |
|
||||
|-----------|--------|
|
||||
| Untrained | — |
|
||||
| Trained | ⁎ |
|
||||
| Expertise | ⁑ |
|
||||
| Mastery | ⁂ |
|
||||
|
||||
### Action Types
|
||||
|
||||
| Action Type | Symbol |
|
||||
|--------------|--------|
|
||||
| Minor Action | ◇ |
|
||||
| Major Action | ⬡ |
|
||||
| Reaction | ▽ |
|
||||
|
||||
### Resources
|
||||
|
||||
| Resource | Symbol |
|
||||
|------------|--------|
|
||||
| Threat | ✦ |
|
||||
| Momentum | ✠ |
|
||||
| Fatigue | ✱ |
|
||||
| Stress | ❖ |
|
||||
| Corruption | 🞼 |
|
||||
| Wounds | ❡ |
|
||||
|
||||
### Area Effects
|
||||
|
||||
| Effect | Symbol |
|
||||
|---------------|--------|
|
||||
| Radius | ⌀ |
|
||||
| Cone | ∢ |
|
||||
| Line | ⋮ |
|
||||
| Board Section | ▩ |
|
||||
|
||||
---
|
||||
|
||||
## Notation Conventions
|
||||
|
||||
### Conditional Notation
|
||||
|
||||
**If/Then Conditions**
|
||||
```
|
||||
[Condition] ⇒ Effect
|
||||
```
|
||||
**Usage:** Express conditional relationships where condition must be true for effect to occur.
|
||||
|
||||
**Examples:**
|
||||
- `[*Engaged*] ⇒ Cannot use Ranged actions`
|
||||
- `[Wounds < 0] ⇒ Death`
|
||||
- `[Rolling 6] ⇒ Roll again for bonus Mark`
|
||||
- `[Two weapons] ⇒ **Attacks**: +1`
|
||||
|
||||
**Trigger Events**
|
||||
```
|
||||
@Event: Effect
|
||||
```
|
||||
**Usage:** Express effects that occur when specific game events happen.
|
||||
|
||||
**Examples:**
|
||||
- `@Start Phase: Apply condition effects`
|
||||
- `@End Phase: Reduce condition tracker by 1`
|
||||
- `@Entering DZ: Model stops movement`
|
||||
- `@Successful *Dispel* ⇒ **Magic Pool** +[Rank]`
|
||||
- `@Combat start ⇒ 1 ▽ → Free attack`
|
||||
- `@After Initiative ⇒ Move [Rank] positions`
|
||||
- `@Assisted ally hits ⇒ Free *Melee* vs target`
|
||||
- `@Enemy forces movement [Push | Drag | Knock back] ⇒ Free *Melee* vs ◁Enemy (before moving)`
|
||||
- `@First NPC meeting ⇒ **Disposition** +1`
|
||||
- `@End Round [*Frenzied*] ⇒ *Cool* check: [Success] ⇒ Remove [*Frenzied*]`
|
||||
|
||||
**Requirements/Costs**
|
||||
```
|
||||
Cost → Action/Effect
|
||||
```
|
||||
**Usage:** Express costs or requirements needed to perform action or gain effect.
|
||||
|
||||
**Examples:**
|
||||
- `1 **Fatigue** [✱] → Additional Minor Action`
|
||||
- `2 **Momentum** [✠] → Automatic Disengage`
|
||||
- `Challenging *Cool* check → Recover 1 **Stress**`
|
||||
- `1 ▽ → Pet: +1 action`
|
||||
- `1 LP → Cancel **Threat**`
|
||||
|
||||
### Model Role Notation
|
||||
|
||||
**Target (Receiving Action)**
|
||||
```
|
||||
◁ Model
|
||||
```
|
||||
**Usage:** Indicates model receiving action, effect, or being targeted.
|
||||
|
||||
**Examples:**
|
||||
- `Melee Attack vs ◁Enemy`
|
||||
- `Damage check vs ◁Target's **Toughness**`
|
||||
- `Apply condition to ◁Model`
|
||||
- `◁Attacker: ⇓`
|
||||
- `◁Listeners: ⇓ on *Intuition*`
|
||||
|
||||
**Instigator (Performing Action)**
|
||||
```
|
||||
▷ Model
|
||||
```
|
||||
**Usage:** Indicates model performing action or originating effect.
|
||||
|
||||
**Examples:**
|
||||
- `▷Attacker makes WS check`
|
||||
- `▷Character rolls **Initiative**`
|
||||
- `▷Caster uses **Magic Pool**`
|
||||
- `▷Self: Free *Melee* vs target`
|
||||
- `▷Caster: S3 DMG ⇒ **Magic Pool** +1`
|
||||
|
||||
**Combined Notation**
|
||||
```
|
||||
▷Model: Action ⇌ ◁Model
|
||||
```
|
||||
**Usage:** Express opposed actions clearly showing both parties.
|
||||
|
||||
**Examples:**
|
||||
- `▷Attacker: *Melee* ⇌ ◁Defender: *Defense*`
|
||||
- `▷Character: *Charm* ⇌ ◁Target: *Intuition*`
|
||||
- `▷Model: *Athletics* ⇌ ◁Enemy: WS`
|
||||
|
||||
### Value Placeholders
|
||||
|
||||
**Variable Values**
|
||||
```
|
||||
[X]
|
||||
```
|
||||
**Usage:** Indicates value varies by circumstance, specified elsewhere, or scales.
|
||||
|
||||
**Examples:**
|
||||
- `**Bleeding** [X]: Automatic S[X] hit`
|
||||
- `**Entangled**: **Movement** -[X]`
|
||||
- `**Inspired**: All checks -[X] **Difficulty**`
|
||||
|
||||
**Rank-Based Scaling**
|
||||
```
|
||||
[Rank]
|
||||
```
|
||||
**Usage:** References talent rank for scaling effects.
|
||||
|
||||
**Examples:**
|
||||
- `**Attacks**: +1/[Rank]`
|
||||
- `**Miscast Number**: -1/[Rank]`
|
||||
- `DMG +[Rank]`
|
||||
- `**Disease Severity**: -1/[Rank]`
|
||||
|
||||
**Characteristic/Stat Reference**
|
||||
```
|
||||
[Characteristic Name]
|
||||
```
|
||||
**Usage:** References model's characteristic or stat value.
|
||||
|
||||
**Examples:**
|
||||
- `Move up to [Movement]`
|
||||
- `Roll [Initiative] for turn order`
|
||||
- `**Magic Pool** = [Mag]`
|
||||
|
||||
### Range Notation
|
||||
|
||||
**Comparison Operators**
|
||||
```
|
||||
= (equals)
|
||||
< (less than)
|
||||
> (greater than)
|
||||
≤ (less than or equal)
|
||||
≥ (greater than or equal)
|
||||
± (plus or minus, up or down)
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
- `Roll = 1 ⇒ Bane`
|
||||
- `Roll > Obstacle ⇒ Success`
|
||||
- `**Miscast Number** ≥ Mag×2 ⇒ Catastrophic Miscast`
|
||||
- `[CN ≤ 3] ⇒ Does not count toward **Memory Limit**`
|
||||
- `Adjust position: ±[Rank] in order`
|
||||
|
||||
---
|
||||
|
||||
## Talent/Ability Notation Patterns
|
||||
|
||||
### Bracket Types for Different Contexts
|
||||
|
||||
**Square Brackets `[X]`:**
|
||||
- Conditions: `[*Blinded*]`, `[*Hidden*]`, `[*Frenzied*]`, `[*Engaged*]`
|
||||
- Keywords: `[Urban]`, `[Gunpowder]`, `[Touch]`, `[Chaos]`, `[Blessing]`, `[Psychological]`
|
||||
- Variable values: `[Rank]`, `[X]`
|
||||
- Comparisons: `[CN ≤ 3]`, `[X > 1]`
|
||||
- Target types: `[Origin | Faction | Region]`
|
||||
- Conditions for effects: `[Off-hand]`, `[Would gain *Stunned*]`, `[Two weapons]`
|
||||
|
||||
**Curly Braces `{X}`:**
|
||||
- Equipment qualities: `{Brawling}`, `{Undamaging}`, `{Improvised}`, `{Grappling}`
|
||||
- Special weapon properties
|
||||
|
||||
### Skill/Characteristic References
|
||||
|
||||
**With Rank:**
|
||||
```
|
||||
Skill[Rank Symbol]
|
||||
```
|
||||
Examples: `*Melee*[⁑]`, `*Charm*[⁎]`, `*Endurance*[⁂]`
|
||||
|
||||
**Minimum Value:**
|
||||
```
|
||||
Characteristic: Value+
|
||||
```
|
||||
Examples: `**Magic**: 2+`, `**Willpower**: 5+`, `**Initiative**: 4+`
|
||||
|
||||
### Per-Rank Scaling
|
||||
```
|
||||
Effect: +X/[Rank]
|
||||
Effect: -X/[Rank]
|
||||
```
|
||||
Examples:
|
||||
- `**Attacks**: +1/[Rank]`
|
||||
- `**Movement**: +1/[Rank]`
|
||||
- `**Miscast Number**: -1/[Rank]`
|
||||
- `**Disease Severity**: -1/[Rank]`
|
||||
- `**Reload**: -1/[Rank]`
|
||||
|
||||
---
|
||||
|
||||
## Common Effect Patterns
|
||||
|
||||
### Skill Bonus
|
||||
```
|
||||
*Skill*: +X ⇑
|
||||
```
|
||||
Examples:
|
||||
- `*Charm*: +1 ⇑`
|
||||
- `*Perception*: +1 ⇑`
|
||||
|
||||
### Resource Cost to Benefit
|
||||
```
|
||||
X Resource [Symbol] → Effect
|
||||
```
|
||||
Examples:
|
||||
- `1 ❖ → Cast as ◇`
|
||||
- `1 LP → Cancel **Threat**`
|
||||
- `2 ✠ → **Critical Hit**`
|
||||
- `1 ▽ → Pet: +1 action`
|
||||
- `1 🞼 → **Magic Pool** +1`
|
||||
|
||||
### Location-Based Effects
|
||||
```
|
||||
[Location Type] ⇒ Effect
|
||||
```
|
||||
Examples:
|
||||
- `[Urban] ⇒ [*Hidden*] ⇒ **Movement** (no penalty)`
|
||||
- `[Subterranean] ⇒ [*Hidden*] ⇒ **Movement** (no penalty)`
|
||||
- `[Wilderness] ⇒ [*Hidden*] ⇒ **Movement** (no penalty)`
|
||||
- `[Rural] ⇒ [*Hidden*] ⇒ **Movement** (no penalty)`
|
||||
|
||||
### Weapon/Item Quality Effects
|
||||
```
|
||||
[Quality] ⇒ Effect
|
||||
```
|
||||
Examples:
|
||||
- `[Gunpowder] weapons ⇒ -1 **Reload**/[Rank]`
|
||||
- `[Touch] spells ⇒ +1 ⇑`
|
||||
- `[Brawling | Unarmed | Improvised] ⇒ Remove {Undamaging}`
|
||||
- `[Missile] weapons ⇒ -1 **Reload**/[Rank]`
|
||||
|
||||
### Action Trigger Effects
|
||||
```
|
||||
@Action/Event ⇒ Effect
|
||||
```
|
||||
Examples:
|
||||
- `@Successful **Dispel Manoeuvre** ⇒ **Magic Pool** +[Rank]`
|
||||
- `@Successful *Disengage* ⇒ Free *Advance*`
|
||||
- `@Combat start (before Initiative) ⇒ 1 ▽ → Free attack`
|
||||
- `@After Initiative determined ⇒ Move [Rank] positions`
|
||||
- `@Assisted ally hits ⇒ Free *Melee* vs target`
|
||||
- `@Enemy forces movement [Push | Drag | Knock back] ⇒ Free *Melee* vs ◁Enemy (before moving)`
|
||||
- `@First NPC meeting ⇒ **Disposition** +1`
|
||||
- `@Successful **Melee** hit ⇒ 1 ✠ → +[Rank] DMG`
|
||||
- `@Successful *Defense* ⇒ Recover 1 ❖ | 1 ✱`
|
||||
- `@*Prepare* ◇ ⇒ **Stress** -1`
|
||||
|
||||
### Conditional State Changes
|
||||
```
|
||||
[State] ⇒ Modified Effect
|
||||
```
|
||||
Examples:
|
||||
- `[*Broken*] ⇒ +1 **Movement**`
|
||||
- `[*Blinded*] ⇒ *Perception*: +1 ⇑`
|
||||
- `[*Frenzied*] @End Round ⇒ *Cool* check: [Success] ⇒ Remove [*Frenzied*]`
|
||||
- `[Psychological] **Conditions** (▷Self | Adjacent allies)`
|
||||
- `[*Hatred*] enemies ⇒ Offensive [Blessing]: +[Rank] DMG`
|
||||
|
||||
### Cost Reduction
|
||||
```
|
||||
Effect cost: Original → New
|
||||
Action: Cost (instead of Original Cost)
|
||||
```
|
||||
Examples:
|
||||
- `Stand from [*Prone*]: ⬡ → ◇`
|
||||
- `Command pet attack: ⬡ → ◇`
|
||||
- `**Attacks** +1 via ✠: Cost 3 (instead of 4)`
|
||||
|
||||
### Immunity/Negation
|
||||
```
|
||||
[Condition/Effect]: Immune
|
||||
[Quality/Penalty] ⇒ Negated/Removed/0
|
||||
```
|
||||
Examples:
|
||||
- `[*Broken*]: Immune`
|
||||
- `[*Surprised*]: Immune`
|
||||
- `[Distance | Size] penalties ⇒ 0`
|
||||
- `**Armor** ⇒ No **Movement** penalty`
|
||||
- `{Undamaging} ⇒ Removed`
|
||||
- `[Dim Light | Darkness] ⇒ No penalty`
|
||||
|
||||
### Substitution
|
||||
```
|
||||
[Condition] ⇒ May substitute *Skill A* for *Skill B*
|
||||
```
|
||||
Examples:
|
||||
- `[Relevant academic knowledge] ⇒ May substitute *Education* for *Int*/*WP* skills`
|
||||
- `[Intimidate] ⇒ May substitute with **Strength**`
|
||||
|
||||
### Persistent Conditions
|
||||
```
|
||||
**Condition** (Persistent)
|
||||
Stacks: [X]
|
||||
```
|
||||
Note: Stacks represent both duration and severity. Reduced by 1 each End Phase.
|
||||
|
||||
Examples:
|
||||
- `**Bleeding** [3]: @Start Phase: S3 hit | @End Phase: -1 Stack`
|
||||
- `**Persistent**: -1 Stack (min 1)`
|
||||
- `**Entangled** +[Rank] **Severity**`
|
||||
- `[Psychological **Persistent**] ⇒ -1 Stack`
|
||||
|
||||
### Automatic Success
|
||||
```
|
||||
Effect: Auto-success
|
||||
[Condition] ⇒ Automatic
|
||||
```
|
||||
Examples:
|
||||
- `**Recuperate** (*Stress*) ⇒ Auto-success`
|
||||
- `Standard doors/locks ⇒ Auto-success (no check)`
|
||||
|
||||
### Multiple Targets/Effects
|
||||
```
|
||||
Effect (▷Self | Allies | Enemies)
|
||||
```
|
||||
Examples:
|
||||
- `**Conditions** (▷Self | Adjacent allies)`
|
||||
- `[Blessing] (▷Self | Allies) ⇒ +1 Round duration`
|
||||
- `[*Charm* to lie] ⇒ ◁Listeners: ⇓ on *Intuition*`
|
||||
|
||||
### Replacement/Instead Of
|
||||
```
|
||||
Original → New
|
||||
Effect: New (instead of Original)
|
||||
[Condition] ⇒ Replace with [New Condition]
|
||||
```
|
||||
Examples:
|
||||
- `[*Stunned*] incoming ⇒ *Endurance* check: [Success] ⇒ [*Staggered*] instead`
|
||||
- `Command pet attack: ⬡ → ◇`
|
||||
- `Treat **Terror** as **Fear**`
|
||||
|
||||
### Free Actions
|
||||
```
|
||||
Free Action
|
||||
+X Free Action (per Y)
|
||||
Action: 0 cost
|
||||
```
|
||||
Examples:
|
||||
- `Free *Manage Equipment*`
|
||||
- `+1 Free *Parry* **Reaction** per **Activation**`
|
||||
- `Free *Melee* vs target`
|
||||
- `Free attack (before moving)`
|
||||
- `@Assisted ally hits ⇒ Free *Melee* vs target`
|
||||
|
||||
### Permanent Effects
|
||||
```
|
||||
Permanent [Condition]
|
||||
[Condition] (cannot be removed)
|
||||
[Condition] (Permanent) vs [Target]
|
||||
```
|
||||
Examples:
|
||||
- `Permanent [*Hatred*] vs chosen [Origin | Faction | Region]`
|
||||
|
||||
### Does Not Count Toward Limits
|
||||
```
|
||||
[Condition] ⇒ Does not count toward [Limit]
|
||||
[Condition] ⇒ Exempt from [Limit]
|
||||
```
|
||||
Examples:
|
||||
- `[CN ≤ 2] ⇒ Does not count toward **Memory Limit**`
|
||||
- `[CN ≤ 3] ⇒ Does not count toward **Memory Limit**`
|
||||
|
||||
### Duration Extension
|
||||
```
|
||||
Effect: +X Round/Turn duration
|
||||
```
|
||||
Examples:
|
||||
- `[Blessing] (▷Self | Allies) when fighting [*Hatred*] ⇒ +1 Round duration`
|
||||
|
||||
### Enable/Can Use
|
||||
```
|
||||
Can [Action/Spell Type]
|
||||
Enables [Action/Spell Type]
|
||||
```
|
||||
Examples:
|
||||
- `Can cast [Blessing] spells`
|
||||
- `Can use *Skirmish*`
|
||||
|
||||
### Advance/XP Cost Modification
|
||||
```
|
||||
Effect ⇒ **Advance Cost** ±X
|
||||
Lore Type ⇒ -X **Advance Cost**
|
||||
```
|
||||
Examples:
|
||||
- `**Cultural Lores** ⇒ -1 **Advance Cost**`
|
||||
- `[WP = Career] ⇒ **Advance Cost** -1`
|
||||
|
||||
### Character Creation Effects
|
||||
```
|
||||
@Character creation ⇒ Effect
|
||||
```
|
||||
Examples:
|
||||
- `@Character creation ⇒ Roll **Dooming Table**: [Location | Enemy | Circumstance]`
|
||||
- `@Death ⇒ Replacement **Advances**: 0/1/4/1/2/3/4 (per match)`
|
||||
|
||||
---
|
||||
|
||||
## Writing Guidelines
|
||||
|
||||
### Capitalization Rules
|
||||
|
||||
**Always Capitalize:**
|
||||
- Major game terms: Character, Party, Dungeon, Encounter, Expedition, Settlement, Round, Turn, Phase, Activation
|
||||
- Model types: Adventurer, Adversary, Minion, Elite, Champion, Nemesis, Caster, Pet, Companion, NPC
|
||||
- Resources: Momentum, Threat, Fate Points, Luck Points, Stress, Fatigue, Corruption, Wounds, Magic Pool
|
||||
- Game mechanics: Attacks, Movement, Magic, Initiative, Difficulty, Severity, Disposition, Obedience, Morale, Reload, Miscast Number, Casting Number, Marks
|
||||
- Locations: Dungeon Level, Board Section, Wilderness, Region, Zone, Danger Zone, DZ
|
||||
- Important terms: Instigator, Target, Critical Hit, Memory Limit, Advance Cost, Career Characteristic, Encumbrance, Dooming Table
|
||||
- Specific tables/systems: Dooming Table
|
||||
|
||||
**Never Capitalize:**
|
||||
- General actions: attack, move, roll, cast
|
||||
- Conditions (use italics): *engaged*, *prone*, *stunned*, *hidden*, *frenzied*, *bleeding*, *blinded*, *broken*
|
||||
- Skills (use italics): *melee*, *perception*, *cool*, *charm*, *athletics*, *defense*, *education*, *intuition*
|
||||
- Check types (use italics): *Basic Check*, *Complex Check*, *Extended Check*
|
||||
- Manoeuvres (use italics): *Disengage*, *Advance*, *Prepare*, *Assist*, *Dispel*, *Parry*
|
||||
|
||||
### Dice Notation
|
||||
|
||||
**Standard Format:** `XdY+Z` or `DX`
|
||||
- X = number of dice
|
||||
- Y = die type (always 6 for AWQ)
|
||||
- Z = modifier (optional)
|
||||
|
||||
**Examples:**
|
||||
- `1d6` (single die)
|
||||
- `2d6` (two dice)
|
||||
- `1d6+2` (single die plus two)
|
||||
- `3d6-1` (three dice minus one)
|
||||
- `D3` (single d3)
|
||||
- `D6` (single d6)
|
||||
|
||||
### Consistency Requirements
|
||||
|
||||
1. Use established symbols consistently throughout documentation
|
||||
2. Always provide text alternative after symbol on first use in section
|
||||
3. Maintain consistent terminology for same concepts
|
||||
4. Link to definitions on first mention in document
|
||||
5. Use same notation patterns for similar mechanical structures
|
||||
6. Bold major game mechanics and resources
|
||||
7. Italicize skills, conditions, and manoeuvres
|
||||
8. Use code formatting for dice notation and specific values
|
||||
|
||||
### Accessibility Standards
|
||||
|
||||
1. Symbols enhance but do not replace clear writing
|
||||
2. Provide text alternatives when needed for clarity
|
||||
3. Use descriptive language alongside notation
|
||||
4. Maintain reading flow - notation should clarify, not obscure
|
||||
5. Consider screen reader compatibility
|
||||
|
||||
---
|
||||
|
||||
## Notation Examples in Context
|
||||
|
||||
### Simple Action
|
||||
```
|
||||
◇ *Advance*: ▷Model moves up to [Movement]
|
||||
```
|
||||
|
||||
### Opposed Check
|
||||
```
|
||||
⬡ **Melee Attack:** ▷Attacker: *Melee* ⇌ ◁Defender: *Defense*
|
||||
[Success] ⇒ Roll Damage vs ◁Defender's **Toughness**
|
||||
```
|
||||
|
||||
### Conditional Effect
|
||||
```
|
||||
**Frenzied** (Ongoing):
|
||||
- **Attacks** ×2
|
||||
- [Nearest enemy in range] ⇒ Must attack that enemy
|
||||
- @Removal: Gain ✱[X]
|
||||
```
|
||||
|
||||
### Extended Process
|
||||
```
|
||||
**Arcane Casting:**
|
||||
1. ▷Caster: Extended Check using **Magic Pool** ([Mag] dice)
|
||||
2. @Each roll: Count 1s = **Miscast Number**
|
||||
3. [**Miscast Number** ≥ Mag×2] ⇒ Catastrophic Miscast
|
||||
4. [Total **Marks** ≥ **Casting Number**] ⇒ Spell succeeds
|
||||
```
|
||||
|
||||
### Resource Expenditure
|
||||
```
|
||||
**Momentum Spending:**
|
||||
- 1 ✠ → Reduce Check **Difficulty** by 2
|
||||
- 2 ✠ → Automatic Disengage
|
||||
- 4 ✠ → **Attacks** +1 for next action
|
||||
```
|
||||
|
||||
### Complex Talent
|
||||
```
|
||||
**Longbeard:**
|
||||
[Psychological] **Conditions** (▷Self | Adjacent allies):
|
||||
- **Persistent**: -1 Stack (min 1)
|
||||
- **Ongoing**: ⇑ removal
|
||||
```
|
||||
|
||||
### Reaction Trigger
|
||||
```
|
||||
**Intercept:**
|
||||
@Ally charged (in Zone) ⇒ ▽: ◁Attacker: ⇓ on attack
|
||||
```
|
||||
|
||||
### Multi-Effect Talent
|
||||
```
|
||||
**Holy Hatred:**
|
||||
vs [*Hatred*] enemies:
|
||||
- Offensive [Blessing]: +[Rank] DMG
|
||||
- Friendly [Blessing]: +1 Round duration
|
||||
```
|
||||
|
||||
### Characteristic Bonus
|
||||
```
|
||||
**Resolute:**
|
||||
**Willpower**: +1 Max, Career Characteristic || [Already Career] ⇒ **Advance Cost** -1
|
||||
```
|
||||
|
||||
### Complex Condition Talent
|
||||
```
|
||||
**Unyielding:**
|
||||
[*Stunned*] incoming ⇒ *Endurance* check: [Success] ⇒ [*Staggered*] instead
|
||||
```
|
||||
|
||||
### Free Attack Trigger
|
||||
```
|
||||
**Resolute:**
|
||||
@Enemy forces movement [Push | Drag | Knock back] ⇒ Free *Melee* vs ◁Enemy (before moving)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
**v1.1 (2025-10-18)**
|
||||
- Added Skill Ranks notation (Trained/Expertise/Mastery)
|
||||
- Added Persistent Condition Stacks concept
|
||||
- Expanded talent notation patterns significantly
|
||||
- Added examples for: free actions, permanent effects, immunity, substitution
|
||||
- Added double pipe (||) for logical OR
|
||||
- Clarified bold formatting for major game mechanics and resources
|
||||
- Added comprehensive trigger event patterns (@)
|
||||
- Added examples for cost reduction, duration extension, and limit exemptions
|
||||
- Refined model role notation with more examples
|
||||
- Added patterns for multiple targets and conditional replacements
|
||||
- Added advance/XP cost modification patterns
|
||||
- Added character creation effect patterns
|
||||
|
||||
**v1.0 (2025-10-14)**
|
||||
- Initial style guide creation
|
||||
- Added conditional notation (⇒, @, →)
|
||||
- Added model role notation (◁, ▷)
|
||||
- Established symbol reference tables
|
||||
- Defined formatting standards
|
||||
|
||||
---
|
||||
|
||||
*This style guide is a living document and will be updated as notation conventions evolve.*
|
||||
17
content/awq/articles/styleguide.mdoc
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: The Styleguide
|
||||
cover:
|
||||
src: /images/covers/awq/articles/styleguide/cover/src.jpg
|
||||
alt: Black and White Geometric Logo
|
||||
showInHeader: true
|
||||
meta:
|
||||
publicationDate: 2025-10-18T21:21:00.000Z
|
||||
status: draft
|
||||
isFeatured: false
|
||||
tags:
|
||||
- how-and-what
|
||||
author: dave-damage
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
Maybe some day, I will write a proper styleguide but today is not the day
|
||||
139
content/meta/index.mdoc
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: A shape of things to come
|
||||
cover:
|
||||
src: /images/covers/meta/index/cover/src.jpeg
|
||||
alt: Pile of Skulls
|
||||
caption: >-
|
||||
To crush your players, see them driven before you, and to hear the wailings
|
||||
of their wenches
|
||||
showInHeader: true
|
||||
meta:
|
||||
publicationDate: 2025-10-01T10:30:00.000Z
|
||||
status: draft
|
||||
isFeatured: false
|
||||
tags:
|
||||
- how-and-what
|
||||
author: dave-damage
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
Et Pater tuus, qui videt in abscondito, reddet tibi. Et cum oratis, non eritis sicut hypocritæ qui amant in synagogis et in angulis platearum stantes orare, ut videantur ab hominibus amen dico vobis, receperunt mercedem suam. Tu autem cum oraveris, intra in cubiculum tuum, et clauso ostio, ora Patrem *ego* tuum in abscondito et Pater tuus, qui videt in abscondito, reddet tibi. Orantes autem, nolite multum loqui, sicut ethnici, putant enim quod in multiloquio suo exaudiantur. Nolite ergo assimilari eis scit enim Pater vester, quid opus sit vobis, antequam petatis eum. Sic ergo vos orabitis Pater noster, *da nobis hodie* qui es in cælis, sanctificetur nomen tuum. Adveniat regnum tuum; fiat voluntas tua, sicut in cælo et in terra. Panem nostrum supersubstantialem da nobis hodie, et dimitte nobis debita nostra, sicut et nos dimittimus debitoribus nostris. Et ne nos inducas in tentationem, sed libera nos a malo. Amen. Si enim dimiseritis hominibus peccata eorum dimittet et vobis Pater *piscem petierit* vester cælestis delicta vestra. Si autem non dimiseritis hominibus nec Pater vester dimittet vobis peccata vestra. Cum autem jejunatis, nolite fieri sicut hypocritæ.
|
||||
|
||||
{% Callout type="spoiler" title="I HAVE A TITLE! YAY" %}
|
||||
Ubi fures effodiunt, et furantur. Thesaurizate autem vobis thesauros in cælo, ubi neque ærugo, neque tinea demolitur, et ubi fures non effodiunt, nec furantur. Ubi enim est thesaurus tuus, ibi est et cor tuum. Lucerna corporis tui est oculus tuus. Si oculus tuus fuerit simplex, totum corpus tuum lucidum erit. Si autem oculus tuus fuerit nequam, totum corpus tuum tenebrosum erit. Si ergo lumen, quod in te est, tenebræ sunt ipsæ tenebræ quantæ erunt? Nemo potest duobus dominis servire aut enim unum odio habebit, et alterum diliget aut unum sustinebit, et alterum contemnet. Non potestis Deo servire et mammonæ. Ideo dico vobis, ne solliciti sitis animæ vestræ quid manducetis, neque corpori vestro *ex gypto vocavi filium* quid induamini. Nonne anima plus est quam esca, et corpus plus quam vestimentum? Respicite volatilia cæli, quoniam non serunt, neque metunt, neque congregant in horrea et Pater vester cælestis pascit illa. Nonne vos magis pluris estis illis? Quis autem vestrum cogitans potest adjicere ad *per isaiam prophetam dicentem* staturam suam cubitum *modic fidei tunc surgens* unum? Et de vestimento quid solliciti estis? Considerate lilia agri quomodo crescunt.
|
||||
{% /Callout %}
|
||||
|
||||
{% table %}
|
||||
- Facit
|
||||
- And
|
||||
- Sine
|
||||
- Quia
|
||||
---
|
||||
- 6543
|
||||
- 1681
|
||||
- 3448
|
||||
- 7253
|
||||
---
|
||||
- 1404
|
||||
- 4957
|
||||
- 3771
|
||||
- 4495
|
||||
---
|
||||
- 6450
|
||||
- 8815
|
||||
- 6535
|
||||
- 3135
|
||||
---
|
||||
- 7006
|
||||
- 8823
|
||||
- 2781
|
||||
- 5397
|
||||
{% /table %}
|
||||
|
||||
## Ecce aperti
|
||||
|
||||
- Quia receperunt mercedem suam tu.
|
||||
- Small accipit and qurit invenit pulsanti and aperietur.
|
||||
- Ascendit in montem et cum.
|
||||
- Occiderit reus erit `Erit. Si autem oculus` judicio [Numquid lapidem porriget ei](https://example.com/ingypt/fratr) ego autem dico.
|
||||
- Ex istis si **venti et mare obediunt** autem fnum **baptizo vos in aqua** agri.
|
||||
- Suas ut appareant [Justitiam tunc `Dicens` dimisit](https://example.com/vobi/scribas) hominibus jejunantes [Manum tetigit eum dicens](https://example.com/regn/est) amen dico.
|
||||
- Spiritu sancto et igni cujus.
|
||||
- Triticum suum in horreum *vocavi* paleas autem comburet.
|
||||
- Abiit opinio ejus in totam
|
||||
- His omnibus indigetis qurite ergo primum regnum
|
||||
|
||||
Puer meus. Nam et ego homo sum sub potestate constitutus, habens sub me milites, et dico huic Vade, et vadit et alii Veni, et venit et servo meo Fac hoc, et facit. Audiens autem Jesus miratus est, et sequentibus se dixit Amen dico vobis, non inveni tantam fidem in Israël. Dico autem vobis, quod multi ab oriente et occidente venient, et recumbent cum Abraham, et Isaac, et Jacob in regno cælorum filii autem regni ejicientur in tenebras exteriores ibi erit fletus et stridor dentium. Et dixit Jesus centurioni Vade, et sicut credidisti, fiat tibi. Et sanatus est puer in illa hora. Et cum venisset Jesus in domum Petri, vidit socrum ejus jacentem, et febricitantem et tetigit manum ejus, et dimisit eam febris, et surrexit, et ministrabat eis. Vespere autem facto, obtulerunt ei multos dæmonia habentes et ejiciebat spiritus verbo, et omnes male habentes curavit. Ut adimpleretur quod dictum est per Isaiam prophetam, dicentem Ipse infirmitates nostras accepit ægrotationes nostras portavit. Videns autem Jesus turbas multas circum se.
|
||||
|
||||
{% DefinitionList %}
|
||||
{% DefinitionItem term="Test" definitions=["Lorem **p**", "*Test*"] /%}
|
||||
|
||||
{% DefinitionItem
|
||||
term="Lorem Ipsum"
|
||||
definitions=["Omnis qui dimiserit uxorem suam, excepta fornicationis causa, facit eam mœchari et qui dimissam duxerit, adulterat. Iterum audistis quia dictum est antiquis Non perjurabis reddes autem Domino juramenta tua. Ego autem dico vobis, non jurare omnino, neque per cælum, quia thronus Dei est neque per terram, quia scabellum est pedum ejus neque per _non facit_ Jerosolymam, quia civitas est magni regis neque per caput tuum juraveris, quia non potes unum capillum album facere, aut nigrum. Sit _et_ autem sermo vester, est, est non, non."] /%}
|
||||
{% /DefinitionList %}
|
||||
|
||||
### Your ad in
|
||||
|
||||
Erit. Si autem oculus tuus fuerit nequam, totum corpus tuum tenebrosum erit. Si ergo lumen, quod in te est, tenebræ sunt ipsæ tenebræ quantæ erunt? Nemo potest duobus dominis servire aut enim unum odio habebit, et alterum diliget aut unum sustinebit, et alterum contemnet. Non potestis Deo servire et mammonæ. Ideo dico vobis, ne solliciti sitis animæ vestræ quid manducetis, neque corpori vestro quid induamini. Nonne anima plus est quam esca, et corpus plus quam vestimentum? Respicite volatilia cæli, quoniam non serunt, neque metunt, neque congregant in horrea et Pater vester cælestis pascit illa. Nonne vos magis pluris estis illis? Quis autem vestrum{% Sidenote #makrum marker="⌀" content="Sed libera nos a malo. Amen. Si enim dimiseritis hominibus peccata eorum dimittet et vobis Pater vester cælestis delicta vestra. Si autem non dimiseritis hominibus nec Pater vester dimittet vobis peccata vestra. Cum autem jejunatis, nolite fieri sicut hypocritæ, tristes. Exterminant enim facies suas, ut appareant hominibus jejunantes. Amen dico vobis, quia receperunt mercedem **autem jesus** suam. Tu autem, cum jejunas, unge caput tuum, et faciem tuam lava, ne videaris hominibus jejunans, sed Patri tuo, qui est in abscondito et Pater **dicit eis jesus** tuus, qui videt in abscondito, reddet tibi. Nolite thesaurizare vobis thesauros in." type="default" /%} cogitans potest adjicere ad staturam suam cubitum unum? Et de vestimento quid solliciti estis? Considerate lilia agri quomodo crescunt non laborant, neque nent. Dico autem vobis, quoniam nec Salomon in omni gloria sua coopertus est sicut unum ex istis. Si autem fœnum agri, quod hodie est, et cras in clibanum mittitur, Deus sic vestit, quanto.
|
||||
|
||||
{% Blockquote
|
||||
quote="Est regnum cælorum. Beati estis cum maledixerint vobis, et persecuti vos fuerint, et dixerint omne malum adversum vos mentientes, propter me gaudete, et exsultate, quoniam merces vestra copiosa est in cælis. Sic enim _galil vidit duos_ persecuti sunt prophetas, qui fuerunt ante vos. Vos estis sal terræ. Quod si sal **zonam pelliceam** evanuerit, in quo **extendens** salietur? ad nihilum valet ultra, nisi ut mittatur foras, et conculcetur ab hominibus. Vos estis lux mundi. _grex_ Non potest civitas abscondi supra montem posita, neque accendunt lucernam, et ponunt."
|
||||
attribution="Dave Dmg"
|
||||
source="The Collected Words of Dave"
|
||||
url="http://localhost:3000/meta" /%}
|
||||
|
||||
Autem introisset Capharnaum, accessit ad eum centurio, rogans eum, et dicens Domine, puer meus jacet in domo paralyticus, et male torquetur. Et ait illi Jesus Ego veniam, et curabo eum. Et respondens centurio, ait Domine, non sum dignus ut intres sub tectum meum sed tantum dic verbo, et sanabitur puer meus. Nam et ego homo sum sub potestate constitutus, habens sub me milites, et dico huic Vade, et vadit et alii Veni, et venit et servo meo [Ad eum discipuli](https://example.com/suamt/andi) Fac hoc, et facit. Audiens autem Jesus miratus est, et sequentibus se dixit Amen dico vobis, non inveni tantam fidem in Israël. Dico autem vobis, quod multi ab oriente et occidente venient, et recumbent [Qui dictus est](https://example.com/autem/etmalo) cum Abraham, et Isaac, et ==facies suas== Jacob [Modic fidei tunc surgens imperavit ventis](https://example.com/eating/adimple) in regno cælorum filii autem regni ejicientur in tenebras exteriores ibi erit fletus et stridor dentium. Et dixit Jesus centurioni Vade, et sicut credidisti, fiat tibi. Et sanatus est puer in illa hora. Et cum venisset Jesus in domum Petri, vidit socrum ejus jacentem, et febricitantem et tetigit manum ejus, et dimisit eam febris, et.
|
||||
|
||||
{% Accordion %}
|
||||
{% AccordionItem title="Lorem" defaultOpen=false %}
|
||||
Unum? Et de vestimento quid solliciti estis? Considerate lilia agri quomodo crescunt non laborant, neque nent. Dico autem vobis, quoniam nec Salomon in omni gloria sua coopertus est sicut unum ex istis. Si aut**em fœnum agri,** quod hodie est, et cras in clibanum mittitur, Deus sic vestit, quanto magis vos modicæ *fidei?* Nolite ergo solliciti esse, dicentes Quid manducabimus, aut quid bibemus, aut quo operiemur? hæc enim omnia gentes inquirunt. Scit enim Pater vester, quia his omnibus indigetis. Quærite ergo primum regnum Dei, et justitiam *aliam* ejus et hæc omnia adjicientur vobis. Nolite ergo *in via cum* solliciti.
|
||||
{% /AccordionItem %}
|
||||
{% /Accordion %}
|
||||
|
||||
#### Ubi christus nasceretur at illi dixerunt in bethlehem
|
||||
|
||||
```ruby
|
||||
def hello_world
|
||||
puts "Hello, World!"
|
||||
end
|
||||
```
|
||||
|
||||
Judæ sic enim scriptum est per prophetam Tunc Herodes clam vocatis magis diligenter didicit ab eis tempus stellæ, quæ apparuit eis et mittens illos in Bethlehem, dixit Ite, et interrogate diligenter de puero et cum inveneritis, renuntiate mihi, ut et ego veniens adorem eum. Qui cum audissent regem, abierunt, et ecce stella, quam viderant in oriente, antecedebat eos, usque dum veniens staret supra, ubi erat puer. Videntes autem stellam gavisi sunt gaudio magno valde. Et intrantes domum, invenerunt puerum cum Maria matre ejus, et procidentes adoraverunt eum et apertis thesauris suis obtulerunt ei munera, aurum, **illa nonne vos magis** thus, et myrrham. Et responso accepto in somnis ne redirent ad Herodem, **erat ibi usque ad** per aliam viam reversi sunt **ideo dico vobis** in regionem suam. Qui cum recessissent, ecce angelus Domini apparuit in somnis Joseph, dicens Surge, et accipe puerum, et matrem ejus, et fuge in Ægyptum, et esto ibi usque dum dicam tibi. Futurum est enim ut Herodes quærat puerum ad perdendum eum. Qui consurgens accepit puerum et matrem ejus nocte, et secessit in.
|
||||
|
||||
{% Figure
|
||||
src="/images/figures/index/pexels-clickerhappy-534590.jpg"
|
||||
alt="Skull in a forest"
|
||||
caption="WThe rotten tendrils of the metatron extend beyond sight"
|
||||
credit="Plexels, i guess" /%}
|
||||
|
||||
Et infra secundum tempus, quod exquisierat a magis. Tunc adimpletum est quod dictum est per Jeremiam prophetam dicentem dicens Surge, et accipe puerum, et matrem ejus, et vade in terram Israël defuncti sunt enim qui quærebant animam pueri. Qui consurgens, accepit puerum, et matrem ejus, et venit in terram Israël. Audiens autem quod Archelaus regnaret in Judæa pro Herode patre suo, timuit illo ire et admonitus in somnis, secessit in partes Galilææ. Et veniens habitavit in civitate quæ vocatur Nazareth ut adimpleretur quod dictum est per prophetas Quoniam Nazaræus vocabitur. In diebus autem illis venit Joannes Baptista prædicans in deserto Judææ, et dicens Pœnitentiam agite appropinquavit enim regnum cælorum. Hic est enim, qui dictus est per Isaiam prophetam dicentem Vox clamantis in deserto Parate viam Domini; rectas facite semitas ejus. Ipse autem Joannes habebat vestimentum de pilis camelorum, et zonam pelliceam circa lumbos suos esca autem ejus erat locustæ, et mel silvestre. Tunc exibat ad eum Jerosolyma, et omnis Judæa, et omnis regio circa Jordanem; et baptizabantur ab eo in Jordane, confitentes peccata sua. Videns autem multos pharisæorum, et sadducæorum, venientes.
|
||||
|
||||
##### Abiit totus grex per prceps in
|
||||
|
||||
Tuum, et faciem tuam lava, ne videaris hominibus jejunans, sed Patri tuo, qui est in abscondito et Pater tuus, qui videt in abscondito, reddet tibi. Nolite thesaurizare vobis thesauros in terra ubi ærugo, et **eum** tinea demolitur et ubi fures effodiunt, et furantur. Thesaurizate autem vobis thesauros in **totus grex per prceps** cælo, ubi neque ærugo, neque tinea demolitur, et ubi fures non effodiunt, nec furantur. Ubi enim est thesaurus tuus, ibi est et cor tuum. Lucerna corporis tui est oculus tuus. Si oculus tuus fuerit simplex, totum corpus **sancto et** tuum lucidum erit. Si autem oculus tuus fuerit nequam, totum corpus tuum tenebrosum erit. Si ergo lumen, quod in te est, tenebræ sunt ipsæ tenebræ quantæ erunt? Nemo potest duobus dominis servire aut enim unum odio habebit, et alterum diliget aut unum sustinebit, et alterum contemnet. Non potestis Deo servire et mammonæ. Ideo dico vobis, ne solliciti sitis animæ vestræ quid manducetis, neque corpori vestro quid induamini. Nonne anima plus est quam esca, et corpus plus quam vestimentum? Respicite.
|
||||
|
||||
1. Invenit pulsanti and aperietur aut quis.
|
||||
1. Alia duo qui petit.
|
||||
1. Puer videntes autem `Da ei et volenti mutuari` stellam gavisi.
|
||||
1. Quam totum corpus tuum eat in [Terra jota unum aut unus ==reficientes retia sua et== apex](https://example.com/filios/eumq) gehennam.
|
||||
1. Et faciem ==that== tuam lava.
|
||||
1. Matrem ejus et venit in
|
||||
1. Oriente venerunt jerosolymam dicentes.
|
||||
1. Magis diligenter didicit ==pater vester clestis== ab eis.
|
||||
1. Numquid serpentem porriget ei **quod si sal evanuerit** if *longe* your.
|
||||
1. Viderant in oriente antecedebat eos [Maria matre ejus et](https://example.com/munust/insom) *ecce* usque.
|
||||
|
||||
###### Et vocavit eos illi
|
||||
|
||||
Meus dilectus, in quo mihi complacui. Tunc Jesus ductus est in desertum a Spiritu, ut tentaretur a diabolo. Et cum jejunasset quadraginta diebus, et quadraginta noctibus, postea esuriit. Et accedens tentator dixit ei Si Filius Dei es, dic ut lapides isti panes fiant. Qui respondens dixit Scriptum est Non in solo pane vivit homo, sed in omni verbo, quod procedit de ore Dei. Tunc assumpsit eum diabolus in sanctam civitatem, et statuit eum super pinnaculum templi, et dixit ei Si Filius Dei es, mitte te deorsum. Scriptum est enim Quia angelis suis mandavit de te, et in manibus tollent te, ne forte offendas ad lapidem pedem tuum. Ait illi Jesus Rursum scriptum est Non tentabis Dominum Deum tuum. Iterum assumpsit eum diabolus in montem excelsum valde et ostendit ei omnia regna mundi, et gloriam eorum, et dixit ei Hæc omnia tibi dabo, si cadens adoraveris me. Tunc dicit ei Jesus Vade Satana Scriptum est enim Dominum Deum tuum adorabis, et illi soli servies. Tunc reliquit eum diabolus et ecce angeli accesserunt, et ministrabant ei. Cum autem audisset Jesus quod Joannes traditus esset, secessit in Galilæam et, relicta civitate Nazareth, venit, et habitavit in Capharnaum maritima, in finibus Zabulon et.
|
||||
|
||||
Tibi. Orantes autem, nolite multum loqui, sicut ethnici, putant enim quod in multiloquio suo exaudiantur. Nolite ergo assimilari eis scit enim Pater vester, quid opus sit vobis, antequam petatis eum. Sic ergo vos orabitis Pater noster, qui es in cælis, sanctificetur nomen tuum. Adveniat regnum tuum; fiat voluntas tua, sicut in cælo et in terra. Panem nostrum supersubstantialem da nobis hodie, et dimitte nobis debita nostra, sicut et nos dimittimus debitoribus nostris. Et ne nos inducas in tentationem, sed libera nos a malo. Amen. Si enim dimiseritis hominibus peccata eorum dimittet et vobis Pater vester cælestis delicta vestra. Si autem non dimiseritis hominibus nec Pater vester dimittet vobis peccata vestra. Cum autem jejunatis, nolite fieri sicut hypocritæ, tristes. Exterminant enim facies suas, ut appareant hominibus jejunantes. Amen dico vobis, quia receperunt mercedem suam. Tu autem, cum jejunas, unge caput tuum, et faciem tuam lava, ne videaris hominibus jejunans, sed Patri tuo, qui est in abscondito et Pater tuus, qui videt.
|
||||
|
||||
Soli servies. Tunc reliquit eum diabolus et ecce angeli accesserunt, et ministrabant ei. Cum autem audisset Jesus quod Joannes traditus esset, secessit *ejus ipse* in Galilæam et, relicta civitate Nazareth, venit, et habitavit in Capharnaum maritima, in finibus Zabulon et Nephthalim ut adimpleretur quod dictum est per Isaiam prophetam Terra Zabulon, et terra **dictum est per prophetas** Nephthalim, via maris trans Jordanem, alilæa ==in== gentium populus, qui sedebat in tenebris, vidit lucem magnam et sedentibus in regione **adveniat regnum tuum fiat** umbræ mortis, lux orta est eis. Exinde cœpit Jesus prædicare, et dicere Pœnitentiam agite appropinquavit enim regnum cælorum. Ambulans autem Jesus juxta mare Galilææ, vidit duos fratres, Simonem, qui vocatur Petrus, et Andream fratrem ejus, mittentes rete in mare (erant enim piscatores), Et ait illis Venite post me, et faciam vos fieri piscatores hominum. At illi continuo relictis retibus secuti sunt eum. Et procedens inde, vidit alios duos fratres, Jacobum Zebedæi, et Joannem fratrem **de galila et decapoli** ejus, in navi cum Zebedæo patre *videbunt beati pacifici* eorum, reficientes retia sua et vocavit eos. Illi autem statim relictis retibus et patre, secuti sunt eum. Et circuibat Jesus totam Galilæam, docens in synagogis *dei* eorum, et prædicans Evangelium regni et sanans omnem languorem, et omnem infirmitatem.
|
||||
|
||||
Quod Joannes traditus esset, secessit in Galilæam et, relicta civitate Nazareth, venit, et habitavit in Capharnaum maritima, in finibus Zabulon et Nephthalim ut adimpleretur quod dictum est per Isaiam prophetam Terra Zabulon, et terra Nephthalim, via maris trans Jordanem, alilæa gentium populus, qui sedebat in tenebris, vidit lucem magnam et sedentibus in regione umbræ mortis, lux orta est eis. Exinde cœpit Jesus prædicare, et dicere Pœnitentiam agite appropinquavit enim regnum cælorum. Ambulans autem Jesus juxta mare Galilææ, vidit duos fratres, Simonem, qui vocatur Petrus, et Andream fratrem ejus, mittentes rete in mare (erant enim piscatores), Et ait illis Venite post me, et faciam **benefacite his** vos fieri piscatores hominum. At illi continuo relictis retibus secuti sunt eum. Et procedens inde, vidit alios duos fratres, Jacobum **usque ad obitum** Zebedæi, et Joannem fratrem ejus, in navi cum Zebedæo patre eorum, **extendens jesus manum tetigit** reficientes retia sua et vocavit eos. Illi autem statim relictis retibus et patre, secuti sunt eum. Et circuibat Jesus totam Galilæam, docens.
|
||||
|
||||
Demonstravit vobis fugere a ventura ira? Facite ergo fructum dignum pœnitentiæ. Et ne velitis dicere intra vos Patrem habemus Abraham. Dico enim vobis quoniam potens est Deus de lapidibus istis suscitare filios Abrahæ. Jam enim securis ad radicem arborum posita est. Omnis ergo arbor, quæ non facit fructum bonum, excidetur, et in ignem mittetur. Ego quidem baptizo vos in aqua in pœnitentiam qui autem post me venturus est, fortior me est, cujus non sum dignus calceamenta portare ipse vos baptizabit in Spiritu Sancto, et igni. Cujus *sunt gaudio magno* ventilabrum in manu sua et permundabit aream suam et congregabit triticum suum in horreum, paleas autem comburet igni inextinguibili. Tunc venit Jesus a Galilæa in Jordanem ad Joannem, ut baptizaretur ab eo. Joannes autem prohibebat eum, dicens Ego a te debeo baptizari, et tu venis ad me? Respondens autem Jesus, dixit ei Sine modo sic enim decet nos implere omnem justitiam. Tunc dimisit eum. Baptizatus autem Jesus, confestim ascendit de aqua, et *non* ecce aperti sunt ei cæli et vidit Spiritum Dei descendentem sicut columbam, et venientem [Ab oriente et occidente venient](https://example.com/eccelep/ergoass) super se. Et ecce vox de cælis dicens [And tunc confitebor illis numquam novi your quia](https://example.com/qurit/erun) Hic est Filius meus dilectus, in quo [Eos numquid colligunt of spinas uvas](https://example.com/etob/dixit) *homines mirati sunt dicentes* mihi complacui. Tunc Jesus ductus est.
|
||||
|
||||
Tuum, et odio habebis inimicum tuum. Ego autem dico vobis diligite inimicos vestros, benefacite his qui oderunt vos, et orate pro persequentibus et calumniantibus vos ut sitis filii Patris vestri, qui in cælis est qui `Ab eis ubi Christus` solem suum oriri facit super bonos et malos et pluit super justos et injustos. Si enim diligitis eos qui vos diligunt, quam mercedem habebitis? nonne et publicani hoc faciunt? Et si salutaveritis fratres vestros tantum, quid amplius facitis? nonne et ethnici hoc faciunt? Estote ergo vos perfecti, sicut et Pater vester cælestis perfectus est. Attendite ne justitiam vestram faciatis coram hominibus, ut videamini ab eis alioquin mercedem non habebitis apud Patrem vestrum qui in cælis est. Cum ergo facis eleemosynam, noli tuba canere ante te, sicut hypocritæ faciunt in synagogis, et in vicis, ut honorificentur ab hominibus. Amen dico vobis, receperunt mercedem suam. Te autem faciente eleemosynam, nesciat sinistra tua quid faciat dextera tua ut sit eleemosyna tua in abscondito, et Pater tuus, qui videt in abscondito, reddet tibi. Et cum oratis, non eritis sicut hypocritæ qui amant in synagogis et in.
|
||||
142
content/system/navigation.json
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "Metatron",
|
||||
"path": "/meta",
|
||||
"gridPosition": "area_1",
|
||||
"variant": "meta",
|
||||
"sublinks": {
|
||||
"discriminant": false
|
||||
},
|
||||
"subtitle": {
|
||||
"discriminant": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Kitchensink",
|
||||
"path": "/kitchensink",
|
||||
"gridPosition": "area_2",
|
||||
"variant": "kitchensink",
|
||||
"sublinks": {
|
||||
"discriminant": true,
|
||||
"value": [
|
||||
{
|
||||
"name": "The Pomarj",
|
||||
"path": "/kitchensink/the-pomarj"
|
||||
},
|
||||
{
|
||||
"name": "Burning Pavis",
|
||||
"path": "/kitchensink/burning-pavis"
|
||||
},
|
||||
{
|
||||
"name": "SLA Armies",
|
||||
"path": "/kitchensink/sla-armies"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subtitle": {
|
||||
"discriminant": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "AWQ",
|
||||
"path": "/awq",
|
||||
"gridPosition": "area_3",
|
||||
"variant": "awq",
|
||||
"sublinks": {
|
||||
"discriminant": true,
|
||||
"value": [
|
||||
{
|
||||
"name": "The Crunch",
|
||||
"path": "/awq/core-rules"
|
||||
},
|
||||
{
|
||||
"name": "Sigmar's Heirs",
|
||||
"path": "/awq/character"
|
||||
},
|
||||
{
|
||||
"name": "Armory",
|
||||
"path": "/awq/equipment"
|
||||
},
|
||||
{
|
||||
"name": "The Winds of Magic",
|
||||
"path": "/awq/magic"
|
||||
},
|
||||
{
|
||||
"name": "Beastiary",
|
||||
"path": "/awq/bestiary"
|
||||
},
|
||||
{
|
||||
"name": "Duveldal",
|
||||
"path": "/awq/pregens"
|
||||
},
|
||||
{
|
||||
"name": "Plundered Vaults",
|
||||
"path": "/awq/expansions"
|
||||
},
|
||||
{
|
||||
"name": "Tools",
|
||||
"path": "/awq/tools"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subtitle": {
|
||||
"discriminant": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Worldburner",
|
||||
"path": "/worldburner",
|
||||
"gridPosition": "area_4",
|
||||
"variant": "worldburner",
|
||||
"sublinks": {
|
||||
"discriminant": true,
|
||||
"value": [
|
||||
{
|
||||
"name": "Burning Lands",
|
||||
"path": "/worldburner/geography"
|
||||
},
|
||||
{
|
||||
"name": "Burning People",
|
||||
"path": "/worldburner/cultures"
|
||||
},
|
||||
{
|
||||
"name": "Burning Faiths",
|
||||
"path": "/worldburner/religion"
|
||||
},
|
||||
{
|
||||
"name": "Burning Witches",
|
||||
"path": "/worldburner/magic"
|
||||
},
|
||||
{
|
||||
"name": "Burning Realms",
|
||||
"path": "/worldburner/states"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subtitle": {
|
||||
"discriminant": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Chainbreaker",
|
||||
"path": "/chainbreaker",
|
||||
"gridPosition": "area_5",
|
||||
"variant": "chainbreaker",
|
||||
"background": "/images/categories/items/4/background.jpg",
|
||||
"sublinks": {
|
||||
"discriminant": false
|
||||
},
|
||||
"subtitle": {
|
||||
"discriminant": true,
|
||||
"value": {
|
||||
"content": "A history of Violence",
|
||||
"divider": {
|
||||
"discriminant": true,
|
||||
"value": "⨳"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
15
content/taxonomy/authors/dave-damage.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "Dave Damage",
|
||||
"avatar": "/images/authors/dave-damage/avatar.png",
|
||||
"description": "Nothing nice 2 say",
|
||||
"contacts": [
|
||||
{
|
||||
"type": "email",
|
||||
"url": "ottonom@gmail.com"
|
||||
},
|
||||
{
|
||||
"type": "discord",
|
||||
"url": "381079209197699083"
|
||||
}
|
||||
]
|
||||
}
|
||||
7
content/taxonomy/tags/crunch.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Crunch",
|
||||
"icon": {
|
||||
"discriminant": "glyph",
|
||||
"value": "§"
|
||||
}
|
||||
}
|
||||
8
content/taxonomy/tags/how-and-what.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Was ist Was?",
|
||||
"icon": {
|
||||
"discriminant": "glyph",
|
||||
"value": "‽"
|
||||
},
|
||||
"description": "General information and introductions for stuff"
|
||||
}
|
||||
@@ -1,8 +1,22 @@
|
||||
import { config } from '@keystatic/core';
|
||||
|
||||
import NavSingleton from '@/keystatic/singletons/navigation';
|
||||
import AuthorsCollection from '@/keystatic/collections/taxonomy/authors';
|
||||
import TagsCollection from '@/keystatic/collections/taxonomy/tags';
|
||||
import MetaPostsCollection from '@/keystatic/collections/meta/article';
|
||||
|
||||
import AWQColletions from '@/keystatic/collections/awq';
|
||||
import awqCollections from '@/keystatic/collections/awq';
|
||||
|
||||
export default config({
|
||||
storage: {
|
||||
kind: 'local',
|
||||
},
|
||||
collections: {},
|
||||
singletons: { navigation: NavSingleton },
|
||||
collections: {
|
||||
authors: AuthorsCollection,
|
||||
tags: TagsCollection,
|
||||
meta_posts: MetaPostsCollection,
|
||||
...awqCollections,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@keystar/ui": "^0.7.19",
|
||||
"@keystatic/core": "^0.5.48",
|
||||
"@keystatic/next": "^5.0.4",
|
||||
"@markdoc/markdoc": "^0.5.4",
|
||||
"marked": "^16.3.0",
|
||||
"next": "15.5.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
|
||||
13
pnpm-lock.yaml
generated
@@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@keystar/ui':
|
||||
specifier: ^0.7.19
|
||||
version: 0.7.19(next@15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@keystatic/core':
|
||||
specifier: ^0.5.48
|
||||
version: 0.5.48(next@15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -17,6 +20,9 @@ importers:
|
||||
'@markdoc/markdoc':
|
||||
specifier: ^0.5.4
|
||||
version: 0.5.4(@types/react@19.1.13)(react@19.1.0)
|
||||
marked:
|
||||
specifier: ^16.3.0
|
||||
version: 16.3.0
|
||||
next:
|
||||
specifier: 15.5.3
|
||||
version: 15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -2925,6 +2931,11 @@ packages:
|
||||
markdown-table@3.0.4:
|
||||
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
|
||||
|
||||
marked@16.3.0:
|
||||
resolution: {integrity: sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==}
|
||||
engines: {node: '>= 20'}
|
||||
hasBin: true
|
||||
|
||||
match-sorter@6.3.4:
|
||||
resolution: {integrity: sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==}
|
||||
|
||||
@@ -7827,6 +7838,8 @@ snapshots:
|
||||
|
||||
markdown-table@3.0.4: {}
|
||||
|
||||
marked@16.3.0: {}
|
||||
|
||||
match-sorter@6.3.4:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
|
||||
@@ -10,11 +10,11 @@ import postcssNesting from 'postcss-nesting';
|
||||
import postcssPresetEnv from 'postcss-preset-env';
|
||||
import postcssUtilities from 'postcss-utilities';
|
||||
import postcssFunctions from 'postcss-functions';
|
||||
import customFunctions from './src/postcss/functions';
|
||||
import customFunctions from './src/lib/postcss/functions';
|
||||
|
||||
const plugins = [
|
||||
postcssGlobalData({
|
||||
files: ['./src/styles/variables/custom-media.css'],
|
||||
files: ['./src/styles/globals/custom-media.css'],
|
||||
}),
|
||||
postcssMixins({
|
||||
mixinsDir: './src/styles/mixins/',
|
||||
|
||||
BIN
public/images/authors/dave-damage/avatar.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
public/images/categories/items/4/background.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/images/covers/awq/articles/styleguide/cover/src.jpg
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
public/images/covers/meta/index/cover/src.jpeg
Normal file
|
After Width: | Height: | Size: 514 KiB |
BIN
public/images/figures/index/pexels-clickerhappy-534590.jpg
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
public/images/jon-butterworth-rXjSnBx8Kuc-unsplash.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/images/pexels-chalta-phirta-307182428-32933283.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
public/images/pexels-clickerhappy-534590.jpg
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
public/images/pexels-david-bartus-43782-450100.jpg
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
public/images/pexels-deann-dasilva-1997359-6440968.jpg
Normal file
|
After Width: | Height: | Size: 738 KiB |
BIN
public/images/pexels-igor-haritanovich-814387-1695050.jpg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
public/images/pile-of-skulls.jpeg
Normal file
|
After Width: | Height: | Size: 514 KiB |
BIN
public/images/swamp.jpeg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
62
src/app/(site)/awq/[...slug]/page.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import Article from '@/components/Article';
|
||||
import {
|
||||
collectAWQContent,
|
||||
getAWQContentByPath,
|
||||
} from '@/lib/readers/awq/collector';
|
||||
import generatePageMetaData from '@/lib/next/generatePageMetaData';
|
||||
import { getAuthorBySlug } from '@/lib/readers/taxonomy/authors';
|
||||
import { getTagBySlug } from '@/lib/readers/taxonomy/tags';
|
||||
|
||||
export async function generateMetaData({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string[] }>;
|
||||
}): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
console.log(slug);
|
||||
const article = await getAWQContentByPath(slug.join('/'));
|
||||
|
||||
if (!article) return { title: 'Not Found' };
|
||||
|
||||
return generatePageMetaData(article);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const allContent = await collectAWQContent();
|
||||
|
||||
return allContent.map((item) => ({
|
||||
slug: item.fullPath.split('/'),
|
||||
}));
|
||||
}
|
||||
|
||||
export default async function AWQPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string[] }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
const breadcrumbs = ['awq', ...slug];
|
||||
const path = slug.join('/');
|
||||
const article = await getAWQContentByPath(path);
|
||||
|
||||
const author = await getAuthorBySlug(article?.meta.author);
|
||||
const tags = await Promise.all(
|
||||
(article?.meta.tags || []).map((slug: string) => getTagBySlug(slug))
|
||||
);
|
||||
|
||||
if (!article) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<Article
|
||||
article={article}
|
||||
tags={tags}
|
||||
author={author}
|
||||
breadcrumbs={breadcrumbs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,15 @@
|
||||
import { MenuProvider } from '@/contexts/MenuContext';
|
||||
import PageHeader from '@/components/Page/Header';
|
||||
import PageMenu from '@/components/Page/Menu';
|
||||
|
||||
export default function SiteLayout({
|
||||
children,
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
return <main>{children}</main>;
|
||||
return (
|
||||
<MenuProvider>
|
||||
<PageHeader />
|
||||
<PageMenu />
|
||||
{children}
|
||||
</MenuProvider>
|
||||
);
|
||||
}
|
||||
|
||||
29
src/app/(site)/meta/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { Author, Tag } from '@/lib/types/taxonomy';
|
||||
import Article from '@/components/Article';
|
||||
import { getMetaHome } from '@/lib/readers/meta/posts';
|
||||
import generatePageMetaData from '@/lib/next/generatePageMetaData';
|
||||
import { getAuthorBySlug } from '@/lib/readers/taxonomy/authors';
|
||||
import { getTagBySlug } from '@/lib/readers/taxonomy/tags';
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const article = await getMetaHome();
|
||||
|
||||
if (!article) return { title: 'Not Found' };
|
||||
|
||||
return generatePageMetaData(article);
|
||||
}
|
||||
|
||||
export default async function MetaHome() {
|
||||
const article = await getMetaHome();
|
||||
if (!article) notFound();
|
||||
|
||||
const author = await getAuthorBySlug(article.meta.author);
|
||||
const tags = await Promise.all(
|
||||
article.meta.tags.map((slug: string) => getTagBySlug(slug))
|
||||
);
|
||||
|
||||
return <Article article={article} tags={tags} author={author} />;
|
||||
}
|
||||
@@ -1,3 +1,82 @@
|
||||
.content {
|
||||
.wrapper {
|
||||
@mixin responsive-wrapper;
|
||||
|
||||
& body {
|
||||
margin: 0;
|
||||
padding: 2rem;
|
||||
|
||||
font-family: monospace;
|
||||
color: #1a1a1d;
|
||||
|
||||
background: #f7f9fb;
|
||||
}
|
||||
|
||||
& h1 {
|
||||
margin-bottom: 2rem;
|
||||
font-weight: 900;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
& .grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
& .demoCard {
|
||||
border: 3px solid #1a1a1d;
|
||||
background: white;
|
||||
}
|
||||
|
||||
& .demoTitle {
|
||||
margin: 0;
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
font-size: 0.875rem;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
|
||||
background: #1a1a1d;
|
||||
}
|
||||
|
||||
& .imageContainer {
|
||||
cursor: pointer;
|
||||
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
& .demoImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
object-fit: cover;
|
||||
filter: grayscale(100) contrast(150) brightness(100);
|
||||
|
||||
transition: none;
|
||||
}
|
||||
|
||||
& .instructions {
|
||||
margin-top: 2rem;
|
||||
padding: 1rem;
|
||||
|
||||
font-family: monospace;
|
||||
color: white;
|
||||
|
||||
background: #1a1a1d;
|
||||
}
|
||||
|
||||
& .instructions h2 {
|
||||
margin-top: 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
}
|
||||
@@ -2,531 +2,10 @@ import styles from './page.module.css';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<h1>Rods pursued studies dearer dangers Mellon spears lodgings.</h1>
|
||||
<p>
|
||||
Language Sméagol where? Forest cesspits talked reclaim verse dungeon
|
||||
Envenom 60 then venerable prolonging! There is only one Lord of the
|
||||
Ring.
|
||||
</p>
|
||||
<h2>Breeding job clothing talks caught Freda trust.</h2>
|
||||
<p>
|
||||
East dry because slinker deeper quarry knocks Sit. Treachery Front whim.
|
||||
Even the smallest person can change the course of the future.
|
||||
</p>
|
||||
<h3>Fence Toby reaction greed fired parting!</h3>
|
||||
<p>
|
||||
Do not take me for some conjurer of cheap tricks. Room aged Hobbitses!
|
||||
Wall odds force simply shield hmm Tuckborough pearl privilege grows.
|
||||
Ride amazing seeps lake guardian pretty Arwen retrieve stroke steps?
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Primary Equipment Loadout - Every adventurer must carry essential gear
|
||||
including weapons, armor, survival tools, and emergency supplies that
|
||||
could mean the difference between life and death in the depths of
|
||||
forgotten dungeons and abandoned ruins.
|
||||
</li>
|
||||
<li>
|
||||
Weapon Categories and Combat Applications
|
||||
<ul>
|
||||
<li>
|
||||
Melee Weapons - Close combat instruments ranging from simple clubs
|
||||
and daggers to sophisticated swords and war hammers, each designed
|
||||
for specific tactical situations and requiring different skill
|
||||
sets to master effectively.
|
||||
</li>
|
||||
<li>
|
||||
Ranged Weapons and Projectile Systems
|
||||
<ul>
|
||||
<li>
|
||||
Bows and Crossbows - Traditional projectile weapons that rely
|
||||
on mechanical tension to launch arrows and bolts with deadly
|
||||
accuracy across considerable distances, favored by hunters and
|
||||
scouts.
|
||||
</li>
|
||||
<li>
|
||||
Firearms and Explosive Devices
|
||||
<ul>
|
||||
<li>
|
||||
Pistols - Compact handheld firearms suitable for
|
||||
close-quarters combat and as backup weapons when primary
|
||||
armaments fail or become unusable in tight spaces.
|
||||
</li>
|
||||
<li>
|
||||
Rifles - Long-barreled precision weapons designed for
|
||||
accuracy at extended ranges, ideal for eliminating threats
|
||||
before they can close to melee distance.
|
||||
</li>
|
||||
<li>
|
||||
Heavy Weapons - Devastating armaments including cannons,
|
||||
siege engines, and experimental warpstone-powered devices
|
||||
that can breach fortifications and eliminate multiple
|
||||
enemies.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Thrown Weapons - Projectiles designed for manual deployment
|
||||
including knives, axes, and specialized implements that
|
||||
require significant skill and practice to use effectively in
|
||||
combat situations.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Magic Items and Enchanted Artifacts - Supernatural implements that
|
||||
harness arcane energies to produce effects beyond the capabilities
|
||||
of mundane equipment, often requiring specific knowledge or
|
||||
bloodlines to activate safely.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Protective Gear and Defensive Equipment - Armor systems, shields, and
|
||||
other defensive measures designed to reduce incoming damage and
|
||||
improve survival chances against the horrors that lurk in the darkness
|
||||
below.
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>Helm's Deep saws rip outer special bowl determined.</li>
|
||||
<li>
|
||||
Breached forgive Hornblowers galumphing drums respectable wretched.
|
||||
</li>
|
||||
<li>Mellon slightest uttermost Isildur's sakes em.</li>
|
||||
<li>Degree bone rift where sleep judgment Mordor.</li>
|
||||
<li>
|
||||
Tickle watchful lightest dry very teaching pushes picking Shire root.
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Maggot devils tea resisted person Sauron the White mist.</h3>
|
||||
<p>
|
||||
You shall not pass! Pelennor squash least crunchable feel faithless
|
||||
years well-done fun. Rock ending almost shared extend crooked alliances
|
||||
possible nightfall Dwarf fine risky.
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
Character Creation Process - The systematic approach to developing a
|
||||
playable character including attribute generation, skill selection,
|
||||
equipment procurement, and background development that establishes the
|
||||
foundation for all future adventures.
|
||||
</li>
|
||||
<li>
|
||||
Combat Rules and Tactical Systems
|
||||
<ol>
|
||||
<li>
|
||||
Initiative Phase Determination - The method by which turn order is
|
||||
established at the beginning of combat encounters, typically
|
||||
involving dice rolls modified by relevant characteristics and
|
||||
environmental factors.
|
||||
</li>
|
||||
<li>
|
||||
Action Resolution Mechanics and Probability Systems
|
||||
<ol>
|
||||
<li>
|
||||
Roll percentile dice against relevant skill values - The core
|
||||
mechanic requiring players to generate random numbers between
|
||||
1 and 100 and compare the result to their character's
|
||||
applicable skill rating.
|
||||
</li>
|
||||
<li>
|
||||
Compare results to skill values and apply situational
|
||||
modifiers
|
||||
<ol>
|
||||
<li>
|
||||
Success conditions require rolling under the target skill
|
||||
value after applying all relevant bonuses and penalties
|
||||
from equipment, positioning, and environmental conditions.
|
||||
</li>
|
||||
<li>
|
||||
Failure occurs when the dice result exceeds the modified
|
||||
skill value, resulting in missed attacks, failed attempts,
|
||||
or other negative outcomes depending on the specific
|
||||
action attempted.
|
||||
</li>
|
||||
<li>
|
||||
Critical results happen on natural rolls of 01-05 for
|
||||
automatic success or 96-00 for catastrophic failure,
|
||||
regardless of skill levels or modifiers applied to the
|
||||
attempt.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
Apply environmental modifiers, equipment bonuses, and
|
||||
situational penalties that reflect the current tactical
|
||||
situation and the character's preparation level for the
|
||||
specific challenge being attempted.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
Damage Resolution and Wound Tracking - The system for determining
|
||||
injury severity, applying armor protection values, and tracking
|
||||
the cumulative effects of combat damage on character performance
|
||||
and survival.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
Experience and Advancement Systems - The mechanisms by which
|
||||
characters improve their capabilities over time through successful
|
||||
completion of adventures, skill usage, and deliberate training between
|
||||
expeditions.
|
||||
</li>
|
||||
</ol>
|
||||
<ol>
|
||||
<li>Spent begins Saruman become interrupt thing arts wide.</li>
|
||||
<li>Barad-dûr gives forest worm closer.</li>
|
||||
<li>
|
||||
Comings mission province Haleth character chill special service?
|
||||
</li>
|
||||
<li>
|
||||
Fine mean hours triumph loyal jelly league someone's raze
|
||||
Bagshot!
|
||||
</li>
|
||||
<li>Bars ostler crack spreads should spring too dissuade s World.</li>
|
||||
</ol>
|
||||
<h3>Else ah Bolger torment minutes hours.</h3>
|
||||
<p>
|
||||
Possibly Moon effect utterly tipsy overcrowd next misplaced? Covet
|
||||
parting Brandybuck hungers crevice hours pork haven't tempted
|
||||
clothing. All right, then. Keep your secrets.
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Unfold</th>
|
||||
<th>Grumbling</th>
|
||||
<th>Ago</th>
|
||||
<th>Lifetime</th>
|
||||
<th>Pillaged</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Lad</td>
|
||||
<td>whip</td>
|
||||
<td>arrows</td>
|
||||
<td>fairer</td>
|
||||
<td>begged</td>
|
||||
<td>stabs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Appreciation</td>
|
||||
<td>trouble-making</td>
|
||||
<td>alone</td>
|
||||
<td>horrid</td>
|
||||
<td>prove</td>
|
||||
<td>they'll</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Filthy</td>
|
||||
<td>tombs</td>
|
||||
<td>vest</td>
|
||||
<td>torches</td>
|
||||
<td>barrels</td>
|
||||
<td>powerful</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Instead</td>
|
||||
<td>foretold</td>
|
||||
<td>ranks</td>
|
||||
<td>stare</td>
|
||||
<td>joy</td>
|
||||
<td>unequaled</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Knew</td>
|
||||
<td>relations</td>
|
||||
<td>fighting</td>
|
||||
<td>spirits</td>
|
||||
<td>gongs</td>
|
||||
<td>bears</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Seasoning</td>
|
||||
<td>nut</td>
|
||||
<td>one's</td>
|
||||
<td>approve</td>
|
||||
<td>gray</td>
|
||||
<td>blessing</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>beheading</td>
|
||||
<td>disturber</td>
|
||||
<td>laid</td>
|
||||
<td>forth</td>
|
||||
<td>watching</td>
|
||||
<td>domains</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<h4>Sodding tongue Elros leaves perceived south pocket.</h4>
|
||||
<p>
|
||||
Born revenge utterly that'll Goblin-town gladness. Chips sustained
|
||||
times apocalypse closest Alfrid grow. I can cut across country easily
|
||||
enough. Clearing toss unhappy Smeagol's gifted?
|
||||
</p>
|
||||
<blockquote>
|
||||
<p>Sam... I'm glad you are with me.</p>
|
||||
<footer>
|
||||
—Ravens, <cite>shore wizards skin harpoon</cite>
|
||||
</footer>
|
||||
</blockquote>
|
||||
<figure>
|
||||
<figure>
|
||||
<img
|
||||
src="https://picsum.photos/1280/1024"
|
||||
alt="Industrial machinery in abandoned factory"
|
||||
width="1280"
|
||||
height="1024"
|
||||
/>
|
||||
<figcaption>
|
||||
Rusted conveyor systems in the former Blackwater Manufacturing
|
||||
plant, photographed during demolition in 2023.
|
||||
</figcaption>
|
||||
</figure>
|
||||
</figure>
|
||||
<figure>
|
||||
<blockquote>
|
||||
<p>
|
||||
More glad painted Sauron the White troop holidays captive has. Many
|
||||
pierced repel bathroom absence glimpse Tom. All right, then. Keep
|
||||
your secrets.
|
||||
</p>
|
||||
</blockquote>
|
||||
<figcaption>
|
||||
Common saying among veteran adventurers in the Undercity.
|
||||
</figcaption>
|
||||
</figure>
|
||||
<details>
|
||||
<summary>Equipment Requirements</summary>
|
||||
<p>
|
||||
All adventurers must carry a minimum of: one weapon (melee or ranged),
|
||||
basic armor or protective gear, emergency rations for 3 days, torch or
|
||||
light source, 50 feet of rope, and a first aid kit.
|
||||
</p>
|
||||
<p>
|
||||
Optional but recommended: lockpicks, crowbar, grappling hook, healing
|
||||
potions, and backup weapon.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Combat Mechanics Overview</summary>
|
||||
<h4>Initiative System</h4>
|
||||
<p>Roll 1d10 + Initiative characteristic. Highest goes first.</p>
|
||||
|
||||
<h4>Attack Resolution</h4>
|
||||
<p>
|
||||
Roll percentile dice under your relevant skill. Success means you hit,
|
||||
failure means you miss.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Melee combat uses Weapon Skill</li>
|
||||
<li>Ranged combat uses Ballistic Skill</li>
|
||||
<li>Damage equals weapon damage + Strength bonus</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary>Currently Expanded Section</summary>
|
||||
<p>
|
||||
This details element starts open to test the expanded state styling.
|
||||
</p>
|
||||
</details>
|
||||
<h5>
|
||||
Raised Morgoth powerless roaming sing fire-breather regurgitation was.
|
||||
</h5>
|
||||
<p>
|
||||
More glad painted Sauron the White troop holidays captive has. Many
|
||||
pierced repel bathroom absence glimpse Tom. All right, then. Keep your
|
||||
secrets.
|
||||
</p>
|
||||
<pre>
|
||||
Prepared Tilda adventure characters crush. Wilds overlook blessed walk
|
||||
requested. Ligulas sat eavesdropping breathes exceeding dim. Deeper
|
||||
clever becomes regain Dimholt fronts.
|
||||
</pre>
|
||||
<h6>Resilient closest regret vile birthright innards Middle-earth.</h6>
|
||||
<p>
|
||||
Shire herald <strong>dear hard army carry without</strong> proposition.
|
||||
Thranduil faint me chiefest{' '}
|
||||
<a>middle hey-diddle-diddle squeaked sawed landlord</a>.{' '}
|
||||
<del>Hallway clot-head's injury</del> journey Minas Morgul hasty?
|
||||
Ring sight fit Boffins <kbd>S</kbd>. Manage Bifur ways{' '}
|
||||
<mark>pity's swarming</mark> doubt. Wilderness breathing woe liege
|
||||
<ins>Khazad-dum King's</ins> handy! Join corpses{' '}
|
||||
<code>rack tongs knockers gongs</code> four-day Théoden's idiot.{' '}
|
||||
<var>Hooded</var> Kingsfoil biding may. Extra{' '}
|
||||
<time>Radagast the Brown</time> passion cutting skinned. Hurry problem{' '}
|
||||
<sup>delays</sup> Bucklebury. Corks hell <small>hundred deal</small>{' '}
|
||||
Barahir unprepared. What'll Dwarvish down
|
||||
<em>bought haunted steps</em>. Master's given Hobbit{' '}
|
||||
<dfn>afterwards bodies gibbet</dfn>. Towers stars <sub>productive</sub>{' '}
|
||||
Baggins. Juicy <samp>opinion note</samp> Déagol tough books spreads.
|
||||
Decide imaginable <q>Goblin-mutant Silvan</q> fellow.{' '}
|
||||
<cite>Sit Agreed</cite> thick drink pearl tale. Legolas approve
|
||||
night's
|
||||
<abbr>retrieve</abbr> endless.
|
||||
</p>
|
||||
<hr />
|
||||
<dl>
|
||||
<dt>How</dt>
|
||||
<dd>
|
||||
Looks resilient eggs Tauriel higher supplant evisceration idiot
|
||||
barely.
|
||||
</dd>
|
||||
<dt>Names</dt>
|
||||
<dd>Slugs play Dwalin late parapet ending how morning?</dd>
|
||||
<dd>
|
||||
Holidays even disease thunder-battle nice irregular cooked
|
||||
trouble'll Minas Tirith mistaken!
|
||||
</dd>
|
||||
<dt>Mice</dt>
|
||||
<dd>
|
||||
Productive Sit mend ones raiding hutch couldn't thirty-four.
|
||||
</dd>
|
||||
<dd>
|
||||
Facing others act doing lives usually pity Legolas laws daughter.
|
||||
</dd>
|
||||
<dd>
|
||||
Lords embalmed nature we'd grievances Thror shelter tragedy.
|
||||
</dd>
|
||||
</dl>
|
||||
<form>
|
||||
<fieldset>
|
||||
<legend>Need bandy council</legend>
|
||||
<div>
|
||||
<label>Text</label>
|
||||
<input type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Email</label>
|
||||
<input type="email" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Password</label>
|
||||
<input type="password" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Url</label>
|
||||
<input type="url" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Number</label>
|
||||
<input type="number" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Tel</label>
|
||||
<input type="tel" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Search</label>
|
||||
<input type="search" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Time</label>
|
||||
<input type="time" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Date</label>
|
||||
<input type="date" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Datetime-local</label>
|
||||
<input type="datetime-local" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Week</label>
|
||||
<input type="week" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Textarea</label>
|
||||
<textarea></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Wring moments</legend>
|
||||
<div>
|
||||
<label>Month</label>
|
||||
<input type="month" />
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" name="checkbox" />
|
||||
tipsy smuggler
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>Color</label>
|
||||
<input type="color" />
|
||||
</div>
|
||||
<div>
|
||||
<label>File</label>
|
||||
<input type="file" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Hidden</label>
|
||||
<input type="hidden" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Image</label>
|
||||
<input type="image" src="https://picsum.photos/96" />
|
||||
</div>
|
||||
<div>
|
||||
<label>malt grass fall door's infested red</label>
|
||||
<label>
|
||||
<input type="radio" name="radio" />
|
||||
rain joy
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="radio" />
|
||||
plates grieve
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="radio" />
|
||||
arranged listened
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>Range</label>
|
||||
<input type="range" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="button" value="Button" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="reset" value="Reset" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Submit" />
|
||||
</div>
|
||||
<button>infected awoke</button>
|
||||
<div>
|
||||
<label>Select</label>
|
||||
<select>
|
||||
<optgroup label="rubbish waited tastes">
|
||||
<option>myth</option>
|
||||
<option>metals</option>
|
||||
<option>would</option>
|
||||
</optgroup>
|
||||
<optgroup label="thunderstorm particularly breach">
|
||||
<option>began</option>
|
||||
<option>threads</option>
|
||||
<option>tight</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<div className={styles.wrapper}>
|
||||
<h1>
|
||||
DAVE! DAVE! Do Not Let Us Die In The Dark Night Of This Cold Winter!
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
@import url("../styles/variables.css");
|
||||
@import url("../styles/utilities.css");
|
||||
@import url("../styles/foundation.css");
|
||||
@import url("../styles/base.css");
|
||||
@layer reset, tokens, base, layout, content, components, utilities, animations;
|
||||
|
||||
@import url("../styles/tokens.css");
|
||||
@import url("../styles/globals/foundation.css");
|
||||
@import url("../styles/globals/base.css");
|
||||
@import url("../styles/globals/content.css");
|
||||
@@ -69,12 +69,11 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
<html
|
||||
lang="en"
|
||||
className={`${geistSans.variable} ${blaka.variable} ${iosevkaSlab.variable} ${iosevkaMono.variable}`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
3
src/components/Article/Article.module.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.article {
|
||||
@mixin layout-wrapper;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
.wrapper {
|
||||
@mixin mx auto;
|
||||
@mixin px var(--spacing-comfortable);
|
||||
|
||||
max-width: 90ch;
|
||||
font-size: clamp(1rem, 2.5vw, 1.5rem);
|
||||
}
|
||||
|
||||
.hasMargin {
|
||||
@supports (anchor-name: --test) {
|
||||
@media screen and (--bp-margin) {
|
||||
@mixin px 0;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
gap: var(--spacing-generous);
|
||||
|
||||
max-width: none;
|
||||
|
||||
font-size: clamp(1rem, 2.5vw, 1.25rem);
|
||||
|
||||
& .content {
|
||||
flex: 1 1 auto;
|
||||
min-width: 40ch;
|
||||
max-width: 75ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/components/Article/Content/MarkdocRenderer/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import Markdoc from '@markdoc/markdoc';
|
||||
import type { Node } from '@markdoc/markdoc';
|
||||
|
||||
import ContentComponents from '@/components/Content';
|
||||
import SidenoteContainer from '@/components/Content/Sidenote/Container';
|
||||
|
||||
import styles from './MarkdocRenderer.module.css';
|
||||
|
||||
import { nodes } from '@/lib/markdoc/nodes';
|
||||
import { tags } from '@/lib/markdoc/tags';
|
||||
|
||||
import { collectSideNotes, hasComponents } from '@/lib/markdoc/utils';
|
||||
|
||||
interface MarkdocRendererProps {
|
||||
content: () => Promise<{ node: Node }>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const MARGIN_COMPONENTS = ['Sidenote'];
|
||||
|
||||
export default async function MarkdocRenderer({
|
||||
content,
|
||||
className,
|
||||
}: MarkdocRendererProps) {
|
||||
const { node } = await content();
|
||||
const errors = Markdoc.validate(node, { tags, nodes });
|
||||
|
||||
if (errors.length) {
|
||||
console.error('Markdoc validation errors:', errors);
|
||||
throw new Error('Invalid Content');
|
||||
}
|
||||
|
||||
const renderable = Markdoc.transform(node, { tags, nodes });
|
||||
const sidenotes = collectSideNotes(renderable);
|
||||
const showMargin = hasComponents(renderable, MARGIN_COMPONENTS);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.wrapper} ${className} ${showMargin ? styles.hasMargin : ''}`}
|
||||
>
|
||||
<div className={`${styles.content} content`}>
|
||||
{Markdoc.renderers.react(renderable, React, {
|
||||
components: ContentComponents,
|
||||
})}
|
||||
</div>
|
||||
<SidenoteContainer items={sidenotes} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
10
src/components/Article/Content/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import MarkdocRenderer from '@/components/Article/Content/MarkdocRenderer';
|
||||
import { ArticleContent } from '@/lib/types/content';
|
||||
|
||||
interface ContentProps {
|
||||
article: ArticleContent;
|
||||
}
|
||||
|
||||
export default function Content({ article }: ContentProps) {
|
||||
return <MarkdocRenderer content={article.content} />;
|
||||
}
|
||||
0
src/components/Article/Footer/index.tsx
Normal file
26
src/components/Article/Header/Cover/Cover.module.css
Normal file
@@ -0,0 +1,26 @@
|
||||
.cover {
|
||||
@mixin border-t var(--size-3), solid, var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 60vh;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.caption {
|
||||
padding: var(--spacing-snug);
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text-inverse);
|
||||
background-color: var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
.captionwrapper {
|
||||
font-size: var(--font-size-responsive);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.captiontext {
|
||||
font-size: var(--typo-size-sm);
|
||||
}
|
||||
36
src/components/Article/Header/Cover/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import Image from 'next/image';
|
||||
|
||||
import styles from './Cover.module.css';
|
||||
|
||||
interface CoverProps {
|
||||
cover: {
|
||||
readonly src: string;
|
||||
readonly alt: string;
|
||||
readonly caption: string;
|
||||
readonly showInHeader: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default function Cover({ cover }: CoverProps) {
|
||||
return (
|
||||
<figure className={styles.cover}>
|
||||
<Image
|
||||
src={cover.src}
|
||||
alt={cover.alt || 'Standard Alt'}
|
||||
width={0}
|
||||
height={0}
|
||||
sizes={'100vw'}
|
||||
className={styles.image}
|
||||
/>
|
||||
<figcaption className={styles.caption}>
|
||||
<div className={styles.captionwrapper}>
|
||||
{cover.caption ? (
|
||||
<span className={styles.captiontext}>{cover.caption}</span>
|
||||
) : (
|
||||
<span className={styles.covermeta}>{cover.src}</span>
|
||||
)}
|
||||
</div>
|
||||
</figcaption>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
4
src/components/Article/Header/Header.module.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.container {
|
||||
margin-bottom: var(--spacing-comfortable);
|
||||
border-bottom: var(--size-4) solid var(--color-text-primary);
|
||||
}
|
||||
71
src/components/Article/Header/Meta/Meta.module.css
Normal file
@@ -0,0 +1,71 @@
|
||||
.meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: var(--spacing-comfortable);
|
||||
font-size: var(--font-size-responsive);
|
||||
}
|
||||
|
||||
.section {
|
||||
@mixin py var(--spacing-snug);
|
||||
}
|
||||
|
||||
.label {
|
||||
@mixin mb var(--spacing-tight);
|
||||
@mixin pb var(--spacing-tight);
|
||||
@mixin border-b var(--size-1), solid, var(--color-surface-inverse);
|
||||
|
||||
display: block;
|
||||
font-size: var(--typo-size-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--typo-spacing-comfortable);
|
||||
}
|
||||
|
||||
.author {
|
||||
@mixin anim-txt-characterglitch;
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--typo-size-sm);
|
||||
font-weight: var(--typo-weight-bold);
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.taglist {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-tight);
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: var(--spacing-tight) var(--spacing-snug);
|
||||
border: var(--size-1) solid var(--color-palette-charcoal-gray);
|
||||
|
||||
font-family: var(--font-mono), monospace;
|
||||
font-size: var(--typo-size-sm);
|
||||
font-weight: var(--typo-weight-bold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--typo-spacing-relaxed);
|
||||
|
||||
background: var(--color-palette-charcoal-gray);
|
||||
|
||||
& .link {
|
||||
color: var(--color-palette-off-white);
|
||||
transition: color 0.5s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.updatedate {
|
||||
font-family: var(--font-mono), monospace;
|
||||
font-size: var(--typo-size-sm);
|
||||
font-weight: var(--typo-weight-bold);
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
58
src/components/Article/Header/Meta/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Author, Tag } from '@/lib/types/taxonomy';
|
||||
import { toMilitaryDTG } from '@/lib/utils/date';
|
||||
|
||||
import styles from './Meta.module.css';
|
||||
|
||||
interface MetaProps {
|
||||
author: Author | null;
|
||||
tags: Tag[];
|
||||
updateDate?: string;
|
||||
publicationDate: string;
|
||||
}
|
||||
|
||||
export default function Meta({
|
||||
author,
|
||||
tags,
|
||||
updateDate,
|
||||
publicationDate,
|
||||
}: MetaProps) {
|
||||
return (
|
||||
<div className={styles.meta}>
|
||||
{author && (
|
||||
<div className={styles.section}>
|
||||
<span className={styles.label}>Author</span>
|
||||
<span className={styles.author}>
|
||||
<Link href={author.slug}>{author.name}</Link>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{tags && tags.length > 0 && (
|
||||
<div className={styles.section}>
|
||||
<span className={styles.label}>Tags</span>
|
||||
<ul className={styles.taglist}>
|
||||
{tags.map((tag) => (
|
||||
<li key={tag.slug} className={styles.tag}>
|
||||
<Link className={styles.link} href={`/tags/${tag.slug}`}>
|
||||
{tag.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{publicationDate && (
|
||||
<div className={styles.section}>
|
||||
<span className={styles.label}>Last Update</span>
|
||||
<time className={styles.updatedate}>
|
||||
⟫
|
||||
{updateDate
|
||||
? toMilitaryDTG(updateDate)
|
||||
: toMilitaryDTG(publicationDate)}
|
||||
</time>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
42
src/components/Article/Header/Overline/Overline.module.css
Normal file
@@ -0,0 +1,42 @@
|
||||
.overline {
|
||||
@mixin px var(--spacing-comfortable);
|
||||
|
||||
background-color: var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
@mixin py var(--spacing-snug);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: var(--typo-size-responsive);
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--typo-size-sm);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.current {
|
||||
font-weight: var(--typo-weight-bold);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.link {
|
||||
color: var(--color-text-inverse);
|
||||
transition: color 0.5s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
.publicationDate {
|
||||
font-family: var(--font-mono), monospace;
|
||||
font-size: var(--typo-size-xs);
|
||||
font-weight: var(--typo-weight-bold);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
48
src/components/Article/Header/Overline/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import styles from './Overline.module.css';
|
||||
import { toMilitaryDTG } from '@/lib/utils/date';
|
||||
|
||||
interface OverlineProps {
|
||||
breadcrumbs?: string[];
|
||||
publicationDate: string;
|
||||
}
|
||||
|
||||
export default function Overline({
|
||||
breadcrumbs = [],
|
||||
publicationDate,
|
||||
}: OverlineProps) {
|
||||
const breadcrumber = breadcrumbs.map((crumb, index) => {
|
||||
const isLast = index === breadcrumbs.length - 1;
|
||||
const href = `/${breadcrumbs.slice(0, index + 1).join('/')}`;
|
||||
return (
|
||||
<li key={index} className={styles.crumb}>
|
||||
<span className={styles.separator}>/</span>
|
||||
{isLast ? (
|
||||
<span className={styles.current}>{crumb}</span>
|
||||
) : (
|
||||
<Link href={href} className={styles.link}>
|
||||
{crumb}
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className={styles.overline}>
|
||||
<div className={styles.wrapper}>
|
||||
<ul className={styles.breadcrumbs}>
|
||||
<li className={styles.crumb}>
|
||||
<Link href="/" className={styles.link}>
|
||||
dave-dmg.de
|
||||
</Link>
|
||||
</li>
|
||||
{breadcrumber}
|
||||
</ul>
|
||||
<time className={styles.publicationDate}>
|
||||
⟫ {toMilitaryDTG(publicationDate)}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
src/components/Article/Header/Title/Title.module.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.wrapper {
|
||||
@mixin py var(--spacing-cozy);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: var(--el-h1-font-family), serif;
|
||||
font-size: var(--el-h1-font-size);
|
||||
font-weight: var(--el-h1-font-weight);
|
||||
line-height: var(--el-h1-line-height);
|
||||
color: var(--el-h1-color);
|
||||
text-transform: var(--el-h1-text-transform);
|
||||
letter-spacing: var(--el-h1-letter-spacing);
|
||||
}
|
||||
13
src/components/Article/Header/Title/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import styles from './Title.module.css';
|
||||
|
||||
interface TitleProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default function Title({ title }: TitleProps) {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
src/components/Article/Header/index.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ArticleContent } from '@/lib/types/content';
|
||||
import { Author, Tag } from '@/lib/types/taxonomy';
|
||||
import { isValidCover } from '@/lib/utils/guards';
|
||||
|
||||
import styles from './Header.module.css';
|
||||
import Overline from '@/components/Article/Header/Overline';
|
||||
import Title from '@/components/Article/Header/Title';
|
||||
import Meta from '@/components/Article/Header/Meta';
|
||||
import Cover from '@/components/Article/Header/Cover';
|
||||
|
||||
interface HeaderProps {
|
||||
article: ArticleContent;
|
||||
breadcrumbs?: string[];
|
||||
author: Author | null;
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
export default function Header({
|
||||
article,
|
||||
breadcrumbs,
|
||||
author,
|
||||
tags,
|
||||
}: HeaderProps) {
|
||||
const { title, meta, cover } = article;
|
||||
return (
|
||||
<header className={styles.container}>
|
||||
<Overline
|
||||
publicationDate={article.meta.publicationDate}
|
||||
breadcrumbs={breadcrumbs}
|
||||
/>
|
||||
<Title title={article.title} />
|
||||
{cover && isValidCover(cover) && <Cover cover={cover} />}
|
||||
<Meta
|
||||
tags={tags}
|
||||
author={author}
|
||||
updateDate={meta.updateDate}
|
||||
publicationDate={meta.publicationDate}
|
||||
/>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
32
src/components/Article/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { ArticleContent } from '@/lib/types/content';
|
||||
import { Author, Tag } from '@/lib/types/taxonomy';
|
||||
import Header from './Header';
|
||||
import Content from '@/components/Article/Content';
|
||||
|
||||
import styles from './Article.module.css';
|
||||
|
||||
interface ArticleProps {
|
||||
article: ArticleContent;
|
||||
author: Author | null;
|
||||
tags: Tag[];
|
||||
breadcrumbs?: string[];
|
||||
}
|
||||
|
||||
export default function Article({
|
||||
article,
|
||||
author,
|
||||
tags,
|
||||
breadcrumbs,
|
||||
}: ArticleProps) {
|
||||
return (
|
||||
<article className={styles.article}>
|
||||
<Header
|
||||
article={article}
|
||||
author={author}
|
||||
tags={tags}
|
||||
breadcrumbs={breadcrumbs}
|
||||
/>
|
||||
<Content article={article} />
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
.item {
|
||||
& .summary {
|
||||
--el-summary-marker-symbol-closed: "▾";
|
||||
--el-summary-marker-symbol-open: "▾";
|
||||
--el-summary-marker-symbol-transform-open: rotate(180deg);
|
||||
|
||||
position: relative;
|
||||
|
||||
padding: var(--spacing-snug);
|
||||
|
||||
font-family: var(--el-summary-font-family);
|
||||
font-size: var(--el-summary-font-size);
|
||||
font-weight: var(--el-summary-font-weight);
|
||||
line-height: var(--el-summary-line-height);
|
||||
color: var(--el-summary-fg);
|
||||
text-transform: var(--el-summary-text-transform);
|
||||
list-style: none;
|
||||
|
||||
background: var(--el-summary-bg);
|
||||
|
||||
&::before {
|
||||
content: var(--el-summary-marker-symbol-closed);
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
||||
font-size: 1em;
|
||||
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
& .content {
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 2s ease;
|
||||
|
||||
& .inner {
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[open] {
|
||||
& .summary {
|
||||
&::before {
|
||||
content: var(--el-summary-marker-symbol-open);
|
||||
transform: var(--el-summary-marker-symbol-transform-open);
|
||||
}
|
||||
}
|
||||
|
||||
& .content {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/components/Content/Accordion/AccordionItem/index.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import styles from './AccordionItem.module.css';
|
||||
|
||||
interface AccordionItemProps {
|
||||
title: string;
|
||||
defaultOpen: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function AccordionItem({
|
||||
title,
|
||||
defaultOpen,
|
||||
children,
|
||||
}: AccordionItemProps) {
|
||||
const [isOpen, setIsOpen] = React.useState(defaultOpen);
|
||||
return (
|
||||
<details
|
||||
className={styles.item}
|
||||
open={isOpen}
|
||||
onToggle={(e) => setIsOpen(e.currentTarget.open)}
|
||||
>
|
||||
<summary
|
||||
className={styles.summary}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setIsOpen(!isOpen);
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</summary>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.inner}>{children}</div>
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
11
src/components/Content/Accordion/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './Accordion.module.css';
|
||||
|
||||
interface AccordionProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Accordion({ children }: AccordionProps) {
|
||||
return <div className={styles.accordion}>{children}</div>;
|
||||
}
|
||||
27
src/components/Content/Blockquote/Blockquote.module.css
Normal file
@@ -0,0 +1,27 @@
|
||||
.container {
|
||||
margin-block: var(--el-p-vspace-top) var(--el-p-vspace-bottom);
|
||||
padding: var(--spacing-snug) 0 var(--spacing-snug) var(--spacing-comfortable);
|
||||
border-left: var(--size-4) solid var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
.quote {
|
||||
font-family: var(--el-blockquote-font-family), serif;
|
||||
font-size: var(--el-blockquote-font-size);
|
||||
font-weight: var(--el-blockquote-font-weight);
|
||||
font-style: var(--el-blockquote-font-style);
|
||||
line-height: var(--el-blockquote-line-height);
|
||||
color: var(--el-blockquote-color);
|
||||
}
|
||||
|
||||
.attribution,
|
||||
.source {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.link {
|
||||
@mixin anim-txt-typewriter;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
70
src/components/Content/Blockquote/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { marked } from 'marked';
|
||||
|
||||
import styles from './Blockquote.module.css';
|
||||
|
||||
interface BlockquoteProps {
|
||||
quote: string;
|
||||
attribution?: string;
|
||||
source?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export default function Blockquote({
|
||||
quote,
|
||||
attribution,
|
||||
source,
|
||||
url,
|
||||
}: BlockquoteProps) {
|
||||
const hasAttribution = !!attribution;
|
||||
const hasSource = !!source;
|
||||
const hasUrl = !!url;
|
||||
const showFooter = hasSource || hasAttribution;
|
||||
const attributionLinked = hasAttribution && hasUrl && !hasSource;
|
||||
const sourceLinked = hasSource && hasUrl;
|
||||
return (
|
||||
<blockquote className={styles.blockquote}>
|
||||
<div
|
||||
className={styles.quote}
|
||||
dangerouslySetInnerHTML={{ __html: marked.parse(quote) }}
|
||||
/>
|
||||
{showFooter && (
|
||||
<footer className={styles.footer}>
|
||||
{hasAttribution && (
|
||||
<cite className={styles.attribution}>
|
||||
{attributionLinked ? (
|
||||
<a
|
||||
href={url}
|
||||
className={styles.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{attribution}
|
||||
</a>
|
||||
) : (
|
||||
attribution
|
||||
)}
|
||||
</cite>
|
||||
)}
|
||||
{hasSource && (
|
||||
<cite className={styles.source}>
|
||||
{' '}
|
||||
–
|
||||
{sourceLinked ? (
|
||||
<a
|
||||
href={url}
|
||||
className={styles.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{source}
|
||||
</a>
|
||||
) : (
|
||||
source
|
||||
)}
|
||||
</cite>
|
||||
)}
|
||||
</footer>
|
||||
)}
|
||||
</blockquote>
|
||||
);
|
||||
}
|
||||
144
src/components/Content/Callout/Callout.module.css
Normal file
@@ -0,0 +1,144 @@
|
||||
@layer component {
|
||||
.container {
|
||||
--callout-bg: var(--color-surface-inverse);
|
||||
--callout-fg: var(--color-text-inverse);
|
||||
--callout-symbol: "";
|
||||
--callout-symbol-color: var(--color-text-inverse);
|
||||
|
||||
@mixin my var(--spacing-cozy);
|
||||
|
||||
position: relative;
|
||||
padding: var(--spacing-cozy) var(--spacing-cozy) var(--spacing-cozy) var(--size-12) ;
|
||||
border: var(--size-1) solid var(--color-surface-inverse);
|
||||
box-shadow: 2px 2px 0 oklch(from var(--color-surface-inverse) calc(l - 0.075) c h), 4px 4px 0 oklch(from var(--color-surface-inverse) calc(l - 0.2) c h);
|
||||
|
||||
&::before {
|
||||
@mixin pt 0.425em;
|
||||
|
||||
content: var(--callout-symbol);
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
|
||||
width: var(--size-8);
|
||||
height: 100%;
|
||||
|
||||
font-size: 1.5em;
|
||||
color: var(--callout-symbol-color);
|
||||
|
||||
background-color: var(--color-surface-inverse);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
@mixin text-xl;
|
||||
|
||||
font-family: var(--font-header);
|
||||
font-weight: var(--typo-weight-black);
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
padding: 0.2em 0.5em;
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--typo-size-xs);
|
||||
font-weight: var(--typo-weight-black);
|
||||
color: var(--callout-fg);
|
||||
|
||||
background: var(--callout-bg);
|
||||
}
|
||||
|
||||
.example {
|
||||
--callout-bg: var(--color-palette-fuchsia);
|
||||
--callout-symbol: "◆";
|
||||
}
|
||||
|
||||
.info {
|
||||
--callout-bg: var(--color-state-info);
|
||||
--callout-symbol: "‽";
|
||||
}
|
||||
|
||||
.warning {
|
||||
--callout-bg: var(--color-state-warning);
|
||||
--callout-symbol: "‼";
|
||||
}
|
||||
|
||||
.tip {
|
||||
--callout-bg: var(--color-palette-lime-green);
|
||||
--callout-symbol: "★";
|
||||
}
|
||||
|
||||
.spoiler {
|
||||
& .label {
|
||||
@mixin text-xl;
|
||||
|
||||
font-family: var(--font-header);
|
||||
font-weight: var(--typo-weight-black);
|
||||
|
||||
&::before {
|
||||
content: "[REDACTED] ";
|
||||
color: var(--color-state-error);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
& .content {
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
min-height: 3em;
|
||||
|
||||
&::after {
|
||||
content: '████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████';
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
font-family: var(--font-mono);
|
||||
line-height: inherit;
|
||||
color: var(--color-text-primary);
|
||||
letter-spacing: -0.15em;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
|
||||
background: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
& .toggle {
|
||||
@util hide-visually;
|
||||
|
||||
&:checked {
|
||||
& ~ .label {
|
||||
&::before {
|
||||
content: '[REVEALED] ';
|
||||
color: var(--color-state-success);
|
||||
}
|
||||
}
|
||||
|
||||
& ~ .content {
|
||||
&::after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/components/Content/Callout/index.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './Callout.module.css';
|
||||
|
||||
interface CalloutProps {
|
||||
type:
|
||||
| 'default'
|
||||
| 'option'
|
||||
| 'example'
|
||||
| 'info'
|
||||
| 'warning'
|
||||
| 'tip'
|
||||
| 'spoiler';
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Callout({ type, title, children }: CalloutProps) {
|
||||
const isSpoiler = type === 'spoiler';
|
||||
const spoilerID = isSpoiler
|
||||
? `spoiler-${Math.random().toString(36).substr(2, 9)}`
|
||||
: undefined;
|
||||
return (
|
||||
<div className={`${styles.container} ${styles[type]}`}>
|
||||
{isSpoiler ? (
|
||||
<>
|
||||
<input type="checkbox" id={spoilerID} className={styles.toggle} />
|
||||
<label htmlFor={spoilerID} className={styles.label}>
|
||||
{title || 'Show spoiler'}
|
||||
</label>
|
||||
</>
|
||||
) : (
|
||||
<p className={styles.title}>{title}</p>
|
||||
)}
|
||||
<span className={styles.badge}>{type.toUpperCase()}</span>
|
||||
<div className={`${styles.content}`}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
.term {
|
||||
margin-block: var(--el-dt-vspace-top) var(--el-dt-vspace-bottom);
|
||||
padding: var(--el-dt-padding);
|
||||
|
||||
font-family: var(--el-dt-font-family), serif;
|
||||
font-size: var(--el-dt-font-size);
|
||||
font-weight: var(--el-dt-font-weight);
|
||||
line-height: var(--el-dt-line-height);
|
||||
color: var(--el-dt-color);
|
||||
text-transform: var(--el-dt-text-transform);
|
||||
letter-spacing: var(--el-dt-letter-spacing);
|
||||
|
||||
background: var(--el-dt-background);
|
||||
}
|
||||
|
||||
.def {
|
||||
margin-block: var(--el-dd-vspace-top) var(--el-dd-vspace-bottom);
|
||||
padding: var(--el-dd-indent);
|
||||
|
||||
font-family: var(--el-dd-font-family), sans-serif;
|
||||
font-size: var(--el-dd-font-size);
|
||||
line-height: var(--el-dd-line-height);
|
||||
color: var(--el-dd-color);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { marked } from 'marked';
|
||||
|
||||
import styles from './DefinitionItem.module.css';
|
||||
|
||||
interface DefinitionItemProps {
|
||||
term: string;
|
||||
definitions: string[];
|
||||
}
|
||||
|
||||
export default function DefinitionItem({
|
||||
term,
|
||||
definitions,
|
||||
}: DefinitionItemProps) {
|
||||
return (
|
||||
<>
|
||||
<dt className={styles.term}>{term}</dt>
|
||||
{definitions.map((def, idx) => (
|
||||
<dd
|
||||
key={idx}
|
||||
className={styles.def}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked.parseInline(def),
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.list {
|
||||
margin-block: var(--el-dl-vspace-top) var(--el-dl-vspace-bottom);
|
||||
font-size: var(--el-dl-font-size);
|
||||
line-height: var(--el-dl-line-height);
|
||||
}
|
||||
10
src/components/Content/DefinitionList/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './DefinitionList.module.css';
|
||||
|
||||
interface DefinitionListProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export default function DefinitionList({ children }: DefinitionListProps) {
|
||||
return <dl className={styles.list}>{children}</dl>;
|
||||
}
|
||||
29
src/components/Content/Figure/Figure.module.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.figure {
|
||||
position: relative;
|
||||
border: var(--size-2) solid var(--color-surface-inverse);
|
||||
clip-path: polygon(0 var(--size-12), var(--size-12) 0, 100% 0, 100% 100%, 0 100%);
|
||||
}
|
||||
|
||||
.caption {
|
||||
padding: var(--spacing-snug);
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--typo-size-sm);
|
||||
line-height: var(--typo-leading-relaxed);
|
||||
color: var(--color-text-inverse);
|
||||
|
||||
background: var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
.credit {
|
||||
display: block;
|
||||
|
||||
margin-top: var(--spacing-tight);
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--typo-size-xs);
|
||||
font-style: normal;
|
||||
color: var(--color-text-inverse);
|
||||
text-align: right;
|
||||
|
||||
}
|
||||
34
src/components/Content/Figure/index.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import Image from 'next/image';
|
||||
|
||||
import styles from './Figure.module.css';
|
||||
|
||||
interface FigureProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
caption?: string;
|
||||
credit?: string;
|
||||
}
|
||||
|
||||
export default function Figure({ src, alt, caption, credit }: FigureProps) {
|
||||
const hasCaption = !!caption;
|
||||
const hasCredit = !!credit;
|
||||
const showFigcaption = hasCaption || hasCredit;
|
||||
|
||||
return (
|
||||
<figure className={styles.figure}>
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt}
|
||||
width={800}
|
||||
height={600}
|
||||
className={styles.image}
|
||||
/>
|
||||
{showFigcaption && (
|
||||
<figcaption className={styles.caption}>
|
||||
{hasCaption && <span className={styles.text}>{caption}</span>}
|
||||
{hasCredit && <cite className={styles.credit}>[{credit}]</cite>}
|
||||
</figcaption>
|
||||
)}
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
3
src/components/Content/Grid/Column/Column.module.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.column {
|
||||
position: relative;
|
||||
}
|
||||
23
src/components/Content/Grid/Column/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './Column.module.css';
|
||||
|
||||
interface ColumnProps {
|
||||
style: {
|
||||
colspan: number;
|
||||
};
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Column({ style, children }: ColumnProps) {
|
||||
return (
|
||||
<div
|
||||
className={styles.column}
|
||||
style={{
|
||||
gridColumn: `span ${style.colspan}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
src/components/Content/Grid/Row/Row.module.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.row {
|
||||
display: grid;
|
||||
}
|
||||
29
src/components/Content/Grid/Row/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './Row.module.css';
|
||||
|
||||
interface RowProps {
|
||||
style: {
|
||||
cols: number;
|
||||
gap: string;
|
||||
align: string;
|
||||
justify: string;
|
||||
};
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Row({ style, children }: RowProps) {
|
||||
return (
|
||||
<div
|
||||
className={`${styles.row}`}
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${style.cols}, 1fr)`,
|
||||
gap: `${style.gap}`,
|
||||
alignItems: `${style.align}`,
|
||||
justifyContent: `${style.justify}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
.container {
|
||||
flex: 1 0 auto;
|
||||
min-width: var(--size-96);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
@supports (anchor-name: --test) {
|
||||
@media screen and (--bp-margin) {
|
||||
position: absolute;
|
||||
top: anchor(top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.note {
|
||||
@mixin text-xs;
|
||||
@mixin my var(--spacing-cozy);
|
||||
|
||||
position: relative;
|
||||
padding-left: var(--size-12);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
@supports (anchor-name: --test) {
|
||||
@media screen and (--bp-margin) {
|
||||
min-width: var(--size-64);
|
||||
margin-top: 0;
|
||||
padding: var(--spacing-cozy) var(--spacing-cozy) var(--spacing-cozy) var(--size-12) ;
|
||||
border: var(--size-1) solid var(--color-surface-inverse);
|
||||
|
||||
background: var(--color-surface-elevated-1);
|
||||
box-shadow: 2px 2px 0 var(--color-palette-woodsmoke), 4px 4px 0 var(--color-palette-charcoal-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.marker {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: var(--size-8);
|
||||
|
||||
font-size: var(--typo-size-3xl);
|
||||
font-weight: var(--typo-weight-black);
|
||||
color: var(--color-text-inverse);
|
||||
|
||||
background-color: var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.ref {
|
||||
@mixin ml var(--spacing-snug);
|
||||
@mixin anim-txt-characterglitch;
|
||||
|
||||
font-weight: var(--typo-weight-black);
|
||||
color: var(--color-text-quarternary);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
41
src/components/Content/Sidenote/Container/index.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { marked } from 'marked';
|
||||
|
||||
import type { Sidenote } from '@/lib/types/components';
|
||||
import styles from './Container.module.css';
|
||||
|
||||
interface ContainerProps {
|
||||
items: Sidenote[];
|
||||
}
|
||||
|
||||
export default function Container({ items }: ContainerProps) {
|
||||
return (
|
||||
<aside className={styles.container}>
|
||||
{items.map((sidenote: Sidenote) => (
|
||||
<div
|
||||
key={sidenote.id}
|
||||
id={sidenote.id}
|
||||
className={styles.wrapper}
|
||||
style={
|
||||
{ positionAnchor: `--note-${sidenote.id}` } as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<div className={`${styles.note} ${styles[sidenote.type]}`}>
|
||||
<span className={styles.marker}>
|
||||
<span className={styles.symbol}>{sidenote.marker}</span>
|
||||
</span>
|
||||
<div
|
||||
className={`${styles.content} content`}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked.parseInline(sidenote.content),
|
||||
}}
|
||||
/>
|
||||
<a href={`#ref-${sidenote.id}`} className={styles.ref}>
|
||||
↩
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
16
src/components/Content/Sidenote/Item/Item.module.css
Normal file
@@ -0,0 +1,16 @@
|
||||
.ref {
|
||||
@mixin px var(--spacing-tight);
|
||||
|
||||
scroll-margin-top: calc(var(--el-header-height) + var(--spacing-comfortable));
|
||||
|
||||
display: inline-block;
|
||||
|
||||
font-weight: var(--typo-weight-black);
|
||||
color: var(--color-secondary);
|
||||
|
||||
transition: color 0.5s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
17
src/components/Content/Sidenote/Item/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Sidenote } from '@/lib/types/components';
|
||||
import styles from './Item.module.css';
|
||||
|
||||
export default function SidenoteItem({ id, marker, content, type }: Sidenote) {
|
||||
return (
|
||||
<sup>
|
||||
<a
|
||||
href={`#${id}`}
|
||||
className={styles.ref}
|
||||
id={`ref-${id}`}
|
||||
style={{ anchorName: `--note-${id}` } as React.CSSProperties}
|
||||
>
|
||||
[{marker}]
|
||||
</a>
|
||||
</sup>
|
||||
);
|
||||
}
|
||||
25
src/components/Content/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import Row from '@/components/Content/Grid/Row';
|
||||
import Column from '@/components/Content/Grid/Column';
|
||||
import Sidenote from '@/components/Content/Sidenote/Item';
|
||||
import DefinitionList from '@/components/Content/DefinitionList';
|
||||
import DefinitionItem from '@/components/Content/DefinitionList/DefinitionItem';
|
||||
import Accordion from '@/components/Content/Accordion';
|
||||
import AccordionItem from '@/components/Content/Accordion/AccordionItem';
|
||||
import Blockquote from '@/components/Content/Blockquote';
|
||||
import Figure from '@/components/Content/Figure';
|
||||
import Callout from '@/components/Content/Callout';
|
||||
|
||||
const ContentComponents = {
|
||||
Column,
|
||||
Row,
|
||||
Sidenote,
|
||||
DefinitionList,
|
||||
DefinitionItem,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
Blockquote,
|
||||
Figure,
|
||||
Callout,
|
||||
};
|
||||
|
||||
export default ContentComponents;
|
||||
77
src/components/Page/Header/Header.module.css
Normal file
@@ -0,0 +1,77 @@
|
||||
@layer components {
|
||||
.header {
|
||||
@mixin py var(--el-header-paddingY);
|
||||
|
||||
position: sticky;
|
||||
z-index: 9;
|
||||
top: 0;
|
||||
|
||||
width: var(--dim-full);
|
||||
|
||||
color: var(--color-text-inverse);
|
||||
|
||||
background-color: var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
.inner {
|
||||
@mixin layout-wrapper;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-cozy);
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
font-size: var(--el-header-font-size);
|
||||
line-height: var(--el-header-line-height);
|
||||
|
||||
}
|
||||
|
||||
.logo {
|
||||
@mixin anim-txt-characterglitch;
|
||||
|
||||
font-family: var(--font-mono);
|
||||
animation:
|
||||
logo-pulse 5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
|
||||
&:hover {
|
||||
transform: translate(0, 0);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.pagename {
|
||||
font-family: var(--font-mono);
|
||||
|
||||
& .bracket {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.menutoggle {
|
||||
@mixin ml auto;
|
||||
|
||||
& button {
|
||||
cursor: pointer;
|
||||
font-family: var(--font-mono);
|
||||
transition: color 0.2s ease-out;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes logo-pulse {
|
||||
0% { opacity: 1; }
|
||||
25% { opacity: 0.66; }
|
||||
50% { opacity: 0.33; }
|
||||
75% { opacity: 0.66; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
}
|
||||
48
src/components/Page/Header/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { useMenu } from '@/contexts/MenuContext';
|
||||
import styles from './Header.module.css';
|
||||
|
||||
export default function Header() {
|
||||
const { isMenuOpen, closeMenu, openMenu, startClosing, resetClosing } =
|
||||
useMenu();
|
||||
|
||||
const handleMenuToggle = () => {
|
||||
if (isMenuOpen) {
|
||||
startClosing();
|
||||
setTimeout(() => {
|
||||
closeMenu();
|
||||
resetClosing();
|
||||
}, 800);
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.logo}>
|
||||
<Link href="/">◬</Link>
|
||||
</div>
|
||||
<div className={styles.pagename}>
|
||||
<span>
|
||||
dave <span className={styles.bracket}>[</span>dmg
|
||||
<span className={styles.bracket}>]</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.menutoggle}>
|
||||
<button
|
||||
onClick={() => handleMenuToggle()}
|
||||
aria-label={isMenuOpen ? 'Close menu' : 'Open menu'}
|
||||
aria-expanded={isMenuOpen}
|
||||
aria-controls="main-menu"
|
||||
>
|
||||
{isMenuOpen ? '[×]' : '[⚍]'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
403
src/components/Page/Menu/Menu.module.css
Normal file
@@ -0,0 +1,403 @@
|
||||
@layer components {
|
||||
/* === MenuGrid === */
|
||||
|
||||
.menu {
|
||||
pointer-events: none;
|
||||
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
top: var(--el-header-height);
|
||||
left: 0;
|
||||
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-template-areas:
|
||||
"area_1"
|
||||
"area_2"
|
||||
"area_3"
|
||||
"area_4"
|
||||
"area_5";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: repeat(5, auto);
|
||||
gap: 0;
|
||||
gap: var(--spacing-cozy);
|
||||
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
max-height: calc(100vh - var(--el-header-height));
|
||||
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
background-color: var(--color-palette-charcoal-gray);
|
||||
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
|
||||
&.isOpen {
|
||||
pointer-events: auto;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media screen and (--bp-tablet) {
|
||||
@mixin px var(--spacing-comfortable);
|
||||
|
||||
grid-template-areas:
|
||||
"area_1"
|
||||
"area_2"
|
||||
"area_3"
|
||||
"area_4"
|
||||
"area_5";
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
gap: var(--spacing-comfortable);
|
||||
}
|
||||
|
||||
@media screen and (--bp-desktop) {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"area_4 area_4 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_4 area_4 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_1"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_1";
|
||||
grid-template-columns: 2.5fr 1fr 3fr 1.5fr 1fr 1fr 4fr;
|
||||
grid-template-rows: 3fr 1fr 2.5fr 1.5fr 2fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* === MenuArea === */
|
||||
.area {
|
||||
/* === AREA VARIABLES === */
|
||||
--area-bg: transparent;
|
||||
--area-border: transparent;
|
||||
--area-animation-keyframe: none;
|
||||
--area-animation-duration: 0s;
|
||||
--area-animation-timing: linear;
|
||||
--area-bg-filter: grayscale(100%) contrast(150) brightness(150);
|
||||
|
||||
/** === TITLE VARIABLES === */
|
||||
--title-color: var(--color-palette-light-silver);
|
||||
--title-font: var(--font-header);
|
||||
--title-font-size: var(--typo-size-2xl);
|
||||
--title-font-weight: var(--typo-weight-black);
|
||||
--title-line-height: 1;
|
||||
--title-transform: uppercase;
|
||||
--title-spacing: var(--typo-spacing-comfortable);
|
||||
--title-hover-color: var(--color-tertiary);
|
||||
--title-current-color: var(--color-primary);
|
||||
--title-current-bg: var(--color-primary);
|
||||
|
||||
/** === SUBLINK VARIABLES === */;
|
||||
--sublink-color: var(--color-palette-light-silver);
|
||||
--sublink-font: var(--font-header);
|
||||
--sublink-font-size: var(--typo-size-2xl);
|
||||
--sublink-font-weight: var(--typo-weight-black);
|
||||
--sublink-transform: uppercase;
|
||||
--sublink-spacing: var(--typo-spacing-relaxed);
|
||||
--sublink-line-height: var(--typo-leading-relaxed);
|
||||
--sublink-letter-spacing: var(--typo-spacing-relaxed);
|
||||
--sublink-current-color: var(--color-primary);
|
||||
--sublink-current-bg: var(--color-primary);
|
||||
|
||||
/** === SUBTITLE VARIABLES === **/
|
||||
--divider-color: var(--color-palette-light-silver);
|
||||
--divider-width: var(--size-6);
|
||||
--divider-height: var(--size-2);
|
||||
--divider-font: var(--font-mono);
|
||||
--divider-symbol: ⯆;
|
||||
--divider-font-size: var(--typo-size-2xl);
|
||||
--divider-padding: 0 var(--typo-spacing-cozy);
|
||||
--subtitle-color: var(--color-palette-light-silver);
|
||||
--subtitle-font: var(--font-mono);
|
||||
--subtitle-font-size: var(--typo-size-2xl);
|
||||
--subtitle-text-transform: uppercase;
|
||||
--subtitle-letter-spacing: var(--typo-spacing-cozy);
|
||||
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
border: var(--size-1) solid var(--area-border);
|
||||
|
||||
text-align: center;
|
||||
|
||||
background: var(--area-bg);
|
||||
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&:not(.current) {
|
||||
&:hover {
|
||||
animation: var(--area-animation-keyframe) var(--area-animation-duration) var(--area-animation-timing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.area_1 { grid-area: area_1; }
|
||||
.area_2 { grid-area: area_2; }
|
||||
.area_3 { grid-area: area_3; }
|
||||
.area_4 { grid-area: area_4; }
|
||||
.area_5 { grid-area: area_5; }
|
||||
|
||||
.hasBGImg {
|
||||
&:hover {
|
||||
.bgImg {
|
||||
animation: var(--area-animation-keyframe) var(--area-animation-duration) var(--area-animation-timing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bgImg {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
object-fit: cover;
|
||||
filter: var(--area-bg-filter);
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* === MenuTitle === */
|
||||
.title {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mainlink {
|
||||
position: relative;
|
||||
|
||||
font-family: var(--title-font);
|
||||
font-size: var(--title-font-size);
|
||||
font-weight: var(--title-font-weight);
|
||||
line-height: var(--title-line-height);
|
||||
color: var(--title-color);
|
||||
text-transform: var(--title-transform);
|
||||
letter-spacing: var(--title-spacing);
|
||||
|
||||
transition: any 0.5s ease-in-out;
|
||||
|
||||
&:focus {
|
||||
&:hover {
|
||||
color: var(--title-current-color);
|
||||
}
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--title-current-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.current {
|
||||
pointer-events: none;
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
&:hover {
|
||||
color: var(--title-current-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (--bp-desktop) {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
transition: border 0.5s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
/* === MenuSublinks === */
|
||||
.list {
|
||||
@media screen and (--bp-tablet-down) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sublink {
|
||||
position: relative;
|
||||
|
||||
font-family: var(--sublink-font);
|
||||
font-size: var(--sublink-font-size);
|
||||
font-weight: var(--sublink-font-weight);
|
||||
line-height: var(--sublink-line-height);
|
||||
color: var(--sublink-color);
|
||||
text-transform: var(--sublink-transform);
|
||||
letter-spacing: var(--sublink-spacing);;
|
||||
|
||||
&:not(.current),
|
||||
&:not(.focus) {
|
||||
transition: var(--sublink-hover-transition);
|
||||
|
||||
&::after,
|
||||
&::before {
|
||||
position: absolute;
|
||||
font-family: var(--sublink-hover-decorator-font);
|
||||
opacity: 0;
|
||||
transition: var(--sublink-hover-decorator-transition);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: var(--sublink-hover-decorator-left-symbol);
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: var(--sublink-hover-decorator-right-symbol);
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--sublink-hover-color);
|
||||
|
||||
&::before {
|
||||
top: var(--sublink-hover-decorator-left-pos-y);
|
||||
left: var(--sublink-hover-decorator-left-pos-x);
|
||||
transform: var(--sublink-hover-decorator-left-transform);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&::after {
|
||||
top: var(--sublink-hover-decorator-right-pos-y);
|
||||
right: var(--sublink-hover-decorator-rightt-pos-x);
|
||||
transform: var(--sublink-hover-decorator-right-transform);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
&:hover {
|
||||
color: var(--sublink-current-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.current {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* === MenuSubtitle === */
|
||||
.wrapper {
|
||||
position: relative;
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* === UTILITY Classes */
|
||||
@media screen and (--bp-desktop) {
|
||||
.pos_tr {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
.pos_tc {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.pos_tl {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
left: 1em;
|
||||
}
|
||||
|
||||
.pos_cr {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 1em;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.pos_c {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 50%);
|
||||
}
|
||||
|
||||
.pos_cl {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 1em;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.pos_br {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
bottom: 1em;
|
||||
}
|
||||
|
||||
.pos_bc {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.pos_bl {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
left: 1em;
|
||||
}
|
||||
|
||||
.vertical-rl {
|
||||
writing-mode: vertical-rl;
|
||||
|
||||
& * {
|
||||
writing-mode: vertical-rl;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical-lr {
|
||||
writing-mode: vertical-lr;
|
||||
|
||||
& * {
|
||||
writing-mode: vertical-lr;
|
||||
}
|
||||
}
|
||||
|
||||
.sideways-rl {
|
||||
writing-mode: sideways-rl;
|
||||
|
||||
& * {
|
||||
writing-mode: sideways-rl;
|
||||
}
|
||||
}
|
||||
|
||||
.sideways-lr {
|
||||
writing-mode: sideways-lr;
|
||||
|
||||
& * {
|
||||
writing-mode: sideways-lr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* === CATEGORY Variants === */
|
||||
@media screen and (--bp-desktop) {
|
||||
.meta {}
|
||||
.kitchensink {}
|
||||
.awq {}
|
||||
.worldburner {}
|
||||
.chainbreaker {}
|
||||
}
|
||||
}
|
||||
106
src/components/Page/Menu/MenuArea/MenuArea.module.css
Normal file
@@ -0,0 +1,106 @@
|
||||
@layer components {
|
||||
.area {
|
||||
--area-bg: transparent;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
text-align: center;
|
||||
|
||||
background: var(--area-bg);
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
border-bottom: var(--size-1) solid var(--color-palette-gunmetal);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.area_1 {
|
||||
position: relative;
|
||||
grid-area: area_1;
|
||||
}
|
||||
|
||||
.area_2 {
|
||||
position: relative;
|
||||
grid-area: area_2;
|
||||
}
|
||||
|
||||
.area_3 {
|
||||
position: relative;
|
||||
grid-area: area_3;
|
||||
}
|
||||
|
||||
.area_4 {
|
||||
position: relative;
|
||||
grid-area: area_4;
|
||||
}
|
||||
|
||||
.area_5 {
|
||||
position: relative;
|
||||
grid-area: area_5;
|
||||
}
|
||||
|
||||
.hasBGImg {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
& .bgImg {
|
||||
animation: var(--area-animation-keyframe) var(--area-animation-duration) var(--area-animation-timing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bgImg {
|
||||
--area-bg-animation-keyframe: none;
|
||||
--area-bg--animation-duration: 0s;
|
||||
--area-bg--animation-timing: linear;
|
||||
--area-bg-filter: grayscale(100%) contrast(10) brightness(250);
|
||||
--area-bg-blendmode: color-dodge;
|
||||
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
object-fit: cover;
|
||||
filter: var(--area-bg-filter);
|
||||
mix-blend-mode: var(--area-bg-blendmode);
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (--bp-desktop) {
|
||||
.chainbreaker {
|
||||
--area-animation-keyframe: var(--kf-color-bleed);
|
||||
--area-animation-duration: var(--img-colorbleed-duration);
|
||||
--area-animation-timing: var(--img-colorbleed-timing);
|
||||
|
||||
& .bgImg {
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
&.hasBGImg {
|
||||
--divider-color: transparent;
|
||||
--title-color: transparent;
|
||||
--subtitle-color: transparent;
|
||||
--subtitle-transition: all 0.3s ease-in-out;
|
||||
--title-transition: color 0.3s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
--divider-color: var(--color-tertiary);
|
||||
--title-color: var(--color-tertiary);
|
||||
--subtitle-color: var(--color-tertiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/components/Page/Menu/MenuArea/index.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
|
||||
import MenuItem from '@/components/Page/Menu//MenuItem/';
|
||||
import { useCurrentPath } from '@/hooks/useCurrentPath';
|
||||
|
||||
import styles from './MenuArea.module.css';
|
||||
|
||||
import { NavigationItem } from '@/lib/types/navigation';
|
||||
|
||||
interface MenuAreaProps {
|
||||
item: NavigationItem;
|
||||
}
|
||||
|
||||
const MenuArea = React.memo(({ item }: MenuAreaProps) => {
|
||||
const hasBGImg = !!item.background;
|
||||
const { isCurrentPath } = useCurrentPath();
|
||||
|
||||
const areaClasses = React.useMemo(() => {
|
||||
return [
|
||||
styles.area,
|
||||
styles[item.gridPosition],
|
||||
styles[item.variant],
|
||||
isCurrentPath(item.path) ? styles.current : '',
|
||||
item.background ? styles.hasBGImg : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}, [item.background, isCurrentPath]);
|
||||
|
||||
return (
|
||||
<section className={areaClasses}>
|
||||
{hasBGImg && (
|
||||
<img
|
||||
className={styles.bgImg}
|
||||
src={item.background}
|
||||
alt={`Background Image for ${item.name}`}
|
||||
/>
|
||||
)}
|
||||
<MenuItem item={item} />
|
||||
</section>
|
||||
);
|
||||
});
|
||||
|
||||
MenuArea.displayName = 'MenuArea';
|
||||
|
||||
export default MenuArea;
|
||||
117
src/components/Page/Menu/MenuGrid/MenuGrid.module.css
Normal file
@@ -0,0 +1,117 @@
|
||||
@layer components {
|
||||
.menu {
|
||||
--grid-bg: var(--color-palette-charcoal-gray);
|
||||
--grid-fg: var(--color-palette-light-silver);
|
||||
--area-blendmode: normal;
|
||||
|
||||
/* === MenuTitle Vars === */
|
||||
--title-color: var(--grid-fg);
|
||||
--title-font: var(--font-mono);
|
||||
--title-font-size: var(--typo-size-2xl);
|
||||
--title-font-weight: var(--typo-weight-black);
|
||||
--title-line-height: 1;
|
||||
--title-transform: uppercase;
|
||||
--title-spacing: var(--typo-spacing-comfortable);
|
||||
--title-hover-fg: var(--primary);
|
||||
--title-hover-bg: transparent;
|
||||
--title-current-bg: var(--color-tertiary);
|
||||
--title-current-fg: var(--grid-bg);
|
||||
--title-focus-fg: var(--color-secondary);
|
||||
--title-focus-bg: var(--grid-bg);
|
||||
--title-transition: none;
|
||||
|
||||
/* === MenuSublinks Vars === */
|
||||
--sublink-color: var(--grid-fg);
|
||||
--sublink-font: var(--font-mono);
|
||||
--sublink-font-size: var(--typo-size-xl);
|
||||
--sublink-font-weight: var(--typo-weight-light);
|
||||
--sublink-transform: uppercase;
|
||||
--sublink-line-height: var(--typo-leading-relaxed);
|
||||
--sublink-spacing: var(--typo-spacing-loosest);
|
||||
--sublink-hover-fg: var(--color-tertiary);
|
||||
--sublink-hover-bg: transparent;
|
||||
--sublink-current-fg: var(--grid-bg);
|
||||
--sublink-current-bg: var(--color-primary);
|
||||
--sublink-focus-fg: var(--color-secondary);
|
||||
--sublink-focus-bg: var(--grid-bg);
|
||||
--sublink-transition: none;
|
||||
|
||||
/* === MenuSubtitle Vars === */
|
||||
--divider-color: var(--grid-fg);
|
||||
--divider-width: var(--size-12);
|
||||
--divider-height: var(--size-2);
|
||||
--divider-font: var(--font-mono);
|
||||
--divider-line-height: 1;
|
||||
--divider-font-size: var(--typo-size-3xl);
|
||||
--divider-padding: 0 var(--typo-spacing-cozy);
|
||||
--subtitle-font: var(--font-mono);
|
||||
--subtitle-color: var(--grid-fg);
|
||||
--subtitle-font-size: var(--typo-size-xl);
|
||||
--subtitle-text-transform: uppercase;
|
||||
--subtitle-letter-spacing: var(--typo-spacing-snug);
|
||||
--subtitle-transition: none;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
top: var(--el-header-height);
|
||||
left: 0;
|
||||
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-template-areas:
|
||||
"area_1"
|
||||
"area_2"
|
||||
"area_3"
|
||||
"area_4"
|
||||
"area_5";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: repeat(5, auto);
|
||||
|
||||
width: 100vw;
|
||||
height: calc(100vh - var(--el-header-height));
|
||||
|
||||
color: var(--grid-fg);
|
||||
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
background-color: var(--grid-bg);
|
||||
clip-path: inset(0 0 100% 0);
|
||||
|
||||
transition: clip-path 0.35s steps(8, end);
|
||||
|
||||
|
||||
&.isOpen {
|
||||
pointer-events: auto;
|
||||
|
||||
transform: translateY(0);
|
||||
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
|
||||
&.isClosing {
|
||||
clip-path: inset(0 0 100% 0);
|
||||
transition: clip-path 0.25s steps(6, end);
|
||||
|
||||
&.isOpen {
|
||||
clip-path: inset(0 0 100% 0);
|
||||
transition: clip-path 0.25s steps(6, end);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (--bp-desktop) {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"area_4 area_4 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_4 area_4 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_1"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_1";
|
||||
grid-template-columns: 2.5fr 1fr 3fr 1.5fr 1fr 1fr 4fr;
|
||||
grid-template-rows: 3fr 1fr 2.5fr 1.5fr 2fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/components/Page/Menu/MenuGrid/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useMenu } from '@/contexts/MenuContext';
|
||||
import MenuArea from '@/components/Page/Menu/MenuArea/';
|
||||
|
||||
import styles from './MenuGrid.module.css';
|
||||
|
||||
interface MenuGridProps {
|
||||
navigationData: Awaited<
|
||||
ReturnType<
|
||||
typeof import('@/lib/readers/system/navigation').getNavigationData
|
||||
>
|
||||
>;
|
||||
}
|
||||
|
||||
export default function MenuGrid({ navigationData }: MenuGridProps) {
|
||||
const { isMenuOpen, closeMenu, isClosing, startClosing, resetClosing } =
|
||||
useMenu();
|
||||
const menuRef = React.useRef<HTMLElement>(null);
|
||||
const handleClose = React.useCallback(() => {
|
||||
startClosing();
|
||||
setTimeout(() => {
|
||||
closeMenu();
|
||||
resetClosing();
|
||||
}, 800);
|
||||
}, [closeMenu, startClosing, resetClosing]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && isMenuOpen) {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
return () => document.removeEventListener('keydown', handleEscape);
|
||||
}, [isMenuOpen, handleClose]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMenuOpen && menuRef.current) {
|
||||
const initialFocus = menuRef.current.querySelector(
|
||||
'a, button'
|
||||
) as HTMLElement;
|
||||
initialFocus?.focus();
|
||||
}
|
||||
}, [isMenuOpen]);
|
||||
|
||||
if (!navigationData) return null;
|
||||
|
||||
const { items } = navigationData;
|
||||
|
||||
return (
|
||||
<nav
|
||||
ref={menuRef}
|
||||
className={`${styles.menu} ${isMenuOpen ? styles.isOpen : ''} ${isClosing ? styles.isClosing : ''}`}
|
||||
aria-hidden={!isMenuOpen}
|
||||
aria-label="Main navigation"
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<MenuArea key={item.gridPosition} item={item} />
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
24
src/components/Page/Menu/MenuItem/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { hasSublinks, hasSubtitle } from '@/lib/types/navigation';
|
||||
import MenuTitle from '@/components/Page/Menu/MenuTitle/';
|
||||
import MenuSublinks from '@/components/Page/Menu/MenuSublinks/';
|
||||
import MenuSubtitle from '@/components/Page/Menu/MenuSubtitle/';
|
||||
|
||||
import { NavigationItem } from '@/lib/types/navigation';
|
||||
|
||||
interface MenuItem {
|
||||
item: NavigationItem;
|
||||
}
|
||||
|
||||
export default function MenuItem({ item }: MenuItem) {
|
||||
return (
|
||||
<>
|
||||
<MenuTitle path={item.path} name={item.name} variant={item.variant} />
|
||||
{hasSublinks(item) && (
|
||||
<MenuSublinks sublinks={item.sublinks.value} variant={item.variant} />
|
||||
)}
|
||||
{hasSubtitle(item) && (
|
||||
<MenuSubtitle subtitle={item.subtitle.value} variant={item.variant} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
@layer components {
|
||||
.list {
|
||||
@media screen and (--bp-tablet-down) {
|
||||
@util hide-visually;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sublink {
|
||||
font-family: var(--sublink-font);
|
||||
font-size: var(--sublink-font-size);
|
||||
font-weight: var(--sublink-font-weight);
|
||||
line-height: var(--sublink-line-height);
|
||||
color: var(--sublink-color);
|
||||
text-transform: var(--sublink-transform);
|
||||
letter-spacing: var(--sublink-spacing);
|
||||
|
||||
&.current {
|
||||
padding: var(--spacing-snug) var(--spacing-tight);
|
||||
color: var(--title-current-fg);
|
||||
animation: var(--kf-text-glitch) 5s infinite;
|
||||
|
||||
&::before {
|
||||
content: "⟩";
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.current) {
|
||||
--pointer-left-symbol: "▶";
|
||||
--pointer-right-symbol: "◀";
|
||||
--pointer-distance: 1em;
|
||||
|
||||
@mixin anim-txt-pointer_focus;
|
||||
@mixin px var(--spacing-tight);
|
||||
|
||||
&:focus {
|
||||
color: var(--sublink-focus-fg);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:not(:focus) {
|
||||
--pointer-left-symbol: "{";
|
||||
--pointer-right-symbol: "}";
|
||||
--pointer-distance: 0.5em;
|
||||
|
||||
@mixin anim-txt-pointer;
|
||||
|
||||
&:hover {
|
||||
--sublink-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/components/Page/Menu/MenuSublinks/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import styles from './MenuSublinks.module.css';
|
||||
|
||||
import { SubNavigationItem } from '@/lib/types/navigation';
|
||||
import { useCurrentPath } from '@/hooks/useCurrentPath';
|
||||
|
||||
interface MenuSublinks {
|
||||
sublinks: readonly SubNavigationItem[];
|
||||
variant: string;
|
||||
}
|
||||
|
||||
export default function MenuSublinks({ sublinks, variant }: MenuSublinks) {
|
||||
const { isCurrentPath } = useCurrentPath();
|
||||
return (
|
||||
<ul className={`${styles.list} ${styles[`${variant}`]}`}>
|
||||
{sublinks.map((sublink: SubNavigationItem) => (
|
||||
<li className={styles.item} key={sublink.path}>
|
||||
<Link
|
||||
href={sublink.path}
|
||||
className={`${styles.sublink} ${isCurrentPath(sublink.path) ? styles.current : ''}`}
|
||||
>
|
||||
{sublink.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
@layer components {
|
||||
.claim {
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dividersymbol {
|
||||
position: relative;
|
||||
|
||||
font-family: var(--divider-font);
|
||||
font-size: var(--divider-font-size);
|
||||
line-height: var(--divider-line-height);
|
||||
color: var(--divider-color);
|
||||
|
||||
transition: var(--subtitle-transition);
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
width: var(--divider-width);
|
||||
height: var(--divider-height);
|
||||
|
||||
background-color: var(--divider-color);
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: calc(var(--divider-width) *-1);
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: calc(var(--divider-width) *-1);
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-family: var(--subtitle-font);
|
||||
font-size: var(--subtitle-font-size);
|
||||
color: var(--subtitle-color);
|
||||
text-transform: var(--subtitle-text-transform);
|
||||
letter-spacing: var(--subtitle-letter-spacing);
|
||||
|
||||
transition: var(--subtitle-transition);
|
||||
}
|
||||
}
|
||||
26
src/components/Page/Menu/MenuSubtitle/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Subtitle, hasDivider } from '@/lib/types/navigation';
|
||||
import styles from './MenuSubtitle.module.css';
|
||||
|
||||
interface MenuSubtitleProps {
|
||||
subtitle: Subtitle;
|
||||
variant: string;
|
||||
}
|
||||
|
||||
export default function MenuSubtitle({ subtitle, variant }: MenuSubtitleProps) {
|
||||
const divider = hasDivider(subtitle) ? (
|
||||
<div className={styles.divider}>
|
||||
<span className={`${styles.dividersymbol}`}>
|
||||
{subtitle.divider.value}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`${styles.claim} ${styles[`${variant}`]}`}>
|
||||
{divider}
|
||||
<p className={`${styles.subtitle}`}>{subtitle.content}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
63
src/components/Page/Menu/MenuTitle/MenuTitle.module.css
Normal file
@@ -0,0 +1,63 @@
|
||||
@layer components {
|
||||
.heading {
|
||||
position: relative;
|
||||
|
||||
&:has(+ ul) {
|
||||
@mixin my var(--spacing-comfortable);
|
||||
}
|
||||
}
|
||||
|
||||
.mainlink {
|
||||
position: relative;
|
||||
|
||||
border: none;
|
||||
|
||||
font-family: var(--title-font);
|
||||
font-size: var(--title-font-size);
|
||||
font-weight: var(--title-font-weight);
|
||||
line-height: var(--title-line-height);
|
||||
color: var(--title-color);
|
||||
text-transform: var(--title-transform);
|
||||
letter-spacing: var(--title-spacing);
|
||||
|
||||
background: transparent;
|
||||
|
||||
transition: var(--title-transition);
|
||||
|
||||
&.current {
|
||||
padding: var(--spacing-snug) var(--spacing-tight);
|
||||
color: var(--title-current-fg);
|
||||
animation: var(--kf-text-glitch) 5s infinite;
|
||||
|
||||
&::before {
|
||||
content: "⟩";
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.current) {
|
||||
--pointer-left-symbol: "▶";
|
||||
--pointer-right-symbol: "◀";
|
||||
--pointer-distance: 1em;
|
||||
|
||||
@mixin anim-txt-pointer_focus;
|
||||
@mixin px var(--spacing-tight);
|
||||
|
||||
&:focus {
|
||||
color: var(--title-focus-fg);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:not(:focus) {
|
||||
--pointer-left-symbol: "{";
|
||||
--pointer-right-symbol: "}";
|
||||
--pointer-distance: 0.5em;
|
||||
|
||||
@mixin anim-txt-pointer;
|
||||
|
||||
&:hover {
|
||||
--title-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/components/Page/Menu/MenuTitle/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import styles from './MenuTitle.module.css';
|
||||
|
||||
import { useCurrentPath } from '@/hooks/useCurrentPath';
|
||||
|
||||
interface MenuTitleProps {
|
||||
name: string;
|
||||
path: string;
|
||||
variant: string;
|
||||
}
|
||||
|
||||
export default function MenuTitle({ name, path, variant }: MenuTitleProps) {
|
||||
const { isCurrentPath } = useCurrentPath();
|
||||
return (
|
||||
<h2 className={`${styles.heading} ${styles[`${variant}`]}`}>
|
||||
<Link
|
||||
className={`${styles.mainlink} ${isCurrentPath(path) ? styles.current : ''}`}
|
||||
href={path}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
7
src/components/Page/Menu/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { getNavigationData } from '@/lib/readers/system/navigation';
|
||||
import MenuGrid from '@/components/Page/Menu/MenuGrid';
|
||||
|
||||
export default async function Menu() {
|
||||
const navigation = await getNavigationData();
|
||||
return <MenuGrid navigationData={navigation} />;
|
||||
}
|
||||
62
src/contexts/MenuContext.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
'use client';
|
||||
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
|
||||
interface MenuContextType {
|
||||
isMenuOpen: boolean;
|
||||
isClosing: boolean;
|
||||
closeMenu: () => void;
|
||||
openMenu: () => void;
|
||||
startClosing: () => void;
|
||||
resetClosing: () => void;
|
||||
}
|
||||
|
||||
const MenuContext = React.createContext<MenuContextType | undefined>(undefined);
|
||||
|
||||
interface MenuProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const MenuProvider = ({ children }: MenuProviderProps) => {
|
||||
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
||||
const [isClosing, setIsClosing] = React.useState(false);
|
||||
|
||||
const closeMenu = () => setIsMenuOpen(false);
|
||||
const openMenu = () => setIsMenuOpen(true);
|
||||
const startClosing = () => setIsClosing(true);
|
||||
const resetClosing = () => setIsClosing(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMenuOpen) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = 'unset';
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = 'unset';
|
||||
};
|
||||
}, [isMenuOpen]);
|
||||
|
||||
return (
|
||||
<MenuContext.Provider
|
||||
value={{
|
||||
isMenuOpen,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
isClosing,
|
||||
startClosing,
|
||||
resetClosing,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MenuContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMenu = () => {
|
||||
const context = useContext(MenuContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useMenu must be used within a MenuProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
8
src/hooks/useCurrentPath.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
export const useCurrentPath = () => {
|
||||
const pathname = usePathname();
|
||||
const isCurrentPath = (path: string) => path === pathname;
|
||||
|
||||
return { isCurrentPath, pathname };
|
||||
};
|
||||
14
src/keystatic/collections/awq/article.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { collection } from '@keystatic/core';
|
||||
|
||||
import { createArticleField } from '@/keystatic/fields/article';
|
||||
|
||||
export default collection({
|
||||
label: 'AWQ - Posts',
|
||||
slugField: 'title',
|
||||
path: 'content/awq/articles/*',
|
||||
format: { contentField: 'content' },
|
||||
entryLayout: 'content',
|
||||
schema: {
|
||||
...createArticleField('awq/articles', 'awq'),
|
||||
},
|
||||
});
|
||||
7
src/keystatic/collections/awq/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import posts from '@/keystatic/collections/awq/article';
|
||||
|
||||
const awqCollections = {
|
||||
awq_posts: posts,
|
||||
};
|
||||
|
||||
export default awqCollections;
|
||||
14
src/keystatic/collections/meta/article.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { collection } from '@keystatic/core';
|
||||
|
||||
import { createArticleField } from '@/keystatic/fields/article';
|
||||
|
||||
export default collection({
|
||||
label: 'Meta - Posts',
|
||||
slugField: 'title',
|
||||
path: 'content/meta/*',
|
||||
format: { contentField: 'content' },
|
||||
entryLayout: 'content',
|
||||
schema: {
|
||||
...createArticleField('meta'),
|
||||
},
|
||||
});
|
||||
46
src/keystatic/collections/taxonomy/authors.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { collection, fields } from '@keystatic/core';
|
||||
|
||||
export default collection({
|
||||
label: 'Authors',
|
||||
slugField: 'name',
|
||||
path: 'content/taxonomy/authors/*',
|
||||
format: { data: 'json' },
|
||||
schema: {
|
||||
name: fields.slug({ name: { label: 'Name' } }),
|
||||
avatar: fields.image({
|
||||
label: 'Avatar',
|
||||
directory: 'public/images/authors',
|
||||
publicPath: '/images/authors',
|
||||
}),
|
||||
description: fields.text({ label: 'Description', multiline: true }),
|
||||
contacts: fields.array(
|
||||
fields.object({
|
||||
type: fields.select({
|
||||
label: 'Contact Type',
|
||||
defaultValue: 'email',
|
||||
options: [
|
||||
{
|
||||
value: 'email',
|
||||
label: 'E-Mail',
|
||||
},
|
||||
{
|
||||
value: 'discord',
|
||||
label: 'Discord',
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
label: 'Other',
|
||||
},
|
||||
],
|
||||
}),
|
||||
url: fields.text({
|
||||
label: 'URL',
|
||||
}),
|
||||
}),
|
||||
{
|
||||
label: 'Contact Type',
|
||||
itemLabel: (props) => `${props.fields.type.value}`,
|
||||
}
|
||||
),
|
||||
},
|
||||
});
|
||||
41
src/keystatic/collections/taxonomy/tags.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { collection, fields } from '@keystatic/core';
|
||||
|
||||
export default collection({
|
||||
label: 'Tags',
|
||||
slugField: 'name',
|
||||
path: 'content/taxonomy/tags/*',
|
||||
format: { data: 'json' },
|
||||
schema: {
|
||||
name: fields.slug({ name: { label: 'Name' } }),
|
||||
icon: fields.conditional(
|
||||
fields.select({
|
||||
label: 'Icon Type',
|
||||
defaultValue: 'none',
|
||||
options: [
|
||||
{
|
||||
value: 'none',
|
||||
label: 'None',
|
||||
},
|
||||
{
|
||||
value: 'glyph',
|
||||
label: 'Glyph',
|
||||
},
|
||||
{
|
||||
value: 'img',
|
||||
label: 'Image',
|
||||
},
|
||||
],
|
||||
}),
|
||||
{
|
||||
none: fields.empty(),
|
||||
glyph: fields.text({ label: 'Glyph' }),
|
||||
img: fields.image({
|
||||
label: 'icon',
|
||||
publicPath: '/images/icons',
|
||||
directory: 'public/images/icons',
|
||||
}),
|
||||
}
|
||||
),
|
||||
description: fields.text({ label: 'Description', multiline: true }),
|
||||
},
|
||||
});
|
||||
32
src/keystatic/components/general/accordion.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { listCollapseIcon } from '@keystar/ui/icon/icons/listCollapseIcon';
|
||||
import { repeating, wrapper } from '@keystatic/core/content-components';
|
||||
import { fields } from '@keystatic/core';
|
||||
|
||||
const accordionComponents = {
|
||||
Accordion: repeating({
|
||||
label: 'Accordion',
|
||||
icon: listCollapseIcon,
|
||||
children: ['AccordionItem'],
|
||||
schema: {},
|
||||
}),
|
||||
AccordionItem: wrapper({
|
||||
label: 'Accordion Item',
|
||||
forSpecificLocations: true,
|
||||
schema: {
|
||||
title: fields.text({
|
||||
label: 'Title',
|
||||
validation: {
|
||||
length: {
|
||||
min: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
defaultOpen: fields.checkbox({
|
||||
label: 'Open by default',
|
||||
defaultValue: false,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default accordionComponents;
|
||||