Change styles

master
Araozu 2023-09-14 22:15:35 -05:00
parent 301c8540b4
commit 5f5ae25060
24 changed files with 1141 additions and 349 deletions

View File

@ -3,7 +3,8 @@ output: static
template: static/template.html
headings:
h1: text-4xl mb-4 font-black
h2: text-3xl mt-8 mb-4 font-bold
h3: text-2xl mt-8 mb-4 font-medium
h1: text-3xl mb-4 font-display opacity-90
h2: text-2xl mt-10 mb-4 font-display opacity-90
h3: text-xl mt-10 mb-4 font-display opacity-90
file_tree_title_classes: "uppercase text-sm font-display"

View File

@ -0,0 +1,80 @@
# Comments
You may have noticed that in some code examples there are some
lines of text explaining the code:
```thp
// This is the variable
val person = "John"
```
Comments are used to explain what the code does. Anything written
in a comment is ignored by THP.
## Single line comments
As the name says, these comments span only 1 line. To create one write
two slashes together `//`. Everything after the slashes and before
the newline will be ignored.
```thp
// This is a single line comment
// You can write anything you want here, although it's usually
// used to describe the code
// The commend ends where the line ends,
so this line will not be ignored by THP, and will throw an error
```
## Multi line comments
As the name says, these comments can span multiple lines.
They have 2 components: a start and a end. To start a multiline comment
write a slash and asterisk `/*`, and to end the comment, the inverse `*/`
```thp
/*
This is a multiline comment.
I can write whatever I want here, and across multiple
lines, as long as I'm before the closing characters (* /)
*/
```
## Using comments to prevent code execution
Since comments are ignored by THP, we can use them to prevent certain
parts of the code from running.
Let's say we have this script:
```thp
print("Hello John")
print("How's your day going?")
```
If I wanted the 2nd line not to execute, I can use a comment:
```thp
print("Hello John")
// print("How's your day going?")
```
Now the second line is ignored, and the message is not printed.
The same can be done with multiline comments.
```thp
print("Hello John")
/*
print("How's your day going?")
*/
```

View File

@ -0,0 +1,127 @@
# Datatypes
Programming is comprised of 2 basic things: instructions and data.
If we compare a program to a cake recipe, the instructions are the steps
(**pour** flour, **add** water, **add** egg, **mix**) and the data are the ingredients
themselves (flour, water, egg).
![Image](Image)
Then, we can describe the recipe in terms of instructions and data. For example,
"mix flour and egg to produce dough".
![Image](Image)
All code is like a recipe: A list of instructions and data that, when combined
correctly, are able to transform a input to an output. Many advanced concepts
are just ways to organize and abstract those instructions and data.
## Classifying datatypes
As with food, datatypes can be classified. Just like there are fruits, vegetables,
oils, there are `numbers`, `text`, `"booleans"`.
### Int
Int is an abbreviation for "integer numbers", which are numbers without a fractional component.
They can be positive or negative.
In code, they are written just like numbers:
```thp
val age = 33
val children = 0
val money = -15000
```
You can use underscores to help you differentiate thousands, millions, etc.
However, there cannot be spaces in between
```thp
val studentDebt = 3_500_000_000_000 // Valid
val invalid = 3 500 000 000 000 // Invalid, will throw an error
```
The common operations can be done as in math:
```thp
val result1 = 10 + 20
val result2 = 1779 * 2 - (55 / 5)
```
### Float
Float is an abbreviation for "floating point numbers". In simplified terms, these are numbers
**with** a fractional component.
They are written with a dot `.` to separate the whole part and the fraction.
```thp
val pi = 3.141592
val epsilon = 2.775557
```
Underscore can also be used:
```thp
val reallyLongNumber = 23_870_000.443_879
```
Common operations can be performed the same way:
```thp
val value1 = 552.23 - 32
val value2 = 3.2 * (-0.22 + 23.334) / 0.5
// etc.
```
### String
A string of letters (technically, characters). Strings are used to represent text,
and are wrapped in quotation marks.
```thp
val name = "John"
val lastName = "Doe"
val greeting = "Hello"
```
#### Concatenation
Strings cannot be added, substracted, multiplied, divided.
They have another operation, called "concatenation".
Concatenation "joins" two strings, for example, the concatenation of
`"human"` and `"kind"` is `"humankind"`.
As it is a common operation, string concatenation reuses the operation
for addition `+`.
```thp
val string = "human" + "kind"
```
### Boolean
A boolean represents something that can only be in one of two states.
Booleans are useful in conditionals, which will be explained later.
```thp
val condition = true
val isSunny = false
if isSunny {
doSomething()
}
```

