5 Commits

Author SHA1 Message Date
4c5c7d7e31 Added general fields
All checks were successful
Build and Deploy DAVE | DMGs Site / deploy (push) Successful in 1m30s
2025-11-12 08:53:29 +01:00
af7a72d174 added Meta & Content Field Collections 2025-11-11 13:49:31 +01:00
abb694bf5b Added taxonomy/tags + reader 2025-11-11 13:32:41 +01:00
ae1cabeb97 Added Author reader 2025-11-11 13:18:37 +01:00
b30b77e6fd Added Taxonomy/Author collection 2025-11-11 11:25:12 +01:00
251 changed files with 4325 additions and 9997 deletions

View File

@@ -2,7 +2,7 @@ name: Build and Deploy DAVE | DMGs Site
on: on:
push: push:
branches: [ main ] branches: [main]
jobs: jobs:
deploy: deploy:
@@ -41,4 +41,4 @@ jobs:
mv src/app/\(cms\) src/app/_cms mv src/app/\(cms\) src/app/_cms
pnpm run build pnpm run build
rm -rf /var/www/sites/dave-dmg/blog/* rm -rf /var/www/sites/dave-dmg/blog/*
cp -r out/* /var/www/sites/dave-dmg/blog/ cp -r out/* /var/www/sites/dave-dmg/blog/

View File

@@ -1,39 +1,6 @@
<component name="InspectionProjectProfileManager"> <component name="InspectionProjectProfileManager">
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="CssDeprecatedValue" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidAtRule" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidCharsetRule" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssInvalidCustomPropertyAtRuleDeclaration" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidCustomPropertyAtRuleName" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidFunction" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidHtmlTagReference" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssInvalidImport" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssInvalidMediaFeature" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidPropertyValue" enabled="false" level="WARNING" enabled_by_default="false" editorAttributes="WARNING_ATTRIBUTES" />
<inspection_tool class="CssInvalidPseudoSelector" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssMissingComma" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssNegativeValue" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssNoGenericFontName" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssNonIntegerLengthInPixels" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="CssOverwrittenProperties" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssRedundantUnit" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssReplaceWithShorthandSafely" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="CssReplaceWithShorthandUnsafely" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="CssUnknownProperty" enabled="false" level="WARNING" enabled_by_default="false">
<option name="myCustomPropertiesEnabled" value="false" />
<option name="myIgnoreVendorSpecificProperties" value="false" />
<option name="myCustomPropertiesList">
<value>
<list size="0" />
</value>
</option>
</inspection_tool>
<inspection_tool class="CssUnknownTarget" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssUnknownUnit" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssUnresolvedClassInComposesRule" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssUnresolvedCustomProperty" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssUnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" /> <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" /> <inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" />
</profile> </profile>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="StylelintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>

View File

@@ -1,86 +0,0 @@
---
title: Anatomy of a Player Character
path: awq
cover:
showInHeader: false
meta:
publicationDate: 2025-10-08T08:43:00.000Z
status: draft
isFeatured: false
tags: []
seo:
noIndex: false
---
Every model shares some common abilities
## Main Characteristics
Characteristics define the innate abilities of a model, and each characteristic has two skills associated with it
- **Weapon Skill (WS):** Aptitude in landing and avoiding blows in close combat; governs _Melee_ and _Defence_
- **Ballistic Skill (BS):** Capability of Hand-Eye-Coordination; governs _Skirmish_ and _Evasion_
- **Strength (S):** Used for brute force, stamina and might; governs _Brawn_ and _Toil_
- **Toughness (T):** Resistance to physical Trauma; governs _Consume Alcohol_ and _Endurance_
- **Initiative(I):** Reflects speed of thought and perception; governs _Perception_ and _Outdoor Survival_
- **Dexterity (Dex):** Affinity for performing fine and delicate manual tasks; governs _Skullduggery_ and _Tradecraft_
- **Agility (Ag):** Physical coordination and natural athleticism; governs _Stealth_ and _Athletics_
- **Intelligence (Int):** Power of thought, analysis, and understanding; governs _Intuition_ and _Education_
- **Willpower (WP):** General strength if mind; governs _Cool_ and _Animal Handling_
- **Fellowship (Fel):** Ability to get on with people; governs _Leadership_ and _Charm_
## Secondary Characteristics
- **Corruption Threshold (CT):[^*]** Ability to withstand _Mutation_
- **Insanity Threshold (IT):[^*]** Ability to withstand _Insanity_
- **Wounds (W):** Number of wounds a model can endure before _out of play_
- **Movement (M):** Indicator of how far a model can move under normal conditionsA
- **Fate Points (FP):[^*]** Can be used to avoid certain death and dark fates
- **Luck Points (LP):[^*]** Used for Re-rolls
- **Attacks (A):** Indicator of the numbers of attacks a character can make in a single round
- **Magic (Mag):** Denotes the Model's Wizard Level
## Skills
Used in _Checks_
- **Melee (WS):** Used to make _Melee Attack_ actions
- **Defense (WS):** Used to defend against a model's _Melee Attack_ action
- **Shooting (BS):** Used to make _Ranged Attack_ actions with _Missile_ weapons
- **Throwing (BS):** Used to make _Ranged Attack_ actions with _Throwing_ weapons
- **Brawn (S):** Used for immediate feats of strength
- **Toil (T):** Used for prolonged manual labor
- **Consume Alcohol (T):** Used for resisting short-term hazards like alcohol and poison
- **Endurance (T):** Used to endure hardship, withstand deprivation, and survive harsh environments
- **Perception (I):** Used to notice things
- **Dodge (I):** Used to evade attacks and immediate hazards, e.g. Traps
- **Stealth (Ag):** Used for moving quietly and concealing
- **Athletics (Ag):** Used for running, jumping, climbing, and swimming
- **Streetwise (Dex):** Used for picking locks or pockets, disarming traps and other feats of Sleight of Hand
- **Crafting (Dex):** Used when crafting trappings
- **Intuition (Int):** Used for detecting subterfuge and determining value of objects
- **Education (Int):** Used for recalling relevant information
- **Cool (WP):** Used to remain calm under stress, resisting fear, and psychological coercion
- **Animal Handling (WP):** Used to charm, train, and care for animals
- **Leadership (Fel):** Used for intimidation, command, and coercing obedience; often resisted by _Discipline_
- **Charm (Fel):** Used for deceiving, blathering, haggling, gossiping; usually resisted by _Intuition_
## Traits
Inherent abilities often based on Species, examples include Flier (for flying Monsters), Dark Vision, Mutation, and so on
## Lore[^*]
Define what a character knows and specialist skills and come in the following categories
- _Academic:_ Represent various academic fields like Accountancy, Anatomy, History and Law
- _Cultural:_ Represent the knowledge of social groups and language
- _Enemy:_ Represent the knowledge of adversaries and how to effectively combat them
- _Environment:_ Represent the knowledge of surviving in various hazardous environment
- _Magic Lores:_ Represent the knowledge of the various forms of magic and enables casting spells from them+
- _Specialist Weapon Groups:_ Represent training with special weapons like Polearms, Two-Handed Swords, Blackpowder Guns
- _Trade Lores:_ Represent the knowledge and ability to create certain trappings or work in certain fields, example Blacksmith, Weaver, Engineer, Artist
- _Vehicle Lores:_ Represent knowledge in operating vehicles and mounts
## Talents[^*]
Represent certain knacks, tricks and innate abilities a character has, examples would be Ambidextrous, Aetheric Attunement, Menacing or Acute Senses
## Maneuvers[^*]
Represent special combat actions and tactics a character can employ during combat, examples include Formation Fighting, Disarm, and Shield Bash
## Careers[^*]
Represent building blocks of a character, that provide certain skills, lores, talents, maneuvers, etc.
[^*]: Only applicable for _Player Characters_

View File

@@ -1,139 +0,0 @@
---
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.

View File

@@ -1,142 +0,0 @@
{
"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": "⨳"
}
}
}
}
]
}

View File

@@ -1,15 +0,0 @@
{
"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"
}
]
}

View File

@@ -1,8 +0,0 @@
{
"name": "Was ist Was?",
"icon": {
"discriminant": "glyph",
"value": "‽"
},
"description": "General information and introductions for stuff"
}

View File

@@ -1,22 +1,14 @@
import { config } from '@keystatic/core'; import { config } from '@keystatic/core';
import NavSingleton from '@/keystatic/singletons/navigation';
import AuthorsCollection from '@/keystatic/collections/taxonomy/authors'; import AuthorsCollection from '@/keystatic/collections/taxonomy/authors';
import TagsCollection from '@/keystatic/collections/taxonomy/tags'; 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({ export default config({
storage: { storage: {
kind: 'local', kind: 'local',
}, },
singletons: { navigation: NavSingleton },
collections: { collections: {
authors: AuthorsCollection, authors: AuthorsCollection,
tags: TagsCollection, tags: TagsCollection,
meta_posts: MetaPostsCollection,
...awqCollections,
}, },
}); });

View File

@@ -10,19 +10,18 @@
"format": "prettier --write ." "format": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@keystar/ui": "^0.7.19",
"@keystatic/core": "^0.5.48", "@keystatic/core": "^0.5.48",
"@keystatic/next": "^5.0.4", "@keystatic/next": "^5.0.4",
"@markdoc/markdoc": "^0.5.4", "@markdoc/markdoc": "^0.5.4",
"marked": "^16.3.0", "@tailwindcss/postcss": "^4.1.13",
"next": "15.5.3", "next": "15.5.3",
"postcss": "^8.5.6",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0" "react-dom": "19.1.0",
"tailwindcss": "^4.1.13"
}, },
"devDependencies": { "devDependencies": {
"@csstools/postcss-global-data": "^3.1.0",
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@fullhuman/postcss-purgecss": "^7.0.2",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
@@ -32,20 +31,12 @@
"eslint-config-next": "15.5.3", "eslint-config-next": "15.5.3",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-mdx": "^3.6.2", "eslint-plugin-mdx": "^3.6.2",
"glob-all": "^3.3.1",
"postcss": "^8.5.6",
"postcss-easings": "^4.0.0",
"postcss-functions": "^4.0.2",
"postcss-import": "^16.1.1",
"postcss-mixins": "^12.1.2",
"postcss-nesting": "^13.0.2",
"postcss-preset-env": "^10.3.1",
"postcss-utilities": "^0.8.4",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"stylelint": "^16.24.0", "stylelint": "^16.24.0",
"stylelint-config-clean-order": "^7.0.0", "stylelint-config-clean-order": "^7.0.0",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-standard": "^39.0.0", "stylelint-config-standard": "^39.0.0",
"stylelint-config-tailwindcss": "^1.0.0",
"typescript": "^5" "typescript": "^5"
} }
} }

6895
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +1,6 @@
// postcss.config.js const config = {
import { purgeCSSPlugin } from '@fullhuman/postcss-purgecss'; plugins: {
import glob from 'glob-all'; '@tailwindcss/postcss': {},
import path from 'path'; },
import postcssGlobalData from '@csstools/postcss-global-data';
import postcssEasing from 'postcss-easings';
import postcssImport from 'postcss-import';
import postcssMixins from 'postcss-mixins';
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/lib/postcss/functions';
const plugins = [
postcssGlobalData({
files: ['./src/styles/globals/custom-media.css'],
}),
postcssMixins({
mixinsDir: './src/styles/mixins/',
}),
postcssImport(),
postcssPresetEnv({
stage: 1,
features: {
'nesting-rules': false,
'media-query-ranges': {
preserve: true,
},
},
}),
postcssEasing(),
postcssUtilities(),
postcssNesting(),
postcssFunctions({
functions: customFunctions,
}),
];
if (process.env.NODE_ENV === 'production') {
plugins.push(
purgeCSSPlugin({
content: glob.sync([
path.join(process.cwd(), 'src/**/*.{js,jsx,ts,tsx,css}'),
path.join(process.cwd(), 'content/**/*.{md,mdx}'),
]),
safelist: ['html', 'body'],
})
);
}
export default {
plugins,
}; };
export default config;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -1,15 +1,5 @@
import { MenuProvider } from '@/contexts/MenuContext';
import PageHeader from '@/components/Page/Header';
import PageMenu from '@/components/Page/Menu';
export default function SiteLayout({ export default function SiteLayout({
children, children,
}: Readonly<{ children: React.ReactNode }>) { }: Readonly<{ children: React.ReactNode }>) {
return ( return <main>{children}</main>;
<MenuProvider>
<PageHeader />
<PageMenu />
{children}
</MenuProvider>
);
} }

