diff options
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | Makefile | 28 | ||||
| -rw-r--r-- | README.md | 7 | ||||
| -rw-r--r-- | cmd/http/errors.go | 23 | ||||
| -rw-r--r-- | cmd/http/handle.go | 65 | ||||
| -rw-r--r-- | cmd/http/load.go | 106 | ||||
| -rw-r--r-- | cmd/http/main.go | 79 | ||||
| -rw-r--r-- | cmd/http/middle.go | 32 | ||||
| -rw-r--r-- | cmd/http/render.go | 39 | ||||
| -rw-r--r-- | go.mod | 3 | ||||
| -rw-r--r-- | html/about.tmpl.html | 16 | ||||
| -rw-r--r-- | html/index.tmpl.html | 17 | ||||
| -rw-r--r-- | html/master.tmpl.html | 47 | ||||
| -rw-r--r-- | html/projects/Husqvarna_Val.tmpl.html | 22 | ||||
| -rw-r--r-- | static/style.css | 450 | 
15 files changed, 938 insertions, 0 deletions
| diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9fac8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store + +/http +!cmd/http diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..963a540 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +.DEFAULT_GOAL := run + +http_path := ./cmd/http +bins := ./http + +fmt: +	go fmt $(http_path) +.PHONY:fmt + +lint: fmt +	golint $(http_path) +.PHONY:lint + +vet: lint +	go vet $(http_path) +.PHONY:vet + +run: vet +	go run $(http_path) +.PHONY:run + +build: +	go build $(http_path) +.PHONY:build + +clean: +	rm -fv $(bins) +.PHONY:clean @@ -1,3 +1,10 @@  # Chelsea's Portfolio Site  Collection of works by Chelsea Rogers, graphic designer. + +## Usefull Commands + +Image Magick +* magick image.jpg -resize 1000x1000 image_1000.jpg +* for file in $(ls -1 | sed 's/\.[^.]*$//'}; do magick "file.jpg" -resize 1000x1000 "$(echo $file)_1000.jpg"; done + diff --git a/cmd/http/errors.go b/cmd/http/errors.go new file mode 100644 index 0000000..9406a9a --- /dev/null +++ b/cmd/http/errors.go @@ -0,0 +1,23 @@ +package main + +import ( +	"fmt" +	"net/http" +	"runtime/debug" +) + +func (app *application) serverError(w http.ResponseWriter, err error) { +	trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack()) +	app.errorLog.Output(2, trace) + +	http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) +} + +func (app *application) clientError(w http.ResponseWriter, status int) { +	app.errorLog.Printf("Clent error: %d\n", status) +	http.Error(w, http.StatusText(status), status) +} + +func (app *application) notFound(w http.ResponseWriter) { +	app.clientError(w, http.StatusNotFound) +} diff --git a/cmd/http/handle.go b/cmd/http/handle.go new file mode 100644 index 0000000..9c1f62a --- /dev/null +++ b/cmd/http/handle.go @@ -0,0 +1,65 @@ +package main + +import ( +	"net/http" +	"strings" +) + +func (app *application) home(w http.ResponseWriter, r *http.Request) { +	w.Header().Set("Content-Type", "text/html; charset=utf-8") + +	path := strings.Split(r.URL.Path, "/") +	if path[1] != "" { +		app.notFound(w) +	} else { +		p, err := app.aggregate("html/projects") +		if err != nil { +			app.serverError(w, err) +		} + +		err = renderTemplate(w, "index", p) +		if err != nil { +			app.serverError(w, err) +		} +	} +} + +func (app *application) post(w http.ResponseWriter, r *http.Request) { +	w.Header().Set("Content-Type", "text/html; charset=utf-8") + +	path := strings.Split(r.URL.Path, "/") +	if len(path) > 4 { +		app.notFound(w) +	} else if len(path) == 4 && path[3] == "" { +		http.Redirect(w, r, "/"+path[1]+"/"+path[2], http.StatusFound) +	} else { +		post, err := app.readFile("html" + strings.TrimSuffix(r.URL.Path, "/") + ".tmpl.html") +		if err != nil { +			app.notFound(w) +			return +		} + +		var posts []*Post +		posts = append(posts, post) +		p := &Posts{Contents: posts} + +		err = renderTemplate(w, path[1]+"/"+path[2], p) +		if err != nil { +			app.serverError(w, err) +		} +	} +} + +func (app *application) about(w http.ResponseWriter, r *http.Request) { +	w.Header().Set("Content-Type", "text/html; charset=utf-8") + +	path := strings.Split(r.URL.Path, "/") +	if path[1] != "about" { +		app.notFound(w) +	} else { +		err := renderTemplate(w, "about", nil) +		if err != nil { +			app.serverError(w, err) +		} +	} +} diff --git a/cmd/http/load.go b/cmd/http/load.go new file mode 100644 index 0000000..d63d7ce --- /dev/null +++ b/cmd/http/load.go @@ -0,0 +1,106 @@ +package main + +import ( +	"os" +	"regexp" +	"sort" +	"strings" +) + +// Post struct contains necessary data for a post +type Post struct { +	FileName string +	Title    string +	Date     string +	Tags     []string +	Image    string +} + +// Posts stuct contains a collection of type Post +type Posts struct { +	Contents []*Post +} + +// Read all found files and load them into a stuct +func (app *application) aggregate(location string) (p *Posts, err error) { +	var posts []*Post + +	files, err := os.ReadDir(location) +	if err != nil { +		return nil, err +	} + +	// Loop over every file in the directory and read the contents. +	for _, file := range files { +		if !file.IsDir() && strings.HasSuffix(file.Name(), ".tmpl.html") { +			newPost, err := app.readFile(location + "/" + file.Name()) +			if err != nil { +				return nil, err +			} + +			posts = append(posts, newPost) +		} +	} + +	sort.Slice(posts, func(i, j int) bool { +		return posts[i].Date > posts[j].Date +	}) + +	return &Posts{Contents: posts}, nil +} + +func (app *application) readFile(location string) (p *Post, err error) { +	fileContent, err := os.ReadFile(location) +	if err != nil { +		return nil, err +	} + +	var post *Post = new(Post) + +	fileName := strings.TrimSuffix(strings.Split(location, "/")[2], ".tmpl.html") + +	// title +	title := strings.ReplaceAll(fileName, "_", " ") + +	// date +	datePattern := regexp.MustCompile(`{{define "uploaded-on"}}(\d{4}-\d{2}-\d{2}){{end}}`) +	dateMatching := datePattern.FindStringSubmatch(string(fileContent)) + +	var date string +	if len(dateMatching) > 1 { +		date = dateMatching[1] +	} else { +		date = "" +	} + +	// tags +	tagsPattern := regexp.MustCompile(`{{define "keywords"}}([\w\s]+){{end}}`) +	matchingTags := tagsPattern.FindStringSubmatch(string(fileContent)) + +	var tags []string +	if len(matchingTags) > 1 { +		tags = strings.Fields(matchingTags[1]) +	} else { +		tags = []string{} +	} + +	// thumbnail image +	imagePattern := regexp.MustCompile(`<img src="(.+)" class="mainImage"( alt="(.+)")* />`) +	matchingImage := imagePattern.FindStringSubmatch(string(fileContent)) + +	var image string +	if len(matchingImage) > 1 { +		image = matchingImage[0] +	} else { +		image = "" +	} + +	post.FileName = fileName +	post.Title = title +	post.Date = date +	post.Tags = tags +	post.Image = image + +	return post, nil + +} diff --git a/cmd/http/main.go b/cmd/http/main.go new file mode 100644 index 0000000..661e6fc --- /dev/null +++ b/cmd/http/main.go @@ -0,0 +1,79 @@ +package main + +import ( +	"flag" +	"log" +	"net/http" +	"os" +	"strings" +) + +var ( +	fullchain = "/etc/letsencrypt/live/alexscerba.com/fullchain.pem" +	privkey   = "/etc/letsencrypt/live/alexscerba.com/privkey.pem" +) + +type application struct { +	errorLog *log.Logger +	infoLog  *log.Logger +} + +func (app *application) httpsRedirect(w http.ResponseWriter, req *http.Request) { +	// remove/add not default ports from req.Host +	target := "https://" + req.Host + req.URL.Path +	if len(req.URL.RawQuery) > 0 { +		target += "?" + req.URL.RawQuery +	} +	app.infoLog.Printf("redirect to: %s", target) +	http.Redirect(w, req, target, +		// see comments below and consider the codes 308, 302, or 301 +		http.StatusMovedPermanently) +} + +func (app *application) wwwRedirect(h http.Handler) http.Handler { +	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +		if !strings.HasPrefix(r.Host, "www.") { +			http.Redirect(w, r, "https://www."+r.Host+r.RequestURI, 302) +			return +		} + +		h.ServeHTTP(w, r) +	}) +} + +func main() { +	addr := flag.String("addr", ":4000", "HTTP Network Address") +	flag.Parse() // required before flag is used + +	infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime) +	errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile) + +	app := &application{ +		errorLog: errorLog, +		infoLog:  infoLog, +	} + +	mux := http.NewServeMux() + +	fs := http.FileServer(http.Dir("./static")) +	mux.Handle("/static/", http.StripPrefix("/static/", fs)) + +	mux.HandleFunc("/about", app.about) +	mux.HandleFunc("/about/", app.about) +	mux.HandleFunc("/projects", app.post) +	mux.HandleFunc("/projects/", app.post) +	mux.HandleFunc("/", app.home) + +	if *addr == ":443" { +		www := app.wwwRedirect(mux) + +		infoLog.Printf("Starting TLS server on %s...\n", *addr) +		go http.ListenAndServe(":80", www) +		err := http.ListenAndServeTLS(*addr, fullchain, privkey, gzipHandler(www)) +		log.Fatal(err) +	} else { +		infoLog.Printf("Starting server on %s...\n", *addr) +		err := http.ListenAndServe(*addr, gzipHandler(mux)) +		log.Fatal(err) +	} +} diff --git a/cmd/http/middle.go b/cmd/http/middle.go new file mode 100644 index 0000000..29b49a6 --- /dev/null +++ b/cmd/http/middle.go @@ -0,0 +1,32 @@ +package main + +import ( +	"compress/gzip" +	"io" +	"net/http" +	"strings" +) + +type gzipResponseWriter struct { +	io.Writer +	http.ResponseWriter +} + +func (grw gzipResponseWriter) Write(data []byte) (int, error) { +	return grw.Writer.Write(data) +} + +func gzipHandler(next http.Handler) http.Handler { +	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +		if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { +			next.ServeHTTP(w, r) +			return +		} + +		w.Header().Set("Content-Encoding", "gzip") +		gzipWriter := gzip.NewWriter(w) +		defer gzipWriter.Close() +		gzippedResponseWriter := gzipResponseWriter{Writer: gzipWriter, ResponseWriter: w} +		next.ServeHTTP(gzippedResponseWriter, r) +	}) +} diff --git a/cmd/http/render.go b/cmd/http/render.go new file mode 100644 index 0000000..9c01e57 --- /dev/null +++ b/cmd/http/render.go @@ -0,0 +1,39 @@ +package main + +import ( +	"net/http" +	"strings" +	"text/template" +) + +func renderTemplate(w http.ResponseWriter, tmplPath string, p *Posts) (err error) { +	t, err := template.ParseFiles("html/master.tmpl.html", "html/"+tmplPath+".tmpl.html") +	if err != nil { +		return err +	} + +	splitPath := strings.Split(tmplPath, "/") + +	data := make(map[string]interface{}) + +	// If were loading the index, set page to 'Index' and pass through all posts. +	// Otherwise, set page to 'Projects' and pass through the first post (should only be one +	// coming in) +	if splitPath[0] == "index" { +		data["Page"] = "Index" +		data["Posts"] = p +	} else if splitPath[0] == "about" { +		data["Page"] = "About" +		data["Posts"] = nil +	} else { +		data["Page"] = "Project" +		data["Post"] = p.Contents[0] +	} + +	err = t.Execute(w, data) +	if err != nil { +		return err +	} + +	return nil +} @@ -0,0 +1,3 @@ +module chelsea.site + +go 1.22 diff --git a/html/about.tmpl.html b/html/about.tmpl.html new file mode 100644 index 0000000..dda674c --- /dev/null +++ b/html/about.tmpl.html @@ -0,0 +1,16 @@ +{{ define "title" }}About{{end}}
 +{{ define "description" }}About me.{{end}}
 +{{ define "keywords" }}info{{end}}
 +
 +{{ define "main" }}
 +      <h1>About</h1>
 +      <h2>Who I Am</h2>
 +      <p>My name is Alex Scerba, and I am an undergrad design student at the College for Creative Studies (CCS) in Detroit, MI. I enjoy drawing, web development, bicycles, motorcycles, and just overall messing around/tinkering with products and objects.</p>
 +      <p>I use this site to practice graphic design and web development. It's a place to share what I'm up to. Check out the <a href="/faq">FAQ</a> for more info.</p>
 +      <figure>
 +        <img src="./static/media/alex_profile_512.jpg" alt="Close up of Alex standing in front of red brick wall wearing a medium gray button down." width="511" height="512"/>
 +        <figcaption>Portrait, 2023. Credits: Chelsea Rogers</figcaption>
 +      </figure>
 +      <h2>Future Plans</h2>
 +      <p>I hope to start my own business designing, manufacturing, and selling custom parts for cars and motorcycles after spending time in the automotive and powersports industry as a designer or digital sculptor.</p>
 +{{ end }}
 diff --git a/html/index.tmpl.html b/html/index.tmpl.html new file mode 100644 index 0000000..582bc24 --- /dev/null +++ b/html/index.tmpl.html @@ -0,0 +1,17 @@ +{{ define "title" }}Home{{end}} +{{ define "description" }}Homepage{{end}} +{{ define "keywords" }}home{{end}} + +{{ define "main" }} +      <h1>Design Projects</h1> +      <div> +        <div class="projects even-2-col-grid"> +{{ range .Posts.Contents }} +          <a href="/projects/{{ .FileName }}" class="thumbnail"> +            {{ .Image }} +            <span class="title">{{ .Title }}</span> +          </a> +{{ end }} +        </div> +      </div> +{{ end }}