View File

@ -0,0 +1,60 @@
# Hello, world!
This pages shows you how to write and run your first THP script.
## Prerequisites
You will need to have PHP and THP installed. If you haven't already, see the
install page.
## Writing the program
Create a new file called `hello.thp`, and inside write the following (you can
copy and paste):
```thp
print("Hello, world!")
```
Then, save the code.
## Running the program
Open a terminal in the folder where you created the file `hello.thp`.
Then write the following command and press enter:
```sh
thp hello.thp
```
This will run the program, and produce the following result:
```sh
Hello, world!
```
Congratulations! You just wrote your first THP program!
## Explaining the program
Now let's understand the code.
```thp
print("Hello, world!")
```
There are 2 essential components:
- `print` - print is a "function", it takes some value and performs some action with it.
In this case, it takes a text and displays it in the terminal.
- `"Hello, world!"` - Is the text that the function `print` takes, and displays in
the terminal. Note that it is enclosed in quotation marks.
## What to do next
You can now experiment with the program you wrote. What happens if you change the text?
What if you don't put quotation marks? And what are the parenthesis for?
To continue learning about THP, continue to the next page, where you'll learn
about "datatypes".

64
md/learn/basics/io.md Normal file
View File

@ -0,0 +1,64 @@
# Input & Output
At the end of the day, a program takes some input,
and transform it to some output. We will explore how to
receive data, and also present it.
## Output
There are many ways to show the result of our programs.
One of them is called `standard output`, which in simplified
terms, is the terminal from where we run our program.
### `print`
Create an empty file, and name it `hello.php`. We will create
a program that shows a result to our screen.
Inside `hello.php` write the following code:
```thp
print("Hello")
```
Then, open a terminal and run the program with the `thp` command.
After you press ENTER, you will see in the terminal the text "Hello".
```sh
$ thp hello.thp # Copy this command and run it
Hello # This is the output of our program
```
The thp function `print` allows us to display something in the terminal.
If you change the `hello.php` file, and run it, you'll see how it changes.
```thp
print(322)
```
```sh
$ thp hello.php
322
```
Or, if you copy `print` multiple times, each will show something in the screen.
```thp
print("Hello")
print("This is an example")
print("of having many prints")
```
```sh
$ thp hello.php
Hello
This is an example
of having many prints
```
### Using `print` in a larger program

View File

@ -0,0 +1,5 @@
# Operators
Some common operators

View File

@ -0,0 +1,72 @@
# The compiler
The compiler is the program that takes your THP code and converts it
into something that the computer can run (in this case, PHP code).
The compiler reads your code and tries to understand it.
When it can't understand some part, it will ask you to clarify what
you meant, with an error.
## Compile time vs runtime
Compile time is when you build your program.
Runtime is when you run your program.
To make an analogy with cars, compile time would be the factory,
while runtime would be using the finished car.
The compiler is very strict. If it finds any errors during compile time
it will refuse to build the program.
### Compile time errors
Compile time errors are errors that happen while the compiler is
building your program. In the car analogy, a compile error would happen
if there's an error in the "blueprint"
### Runtime errors
These are errors that happen to the built program, or in this case,
the finished car. It would be like the car blowing up while on the highway.
To minimize the amount of runtime errors the compiler tries to catch them all
during compile time. It's better if it fails in the factory than in the
real world.
## The compiler and datatypes
The first measure the compiler takes is to check that all the datatypes
match. If an operation requires a number, you can't just give it a string.
For example, if we wanted to add a number to a string, we would get an
error:
```thp
50 + "hello" // Error: The `+` operator expects two Numbers,
// but an String was found.
```
This way, we can assure that many errors are avoided (since it doesn't
really make sense to add a number to a text).