View File

@@ -1,29 +0,0 @@
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} />;
}

View File

@@ -1,82 +0,0 @@
.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;
}
}

View File

@@ -1,11 +1,7 @@
import styles from './page.module.css';
export default function Home() { export default function Home() {
return ( return (
<div className={styles.wrapper}> <div className="text-3xl font-bold underline">
<h1> Do not let us die in the dark night of this cold winter, Dave
DAVE! DAVE! Do Not Let Us Die In The Dark Night Of This Cold Winter!
</h1>
</div> </div>
); );
} }

View File

@@ -1,6 +1 @@
@layer reset, tokens, base, layout, content, components, utilities, animations; @import 'tailwindcss';
@import url("../styles/tokens.css");
@import url("../styles/globals/foundation.css");
@import url("../styles/globals/base.css");
@import url("../styles/globals/content.css");

View File

@@ -17,44 +17,42 @@ const blaka = Blaka({
const iosevkaSlab = localFont({ const iosevkaSlab = localFont({
src: [ src: [
{ {
path: '../../public/fonts/IosevkaSlabQp/IosevkaSlabQp-Regular.woff2', path: '../fonts/IosevkaSlabQp/IosevkaSlabQp-Regular.woff2',
weight: '400', weight: '400',
style: 'normal', style: 'normal',
}, },
{ {
path: '../../public/fonts/IosevkaSlabQp/IosevkaSlabQp-Bold.woff2', path: '../fonts/IosevkaSlabQp/IosevkaSlabQp-Bold.woff2',
weight: '700', weight: '700',
style: 'normal', style: 'normal',
}, },
{ {
path: '../../public/fonts/IosevkaSlabQp/IosevkaSlabQp-Heavy.woff2', path: '../fonts/IosevkaSlabQp/IosevkaSlabQp-Heavy.woff2',
weight: '900', weight: '900',
style: 'normal', style: 'normal',
}, },
], ],
variable: '--font-iosevka-slab', variable: '--font-iosevka-slab',
display: 'swap',
}); });
const iosevkaMono = localFont({ const iosevkaSans = localFont({
src: [ src: [
{ {
path: '../../public/fonts/IosevkaSansMono/IosevkaSansMono-Regular.woff2', path: '../fonts/IosevkaSansMono/IosevkaSansMono-Regular.woff2',
weight: '400', weight: '400',
style: 'normal', style: 'normal',
}, },
{ {
path: '../../public/fonts/IosevkaSansMono/IosevkaSansMono-Bold.woff2', path: '../fonts/IosevkaSansMono/IosevkaSansMono-Bold.woff2',
weight: '700', weight: '700',
style: 'normal', style: 'normal',
}, },
{ {
path: '../../public/fonts/IosevkaSansMono/IosevkaSansMono-Heavy.woff2', path: '../fonts/IosevkaSansMono/IosevkaSansMono-Heavy.woff2',
weight: '900', weight: '900',
style: 'normal', style: 'normal',
}, },
], ],
display: 'swap',
variable: '--font-iosevka-mono', variable: '--font-iosevka-mono',
}); });
@@ -69,11 +67,12 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html <html lang="en">
lang="en" <body
className={`${geistSans.variable} ${blaka.variable} ${iosevkaSlab.variable} ${iosevkaMono.variable}`} className={`${geistSans.variable} ${blaka.variable} ${iosevkaSlab.variable} ${iosevkaSlab.variable} antialiased`}
> >
<body>{children}</body> {children}
</body>
</html> </html>
); );
} }