\ No newline at end of file diff --git a/html/master.tmpl.html b/html/master.tmpl.html new file mode 100644 index 0000000..275ea40 --- /dev/null +++ b/html/master.tmpl.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html lang="en-US"> +  <head> +    <meta charset="UTF-8"> +    <meta http-equiv="X-UA-Compatible" content="IE=edge"> +    <meta name="viewport" content="width=device-width, initial-scale=1.0"> +    <meta name="author" content="Chelsea Rogers" /> +    <title>{{block "title" .}}Page{{end}} - Chelsea Rogers</title> +    <meta name="description" content="{{block "description" .}}Alex's personal site{{end}}" /> +    <meta name="keywords" content="{{block "keywords" .}}personal site{{end}}"> +    <link rel="canonical" href="https://www.chelsea-rogers.com{{block "canonical" .}}{{end}}"> +    <link rel="stylesheet" href="/static/style.css" /> +    <link rel="shortcut icon" href="/static/assets/favicon.ico" type="image/x-icon" /> +  </head> +  <body> +    <header> +      <div class="main-nav-position"> +        <input type="checkbox" class="toggler"> +        <div class="hamburger"><div></div></div> +        <nav aria-labelledby="global-navigation" class="main-nav"> +          <a href="/">HOME</a> +          <a href="/about">ABOUT</a> +          <a href="/static/Resume_Chelsea_Rogers.pdf">RESUME</a> +        </nav> +      </div> +      <a href="/" class="logo-w-name"> +        <span class="name">Chelsea Rogers</span> +        <span class="sub-head">Communication Designer</span> +      </a> +    </header> + +    <main> + +        {{ template "main" . }} + +    </main> + +    <footer> +      <img src="/static/assets/logo.svg" alt="Logo" class="footer-logo"/> +      <address> +        <p>CONTACT: <a href="mailto:crogers16@ccsdetroit.edu">crogers16@ccsdetroit.edu</a></p> +        <p>LINKEDIN: <a href="https://linkedin.com/in/chelsea-rogers-b9997a227" target="_blank" rel="noopener noreferrer">linkedin.com/in/chelsea-rogers-b9997a227</a></p></p> +        <p>RESUME: <a href="/static/Resume_Scerba.pdf">view</a></p> +      </address> +    </footer> +  </body> +</html> diff --git a/html/projects/Husqvarna_Val.tmpl.html b/html/projects/Husqvarna_Val.tmpl.html new file mode 100644 index 0000000..ab32eb2 --- /dev/null +++ b/html/projects/Husqvarna_Val.tmpl.html @@ -0,0 +1,22 @@ +{{define "title"}}{{.Post.Title}}{{end}} +{{define "description"}}Husqvarna Val CCS unsponsored project.{{end}} +{{define "uploaded-on"}}2024-05-08{{end}} +{{define "keywords"}}project CCS{{end}} +{{ define "canonical" }}/projects{{end}} + +{{define "main"}} +    <h1>Project</h1> +    <div class="postBackground"> +      <article class="projectContent"> +        <h2>{{.Post.Title}}</h2> +        <p>Husqvarna Val powersports project. Extend electric range through the sidecar for longer and more remote adventures.</p> +        <p><b>Final Deliverable:</b> Digital model and animation.</p> +        <div class="hide"> +        <img src="/static/media/Husqvarna_Val/Bar_Shot.jpg" class="mainImage" alt="Husqvarna Val UI." width="1435" height="929" /> +        </div> +        <img src="/static/media/Husqvarna_Val/HUSQVARNA_Scerba_3k_rev2.jpg" alt="Husqvarna Val sidecar motorcycle project." /> +        <!-- <video src="/static/media/Husqvarna_Val/Val_Animation.mp4" alt="Husqvarna Val animation." controls="" width="1920" height="1080"><a href="/static/media/Husqvarna_Val/Val_Animation.mp4">CJ7 Signal Test</a></video> --> +        <iframe width="1920" src="https://www.youtube.com/embed/RXhwG4fjCKw?si=Bj5jhNxfdjiXy_Ch" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> +      </article> +    </div> +{{end}}
\ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..1f27ceb --- /dev/null +++ b/static/style.css @@ -0,0 +1,450 @@ +@font-face {
 +    font-family: 'DejaVu Sans';
 +    src: url('/static/assets/fonts/dejavu-sans-fontfacekit/web fonts/dejavusans_regular_macroman/DejaVuSans-webfont.woff') format('woff');
 +    font-weight: normal;
 +    font-style: normal;
 +}
 +
 +@font-face {
 +    font-family: 'DejaVu Sans';
 +    src: url('/static/assets/fonts/dejavu-sans-fontfacekit/web fonts/dejavusans_bold_macroman/DejaVuSans-Bold-webfont.woff') format('woff');
 +    font-weight: bold;
 +    font-style: normal;
 +}
 +
 +@font-face {
 +    font-family: 'DejaVu Sans';
 +    src: url('/static/assets/fonts/dejavu-sans-fontfacekit/web fonts/dejavusans_oblique_macroman/DejaVuSans-Oblique-webfont.woff') format('woff');
 +    font-weight: normal;
 +    font-style: italic;
 +}
 +
 +@font-face {
 +    font-family: 'DejaVu Sans';
 +    src: url('/static/assets/fonts/dejavu-sans-fontfacekit/web fonts/dejavusans_boldoblique_macroman/DejaVuSans-BoldOblique-webfont.woff') format('woff');
 +    font-weight: bold;
 +    font-style: italic;
 +}
 +
 +@font-face {
 +    font-family: 'DejaVu Sans';
 +    src: url('/static/assets/fonts/dejavu-sans-fontfacekit/web fonts/dejavusans_extralight_macroman/DejaVuSans-ExtraLight-webfont.woff') format('woff');
 +    font-weight: lighter;
 +    font-style: normal;
 +}
 +
 +* {
 +    box-sizing: border-box;
 +}
 +
 +html {
 +    scroll-behavior: smooth;
 +}
 +
 +body {
 +    margin: 0;
 +    font-family: 'DejaVu Sans', 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
 +}
 +
 +main {
 +    padding: 1rem;
 +    padding-top: 7rem;
 +    padding-bottom: 3rem;
 +    max-width: 85rem;
 +    margin-left: auto;
 +    margin-right: auto;
 +}
 +
 +header {
 +    position: fixed;
 +    width: 100%;
 +    display: flex;
 +    align-items: center;
 +    padding: 1rem;
 +    background-color: white;
 +    border-bottom: 0.1rem solid black;
 +    z-index: 100;
 +}
 +
 +.logo-w-name {
 +    display: flex;
 +}
 +
 +.logo {
 +    padding-left: .75rem;
 +    padding-right: .75rem;
 +    height: 1.5rem;
 +    filter: invert(70%)
 +}
 +
 +.name {
 +    font-weight: bold;
 +    margin: auto 1rem;
 +    color: rgb(100, 100, 100)
 +}
 +
 +h1 {
 +  display: none;
 +  font-size: 2.5rem;
 +  color: rgb(143, 143, 143);
 +  /* padding-left: 1rem; */
 +  /* border-left: 1rem solid rgb(59, 195, 219); */
 +  /* border-bottom: .12rem solid rgb(196, 196, 196); */
 +}
 +
 +b {
 +  color:rgb(100, 100, 100);
 +}
 +
 +.main-nav-position {
 +    display: flex;
 +    justify-content: right;
 +    flex-grow: 1;
 +    padding-right: 2rem;
 +    border-right: .5rem solid rgb(196, 196, 196);
 +}
 +
 +.main-nav {
 +    display: flex;
 +    gap: 2rem;
 +    padding-top: .5rem;
 +    padding-bottom: .5rem;
 +    font-size: .8rem;
 +}
 +
 +.toggler, .hamburger {
 +    display: none;
 +}
 +
 +.even-2-col-grid {
 +    display: grid;
 +    grid-template-columns: 1fr 1fr;
 +}
 +
 +.uneven-column {
 +	display: flex;
 +	gap: 4rem;
 +}
 +
 +.uneven-column-grid {
 +  display: grid;
 +  gap: 3rem;
 +  grid-template-columns: 25rem 1fr;
 +}
 +
 +.side-info {
 +	min-width: 20rem;
 +	max-width: 30%;
 +	/*background-color: rgb(240,240,240);*/
 +}
 +
 +/* img {
 +    max-width: 100%;
 +    height: auto;
 +    vertical-align: middle;
 +    font-style: italic;
 +    shape-margin: 1rem;
 +} */
 +
 +img, video {
 +  max-width: 100%;
 +  height: auto;
 +  display: block;
 +  font-style: italic;
 +/*   color: rgb(177, 177, 177);
 +  background-color: rgb(73, 73, 73); */
 +}
 +
 +iframe {
 +  max-width: 100%;
 +  aspect-ratio: 16/9;
 +  display: block;
 +}
 +
 +.thumbnail img {
 +  width: fit-content;
 +  aspect-ratio: 4/3;
 +  object-fit: cover;
 +  display: block;
 +  position: relative;
 +  z-index: -1;
 +}
 +
 +.thumbnail {
 +	position: relative;
 +}
 +
 +.title {
 +  z-index: -1;
 +  opacity: 0;
 +  display: block;
 +	position: absolute;
 +	top: 0;
 +	left: 0;
 +	padding: 8rem 1rem;
 +	text-align: center;
 +	font-weight: bold;
 +	color: white;
 +	background-color: rgba(27, 27, 27, 0.726);
 +	min-width: 100%;
 +	min-height: 100%;
 +  transition: all 0.3s ease-in-out;
 +}
 +
 +.thumbnail:hover .title {
 +	opacity: 100%;
 +}
 +
 +a {
 +  color: black;
 +}
 +
 +header a, .projects a {
 +  text-decoration: none;
 +}
 +
 +a:hover:not(.thumbnail) {
 +  opacity: 60%;
 +}
 +
 +.projects p {
 +  margin-top: 0.5rem;
 +  border-right: .5rem solid rgb(196, 196, 196);
 +  border-top: .12rem solid rgb(196, 196, 196);
 +}
 +
 +.projectContent p {
 +  margin-left: 1.5rem;
 +}
 +
 +.hide {
 +  display: none;
 +}
 +
 +.footer-logo {
 +    filter: invert(100%);
 +    height: 1rem;
 +    margin-left: .75rem;
 +}
 +
 +footer {
 +    display: flex;
 +    background-color: black;
 +    padding-left: 1rem;
 +    padding-right: 1rem;
 +    gap: 1rem;
 +    align-items: center;
 +}
 +
 +address {
 +    padding-left: 1rem;
 +    gap: 2rem;
 +    font-size: .7rem;
 +}
 +
 +footer * {
 +    color: white;
 +}
 +
 +blockquote {
 +  font-style: italic;
 +}
 +
 +.sketchbook > img:first-of-type {
 +  display: none;
 +}
 +
 +.back-button {
 +  background-color: orange;
 +}
 +
 +@media screen and (max-width: 82rem) {
 +  main {
 +    margin-left: 5rem;
 +    margin-right: 5rem;
 +  }
 +	.uneven-column {
 +		flex-direction: column-reverse;
 +	}
 +	.side-info {
 +		max-width: 100%;
 +	}
 +  .side-info img {
 +    display: block;
 +    margin: auto;
 +  }
 +}
 +
 +@media screen and (max-width: 70rem) {
 +  main {
 +    margin-left: 1rem;
 +    margin-right: 1rem;
 +  }
 +}
 +
 +@media screen and (min-width: 45rem) {
 +  address {
 +    display: flex;
 +  }
 +}
 +
 +@media screen and (max-width: 45rem) {
 +    main {
 +      margin-left: 0.5rem;
 +      margin-right: 0.5rem;
 +    }
 +    nav {
 +      display: none;
 +    }
 +    .footer-logo {
 +      height: 1.5rem;
 +    }
 +
 +    header {
 +        width: 100%;
 +    }
 +
 +    .even-2-col-grid {
 +        grid-template-columns: 1fr;
 +    }
 +
 +    .toggler{
 +    /* ALWAYS KEEPING THE TOGGLER OR THE CHECKBOX ON TOP OF EVERYTHING :  */
 +      visibility: visible;
 +      display: block;
 +      z-index: 104;
 +      height: 60px;
 +      width: 60px;
 +      position: fixed;
 +      top: 0;
 +      right: 1rem;
 +      cursor: pointer;
 +      opacity: 0;
 +      margin: 0;
 +    }
 +
 +    .hamburger{
 +      z-index: 103;
 +      position: fixed;
 +      top: 0;
 +      right: 1rem;
 +      height: 60px;
 +      width: 60px;
 +      padding: 5px;
 +      /*border-bottom-left-radius: 8px;
 +      background-color: var(--dark-transparent);*/
 +
 +    /* FOR DISPLAYING EVERY ELEMENT IN THE CENTER : */
 +
 +      display: flex;
 +      align-items: center;
 +      justify-content: center;
 +    }
 +
 +    /* CREATING THE MIDDLE LINE OF THE HAMBURGER : */
 +
 +    .hamburger > div{
 +      position: relative;
 +      top: 0;
 +      right: 0;
 +      background: black;
 +      height: 2px;
 +      width: 60%;
 +      transition: all  0.4s ease;
 +    }
 +
 +    /* CREATING THE TOP AND BOTTOM LINES :
 +    TOP AT -10PX ABOVE THE MIDDLE ONE AND BOTTOM ONE IS 10PX BELOW THE MIDDLE: */
 +
 +    .hamburger > div::before,
 +    .hamburger > div::after{
 +      content: '';
 +      position: absolute;
 +      top: -10px;
 +      background: black;
 +      width: 100%;
 +      height: 2px;
 +      transition: all  0.4s ease;
 +    }
 +
 +    .hamburger > div::after{
 +      top: 10px;
 +    }
 +
 +    /* IF THE TOGGLER IS IN ITS CHECKED STATE, THEN SETTING THE BACKGROUND OF THE MIDDLE LAYER TO COMPLETE BLACK AND OPAQUE :  */
 +
 +    .toggler:checked + .hamburger > div{
 +      background: rgba(0,0,0,0); /*Not bothering with var because one-off*/
 +    }
 +
 +    .toggler:checked + .hamburger > div::before{
 +      top: 0;
 +      transform: rotate(-45deg);
 +    }
 +
 +    /* AND ROTATING THE TOP AND BOTTOM LINES :  */
 +
 +    .toggler:checked + .hamburger > div::after{
 +      top: 0;
 +      transform: rotate(45deg);
 +    }
 +
 +    /* MAIN MENU WITH THE WHITE BACKGROUND AND THE TEXT :  */
 +
 +    .main-nav {
 +
 +    /* APPLYING TRANSITION TO THE MENU :  */
 +
 +      display: flex;
 +      flex-direction: column;
 +      position: fixed;
 +      top: 0;
 +      right: 0;
 +      z-index: 102;
 +      background-color: white;
 +      padding: 6rem 2rem 1rem 2rem;
 +      align-items: center;
 +
 +    /* HIDDEN INITIALLY  :  */
 +
 +      visibility: hidden;
 +    }
 +
 +    .main-nav > a {
 +      margin-top: 15px;
 +      margin-bottom: 15px;
 +      font-size: 2rem;
 +    }
 +
 +    /* IF THE TOGGLER IS CHECKED, THEN INCREASE THE WIDTH OF THE MENU TO 30% , CREATING A SMOOTH EFFECT :  */
 +
 +    .toggler:checked ~ .main-nav{
 +      visibility: visible;
 +      animation: fadeIn 0.8s ease;
 +    }
 +    .toggler:checked ~ .bg-nav-block {
 +      visibility: visible;
 +      animation: fadeIn 0.8s ease;
 +    }
 +    .toggler:checked ~ .hamburger {
 +      background-color: #00000000;
 +    }
 +}
 +
 +@media screen and (max-width: 27rem) {
 +/*   .logo {
 +    height: 5vw;
 +  }
 +  .name {
 +    font-size: 3vw;
 +  } */
 +  .address {
 +    font-size: .5rem;
 +  }
 +  .toggler{
 +    top: .4rem;
 +  }
 +  .hamburger{
 +    top: .4rem;
 +  }
 +}
\ No newline at end of file | 