View File

@ -0,0 +1,139 @@
# Variables
Variables allows us to store values under a name, so that we can use them
later.
For example, in a program we could have the name of our user, and use it
multiple times:
```thp
print("Hello, where's Joe?")
print("How old is Joe?")
print("Do you know if Joe has kids?")
```
We are using the same value `Joe` many times. Each time we use it we have
to type `Joe`. But what happens if we needed to use a different name?
We'd have to change the name everywhere in our code!
```thp
print("Hello, where's Jane?")
print("How old is Jane?")
print("Do you know if Jane has kids?")
```
## Variables to the rescue
With a variable we can store values so we can use them later, or use them
in multiple times.
In the previous code, we can use a variable to store the person's name,
and then use it everywhere.
```thp
// This is the variable
val person = "John"
print("Hello, where's {person}?")
print("How old is {person}?")
print("Do you know if Joe has {person}?")
```
Now, instead of writing `"John"` every time, we write the name of the
variable instead.
If we wanted to change the person's name to "Jane", we just need to change
it in one place: the variable
```thp
// We change this
val person = "Jane"
// And all these lines will use the new value
print("Hello, where's {person}?")
print("How old is {person}?")
print("Do you know if Joe has {person}?")
```
## Variable rules
To use a variable we do the following:
- Write the special word `val`
- Write the name of our variable
- Write the equal sign `=`
- Write the value of our variable
```thp
val person = "Jane"
/* --- ------ - ------
| | | +- The value of our variable
| | +----- The equal sign
| +---------- The name of our variable
+--------------- The special word (keyword) val
*/
```
The value can be anything: ints, floats, string, bools, even other variables and operations!
```thp
val variable_1 = 322
val variable_2 = 123.456
val variable_3 = "a text"
val variable_4 = false
val variable_5 = variable_1 + variable 2
```
## Variable name rules
- Starts with a lowercase letter (a-z) or underscore (`_`)
- Then can have any letter (a-zA-Z), underscore (`_`) or number (0-9)
- Cannot have spaces
- Cannot have the same name as a keyword (for example, the `val` keyword)
Some examples of valid variable names:
```thp
val name = ...
val age = ...
val my_name = ...
val many_words_joined_by_underscores = ...
val person_2 = ...
val person_3 = ...
```
Some invalid variables and why they are invalid:
```thp
val 1name = ... // Invalid: starts with a number
val 123_person = ... // Invalid: starts with a number
val val = ... // Invalid: same name as a keyword (val)
val Person = ... // Invalid: starts with an uppercase letter
val person name = ... // Invalid: contains whitespace
val +@name = ... // Invalid: contains non-letters (+@)
val name🫠 = ... // Invalid: contains emoji (🫠)
```
## Variable reassignment
When you create a new variable with the same name of an old variable,
the old is "replaced" with the new one.
```thp
val person_name = "John"
print(person_name) // Will print "John"
val person_name = "Jane"
print(person_name) // Will print "Jane"
```
This will have some implications on the future, but for now you should
now that you will always use the value of the last variable you define.

View File

View File

@ -0,0 +1,3 @@
# Conditionals

View File

View File

View File

View File

View File

@ -0,0 +1,71 @@
# Function parameters
## Immutable reference
```thp
fun add_25(Array[Int] numbers) {
numbers.push(25) // Error: `numbers` is immutable
}
```
When using a regular type as a parameter, only it's immutable
properties can be used inside the function
```thp
fun count(Array[Int] numbers) -> Int {
val items_count = numbers.size() // Ok, `size` is pure
items_count
}
```
## Mutable reference
```thp
fun add_25(&Array[Int] numbers) {
numbers.push(25) // Ok, will also mutate the original array
}
```
Placing a `&` before the type makes the parameter a mutable
reference. Mutable methods can be used, and the original
data **will** be mutated.
The callee *must* also use `&`.
```thp
val numbers = Array(1, 2, 3, 4)
add_25(&numbers) // Pass `numbers` as reference.
print(numbers(4)) // `Some(25)`
```
## Clone
```thp
fun add_25(clone Array[Int] numbers) {
numbers.push(25) // Ok, the original array is unaffected
}
```
Using the `clone` keyword before the type creates a mutable copy
of the parameter. The original data will **not** be mutated.
```thp
val numbers = Array(1, 2, 3, 4)
add_25(&numbers) // Pass `numbers` as reference.
print(numbers(4)) // None
```