View File

@@ -1,3 +0,0 @@
.article {
@mixin layout-wrapper;
}

View File

@@ -1,30 +0,0 @@
.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;
}
}
}
}

View File

@@ -1,50 +0,0 @@
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>
);
}

View File

@@ -1,10 +0,0 @@
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} />;
}

View File

@@ -1,26 +0,0 @@
.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);
}

View File

@@ -1,36 +0,0 @@
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>
);
}

View File

@@ -1,4 +0,0 @@
.container {
margin-bottom: var(--spacing-comfortable);
border-bottom: var(--size-4) solid var(--color-text-primary);
}

View File

@@ -1,71 +0,0 @@
.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;
}

View File

@@ -1,58 +0,0 @@
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>
);
}

View File

@@ -1,42 +0,0 @@
.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);
}

View File

@@ -1,48 +0,0 @@
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>
);
}

View File

@@ -1,13 +0,0 @@
.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);
}

View File

@@ -1,13 +0,0 @@
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>
);
}

View File

@@ -1,41 +0,0 @@
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>
);
}

View File

@@ -1,32 +0,0 @@
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>
);
}

View File

@@ -1,56 +0,0 @@
.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;
}
}
}

View File

@@ -1,39 +0,0 @@
'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>
);
}

View File

@@ -1,11 +0,0 @@
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>;
}

View File

@@ -1,27 +0,0 @@
.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);
}
}

View File

@@ -1,70 +0,0 @@
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>
);
}

View File

@@ -1,144 +0,0 @@
@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;
}
}
}
}
}
}

View File

@@ -1,32 +0,0 @@
import React from 'react';
import styles from './Callout.module.css';
interface CalloutProps {
type: 'default' | '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>
);
}

View File

@@ -1,24 +0,0 @@
.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);
}

View File

@@ -1,28 +0,0 @@
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),
}}
/>
))}
</>
);
}

View File

@@ -1,5 +0,0 @@
.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);
}

View File

@@ -1,10 +0,0 @@
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>;
}

View File

@@ -1,29 +0,0 @@
.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;
}

View File

@@ -1,34 +0,0 @@
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>
);
}

View File

@@ -1,3 +0,0 @@
.column {
position: relative;
}

View File

@@ -1,23 +0,0 @@
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>
);
}

View File

@@ -1,3 +0,0 @@
.row {
display: grid;
}

View File

@@ -1,29 +0,0 @@
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>
);
}

View File

@@ -1,69 +0,0 @@
.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);
}
}

View File

@@ -1,41 +0,0 @@
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>
);
}

View File

@@ -1,16 +0,0 @@
.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);
}
}