201
md/learn/ideas/idea_1.md Normal file
View File

@ -0,0 +1,201 @@
# Idea 1
var x = 20
val y = 30
type Something = ...
Something s1 = ...
Something s2 = s1
// Passes `some` by reference, but it's immutable. Cannot call mutable methods
// or use it in mutable operations
fun do_something(Something some) -> Bool {}
do_something(s1)
// Passes `some` by reference, and it's mutable. Can call mutable methods
// or use it in mutable operations
fun do_something(&Something some) -> Bool {}
do_something(&s1)
var arr1 = Array(10, 20, 30)
var arr2 = &arr1
Owned/Reference Mutable
Type Owned n
&Type Reference n
mut Type Owned y
&mut Type Reference y
Copy/Reference Mutable Equivalent
Some Copy n 1 (technically) references the other data
&Some Reference n 1 References the other data
mut Some Copy y 2 Creates a __mutable__ copy
&mut Some Reference y 3 References the other data, __mutable__
## `Array[A]::map`
```thp
fun map[B](this, (A) -> B callback) -> Array[B]
```
Applies `callback` to all the elements of this array, and
returns those new values in a new array.
### Example
```thp
val numbers = Array(1, 2, 3, 4, 5)
val numbers_squared = numbers.map {it ** 2}
print(numbers_squared) // Array(1, 4, 9, 16, 25)
numbers.map(fun(v) {
v - 2
})
```
## `Array[A]::reduce`
```thp
fun reduce[B](
this,
B initial,
(A previous, B current) -> B callback,
) -> B
```
Iteratively reduce the array to a single value using `callback`.
### Example
```thp
val numbers = Array(1, 2, 3, 4, 5)
val sum = numbers.reduce(0, \+)
val sum = numbers.reduce(0) {$1 + $2}
val sum = numbers.reduce(0, fun(prev, curr) {prev + curr})
print(sum) // 15
```
```thp
val numbers = Array(1, 2, 3, 4, 5)
val sum = numbers.reduce("", fun(prev, curr) {prev + curr})
val sum = numbers.reduce("") {prev, curr -> prev + curr}
print(sum) // "12345"
```
```thp
// Functor
fun fmap(
(A) -> B,
f[A],
) -> f[B]
fun (<$)(
A,
f[B],
) -> f[A]
// Applicative
fun pure(A) -> f[A]
fun (<*>)(
f[A -> B],
f[A],
) -> f[B]
fun (*>)(
f[_],
f[B],
) -> f[B]
fun (<*)(
f[A],
f[_],
) -> f[A]
// Monad
fun (>>=)[m, A, B](
m[A],
(A) -> m[B],
) -> m[B]
(Array[Int], Int -> Array[String]) -> Array[String]
val result = Array(1, 2, 3, 4, 5) >>= {Array($.into[String]())}
print(result) // Array("1", "2", "3", "4", "5")
```
```thp
Option[Int] result = "322".try_into()
Option[Int] result_halved = result >>= {Some($ / 2)}
print(result_halved) // Some(161)
Option[Int] result = "abc".try_into()
Option[Int] result_halved = result >>= {Some($ / 2)}
print(result_halved) // None
```
```thp
fun (<$>)[m, A, B](
(A) -> B,
m[A],
) -> m[B]
fun half(Int x) -> Int {
x / 2
}
Option[Int] result = "322".try_into()
Option[Int] result_halved = result <$> half
print(result_halved) // Some(161)
Option[Int] result = "abc".try_into()
Option[Int] result_halved = result <$> half
print(result_halved) // None
```
```thp
fun (>>)[A, B, C](
(A) -> B,
(B) -> C,
) -> (A) -> C
val f1 = add1 >> times2
f1(5) // 12
```