View File

@@ -1,17 +0,0 @@
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>
);
}

View File

@@ -1,25 +0,0 @@
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;

View File

@@ -1,77 +0,0 @@
@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; }
}
}

View File

@@ -1,48 +0,0 @@
'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>
);
}

View File

@@ -1,403 +0,0 @@
@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 {}
}
}

View File

@@ -1,106 +0,0 @@
@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);
}
}
}
}
}

View File

@@ -1,46 +0,0 @@
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;

View File

@@ -1,117 +0,0 @@
@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;
}
}
}

View File

@@ -1,66 +0,0 @@
'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>
);
}

View File

@@ -1,24 +0,0 @@
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} />
)}
</>
);
}

View File

@@ -1,57 +0,0 @@
@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);
}
}
}
}
}

View File

@@ -1,29 +0,0 @@
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>
);
}

View File

@@ -1,57 +0,0 @@
@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);
}
}

View File

@@ -1,26 +0,0 @@
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>
);
}

View File

@@ -1,63 +0,0 @@
@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);
}
}
}
}
}

View File

@@ -1,25 +0,0 @@
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>
);
}

View File

@@ -1,7 +0,0 @@
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} />;
}

View File

@@ -1,62 +0,0 @@
'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;
};

Some files were not shown because too many files have changed in this diff Show More