View File

@ -4,6 +4,13 @@ Welcome to the documentation of the THP programming languague.
THP is a new programming language that compiles to PHP.
<br>
This page discusses some of the design decitions of the language,
if you want to install THP go to the installation guide](/installation-guide)
If you want to learn the language, go to the learn section.
## Goals
- Bring static typing to PHP: Not just type hints, not use `mixed` for everything

View File

@ -4,3 +4,25 @@ has_index: true
children:
- path: index
name: Index
- path: install
name: Install
- path: basics
name: Basics
children:
- path: hello-world
name: Hello, world!
- path: datatypes
name: Datatypes
- path: the-compiler
name: The compiler
- name: Variables
path: variables
- path: io
name: Input & Output
- path: comments
name: Comments
- name: Flow control
path: flow-control
children:
- name: Conditionals
path: conditionals

10
md/learn/install.md Normal file
View File

@ -0,0 +1,10 @@
# Install
## From scratch
Also install php (through XAMPP in windows/mac, php in linux) and Composer.
## With composer
TBD, the user should be able to just run `composer require thp` and
the proper binary should be intalled.

File diff suppressed because it is too large Load Diff

View File

@ -12,157 +12,71 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Sans+Condensed:wght@400;500;600;700;800;900&family=Fugaz+One&family=Inconsolata&family=Inter&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Fira+Code&family=Josefin+Sans:ital,wght@0,400;1,700&display=swap" rel="stylesheet">
</head>
<body class="bg-c-bg text-c-text">
<div class="px-2 grid grid-cols-2 gap-4 relative">
<div class="text-center h-screen overflow-hidden sticky top-0 left-0">
<div class="rounded-md m-4 p-4 bg-c-primary"
style="box-shadow: 0 0 10px -4px var(--c-box-shadow);"
>
<h1
class="py-8 font-display"
style="font-size: 10rem; text-shadow: 3px 3px 0 var(--c-bg)"
>
thp 23
</h1>
<h1 class="text-6xl text-center py-8 font-display"
style="text-shadow: 1px 1px 0 var(--c-bg)"
>
Typed Hypertext Processor
</h1>
<p class="text-2xl">
A <b>modern, consistent</b> typed language for PHP.
</p>
<div class="m-4 relative mb-2 button-effect inline-block bg-c-bg rounded">
<button class="button-effect-receiver py-2 px-4 rounded font-display
bg-c-text text-c-primary"
>
Get started
</button>
</div>
<div class="m-4 relative mb-2 button-effect inline-block bg-c-bg rounded">
<a class="button-effect-receiver py-2 px-4 rounded font-display inline-block
bg-c-text text-c-primary"
href="/learn/"
>
Learn
</a>
</div>
</div>
<div
class="max-w-[70rem] mx-auto py-28 grid grid-cols-2 gap-4"
>
<div>
<h1 class="font-display font-bold italic text-[6rem] leading-tight">
Typed
<br>
Hypertext
<br>
Processor
</h1>
<p class="font-display text-3xl pt-4">
Syntax, stdlib and types for PHP
</p>
</div>
<div>
<div class="rounded-md my-4 mx-2 p-4 bg-c-primary"
style="box-shadow: 0 0 10px -4px var(--c-box-shadow);"
<div
class="bg-[var(--code-theme-bg-color)] p-8 rounded-lg"
>
<h2 class="text-2xl mb-2">
A truly Static Type System
</h2>
<p>
thp keeps track of the datatype of all variables, and allows
you to have complex types and type inference.
</p>
<svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg>
<br>
<pre
class="p-2 my-2 rounded-md bg-c-bg language-misti"
style="box-shadow: inset 0 0 10px -5px var(--c-box-shadow);"
>type <span class="token class-name">Person</span> = {
<span class="token class-name">String</span> name,
<span class="token class-name">String</span> lastName,
<span class="token class-name">Int</span> age,
class="rounded-md bg-c-bg language-misti"
style="padding: 0 !important;"
><span class="token keyword">use</span> <span class="token class-name">Globals</span>::<span class="token class-name">POST</span>
<span class="token keyword">use</span> <span class="token class-name">JSON</span>
<span class="token keyword">val</span> person_id = <span class="token class-name">POST</span>::get(<span class="token string">"person_id"</span>) ?: <span class="token keyword">die</span>
<span class="token keyword">match</span> <span class="token class-name">Person</span>::find_by_id(person_id) {
<span class="token class-name">Ok</span>(person) => {
<span class="token class-name">JSON</span>::encode(person)
}
<span class="token class-name">Err</span>(e) => {
<span class="token class-name">JSON</span>::encode(Obj {<span class="token string">"error"</span>: e})
}
}
<span class="token class-name">Option</span>[<span class="token class-name">Array</span>[<span class="token class-name">Person</span>]] persons = <span class="token class-name">PersonManager</span>::getAll()
print(<span class="token string">"There are {persons?.length ?? 0} persons registered!"</span>)</pre>
</pre>
</div>
<div class="rounded-md my-4 mx-2 p-4 bg-c-primary"
style="box-shadow: 0 0 10px -4px var(--c-box-shadow);"
>
<h2 class="text-2xl mb-2">
A <b>new</b> stdlib for PHP
</h2>
<p>
thp groups all global variables and function of PHP into modules,
to allow easy access and organization.
<br>
Function names, parameters and return types are improved, and you
can treat primitive types as objects.
</p>
<pre
class="p-2 my-2 rounded-md bg-c-bg language-misti"
style="box-shadow: inset 0 0 10px -5px var(--c-box-shadow);"
><span class="token keyword">val</span> name = <span class="token string">"John Doe"</span>
<span class="token keyword">val</span> lastNamePos = name.indexOf(<span class="token string">"Doe"</span>) <span class="token comment">// Instead of `strpos`</span>
<span class="token keyword">val</span> letters = name.split(<span class="token string">""</span>) <span class="token comment">// Instead of `str_split` or `explode`</span>
<span class="token keyword">val</span> notALetters = letters.filter { $ != <span class="token string">"a"</span> } <span class="token comment">// Instead of `array_filter`</span></pre>
</div>
<div class="rounded-md my-4 mx-2 p-4 bg-c-primary"
style="box-shadow: 0 0 10px -4px var(--c-box-shadow);"
>
<h2 class="text-2xl mb-2">
Sound null safety & Pattern Matching
</h2>
<p>
All null values must be explicitly marked and handled,
avoiding many errors, via the <code>Option</code> ADT.
<br>
<br>
Also, functions that return <code>false</code> as
an error state now return an <code>Option</code>,
and exceptions return a <code>Result</code> instead.
</p>
<pre
class="p-2 my-2 rounded-md bg-c-bg language-misti"
style="box-shadow: inset 0 0 10px -5px var(--c-box-shadow);"
><span class="token keyword">val</span> allowedColors = <span class="token class-name">Array</span>(<span class="token string">"red"</span>, <span class="token string">"blue"</span>, <span class="token string">"green"</span>)
<span class="token keyword">val</span> response = match colors.search(<span class="token string">"purple"</span>) {
<span class="token class-name">Some</span>(_) -&gt; <span class="token string">"purple is allowed!"</span>
<span class="token class-name">None</span> -&gt; <span class="token string">"purple is not allowed"</span>
}
print(response)</pre>
<pre
class="p-2 my-2 rounded-md bg-c-bg language-misti"
style="box-shadow: inset 0 0 10px -5px var(--c-box-shadow);"
>use <span class="token class-name">PDO</span>
use <span class="token class-name">Globals</span>::<span class="token class-name">Env</span>
<span class="token keyword">val</span> #(<span class="token class-name">Some</span>(dbUri), <span class="token class-name">Some</span>(dbUser), <span class="token class-name">Some</span>(dbPassword)) = #(
<span class="token class-name">Env</span>::get(<span class="token string">"DB_URI"</span>),
<span class="token class-name">Env</span>::get(<span class="token string">"DB_USERNAME"</span>),
<span class="token class-name">Env</span>::get(<span class="token string">"DB_PASSWORD"</span>),
)
else {
die(<span class="token string">"All 3 db environment variables must be set."</span>)
}
match <span class="token class-name">PDO</span>(dbUri, dbUser, dbPassword) {
<span class="token class-name">Ok</span>(connection) -&gt; { /* db operations */ }
<span class="token class-name">Err</span>(pdoException) -&gt; { /* handle exception */ }
}</pre>
</div>
</div>
</div>
<div
class="max-w-[70rem] mx-auto text-center"
>
<a
class="inline-block font-display text-2xl border-4 border-[#F5A9B8] py-3 px-8 mx-6 rounded"
href="/learn/"
>
Learn
</a>
<a
class="inline-block font-display text-2xl border-4 border-[#5BCEFA] py-3 px-8 mx-6 rounded"
href="/install/"
>
Install
</a>
</div>
</body>
</html>

View File

@ -13,16 +13,23 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Sans+Condensed:wght@400;500;600;700;800;900&family=Fugaz+One&family=Inconsolata&family=Inter&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Fira+Code&family=Josefin+Sans:ital,wght@0,400;1,700&display=swap" rel="stylesheet">
<style>
html {
font-size: 20px;
}
</style>
</head>
<body class="bg-c-bg text-c-text">
<div class="grid grid-cols-[10rem_12rem_auto] gap-2">
<div class="grid grid-cols-[10rem_12rem_auto] gap-2 max-w-[70rem] mx-auto">
<div class="p-2 overflow-x-scroll max-h-screen sticky top-0">
<div class="relative mb-2 button-effect">
<a class="button-effect-receiver inline-block w-[9rem] p-4 bg-c-primary rounded-md"
href="/"
>
<h1
class="py-8 font-display text-6xl text-center"
class="py-8 font-display text-6xl text-center font-bold italic"
style="text-shadow: 3px 3px 0 var(--c-bg)"
>
thp
@ -32,13 +39,13 @@
<div class="w-full h-full bg-c-text absolute top-0 left-0 -z-10 rounded-md"></div>
</div>
<nav class="rounded-md p-4 bg-c-primary">
<nav class="rounded-md p-4 border-2 border-[#F5A9B8]">
{{pages}}
</nav>
</div>
<div class="py-2 max-h-screen overflow-x-scroll sticky top-0">
<nav class="rounded-md p-4 bg-c-secondary">
<nav class="rounded-md p-4 border-2 border-[#5BCEFA]">
<h2 class="text-2xl">On this page</h2>
<br>
@ -46,7 +53,7 @@
{{sidebar}}
</nav>
</div>
<main class="markdown p-4 my-2">
{{markdown}}
</main>

View File

@ -16,9 +16,10 @@ module.exports = {
}
},
fontFamily: {
"mono": ["Inconsolata", "Iosevka", "monospace"],
"display": ["'Fugaz One'", "Inter", "'Fira Sans Condensed'", "sans-serif"],
"mono": ["'Fira Code'", "Inconsolata", "Iosevka", "monospace"],
"display": ["'Josefin Sans'", "'Fugaz One'", "sans-serif"],
"body": ["'Fira Sans Condensed'", "Inter", "sans-serif"],
"nav-title": ["'Josefin Sans'", "'Fugaz One'", "sans-serif"],
},
},
plugins: [],

View File

@ -33,7 +33,7 @@ body {
}
pre, code {
font-family: Inconsolata, monospace;
font-family: 'Fira Code', Inconsolata, monospace;
}
.button-effect-receiver {
@ -44,6 +44,9 @@ pre, code {
transform: translateX(-3px) translateY(-3px);
}
.markdown p {
margin: 0.75rem 0;
}
/* Used by headers generated from markdown */
.heading-linked :hover::after{