8 커밋 4c1228758a ... e863adc0a6

작성자 SHA1 메시지 날짜
  Danilo Gómez Gómez e863adc0a6 Include eye client offset in logo animation 5 년 전
  Danilo Gómez Gómez da0322925a Insert a framed logo 5 년 전
  Danilo Gómez Gómez b140ed57d8 Render new favicons 5 년 전
  Danilo Gómez Gómez 650ea7a106 Realign side margins and set footer layout 5 년 전
  Danilo Gómez Gómez 39e5405b94 Add animated figure captions 5 년 전
  Danilo Gómez Gómez 694003224e Reformat base template 5 년 전
  Danilo Gómez Gómez 8d6510015b Manage exceptions in hot reloading 5 년 전
  Danilo Gómez Gómez a26015091d Add file watcher 5 년 전

+ 24 - 14
layout/_base.html

@@ -15,11 +15,11 @@
 	<meta name="theme-color" content="#ffffff">
 	<script src="{{ 'js/eye.js' | static }}"></script>
 </head>
-<body>
+<body id="body">
 	<header>
 		<div class="header-box">
 			<div class="lbox">
-				<div class="vbox">
+				<div id="lang-chooser" class="vbox">
 					<a class="first en {% if en %}selected{% endif %}" href="{{ 'en' | lang_url }}">en</a>
 					<a class="first {% if es %}selected{% endif %}" href="{{ 'es' | lang_url }}">es</a>
 				</div>
@@ -36,7 +36,7 @@
 								<div><a href="{{ '/work' | cur_lang }}" {% if work_selected %}class="selected"{% endif %}>{{ 'work' | lang }}</a></div> /
 								<div><a href="{{ '/news' | cur_lang }}" {% if news_selected %}class="selected"{% endif %}>{{ 'news' | lang }}</a></div> /
 								<div><a href="{{ '/about' | cur_lang }}" {% if about_selected %}class="selected"{% endif %}>{{ 'about' | lang }}</a></div> /
-								<div><a href="{{ '/contact' | cur_lang }}" {% if contact_selected %}class="selected"{% endif %}>{{ 'contact' | lang }}</a></div> /
+								<div><a href="{{ '/contact' | cur_lang }}" class="last{% if contact_selected %} selected{% endif %}">{{ 'contact' | lang }}</a></div>
 							</h1>
 						</div>
 						<div class="hbox">
@@ -46,23 +46,34 @@
 								<div><a href="{{ '/work/illustration' | cur_lang }}" {% if illustration_selected %}class="selected"{% endif %}>{{ 'illustration' | lang }}</a></div> /
 								<div><a href="{{ '/work/editorial' | cur_lang }}" {% if editorial_selected %}class="selected"{% endif %}>{{ 'editorial' | lang }}</a></div> /
 								<div><a href="{{ '/work/branding' | cur_lang }}" {% if branding_selected %}class="selected"{% endif %}>{{ 'branding' | lang }}</a></div> /
-								<div><a href="{{ '/work/other' | cur_lang }}" {% if other_selected %}class="selected"{% endif %}>{{ 'other' | lang }}</a></div>
+								<div><a href="{{ '/work/other' | cur_lang }}" class="last{% if other_selected %} selected{% endif %}">{{ 'other' | lang }}</a></div>
 							</div>
 						</div>
 					</div>
 			</div>
 		</div>
 	</header>
-	<div id="main" class="v">
-		<div class="main-content">
-			{% block main %}
-			{% endblock %}
-		</div>
-	</div>
+	<main>
+		{% block main %}
+		{% endblock %}
+	</main>
 	<footer>
-		<h3>
-			copyright / <a href="/">www.alucho.com</a> / 2019 / <a href="/contact">{{ 'contact' | lang }}</a>
-		</h3>
+		<div class="copyright">
+			<a class=>copyright</a> / <a href="/">www.alucho.com</a> / <a>2019</a> / <a href="/contact">{{ 'contact' | lang }}</a>
+		</div>
+		<div class="hbox">
+			Fb
+			Tw
+			In
+		</div>
+		<div class="hbox">
+			<h2 class="header-title">
+				<div><a href="{{ '/work' | cur_lang }}">{{ 'work' | lang }}</a></div> /
+				<div><a href="{{ '/news' | cur_lang }}">{{ 'news' | lang }}</a></div> /
+				<div><a href="{{ '/about' | cur_lang }}">{{ 'about' | lang }}</a></div> /
+				<div><a href="{{ '/contact' | cur_lang }}" class="last">{{ 'contact' | lang }}</a></div>
+			</h2>
+		</div>
 	</footer>
 
 	<form name="setLangEnglish" action="/i18n/setlang/" method="POST">
@@ -77,6 +88,5 @@
 		<input type="hidden" name="language" value="es"/>
 	</form>
 
-	<script src="{{ 'js/eye.js' | static }}"></script>
 </body>
 </html>

+ 9 - 7
layout/work.html

@@ -4,11 +4,13 @@
 {% set all_selected = False if no_all_selected else True %}
 
 {% block main %}
-    <div class="sqr-item"><img src="{{ 'image/tile.jpg' | static }}" alt="Ejemplo"></div>
-    <div class="sqr-item"><img src="{{ 'image/tile.jpg' | static }}" alt="Ejemplo"></div>
-    <div class="sqr-item"><img src="{{ 'image/tile.jpg' | static }}" alt="Ejemplo"></div>
-    <div class="sqr-item"><img src="{{ 'image/tile.jpg' | static }}" alt="Ejemplo"></div>
-    <div class="sqr-item"><img src="{{ 'image/tile.jpg' | static }}" alt="Ejemplo"></div>
-    <div class="sqr-item"><img src="{{ 'image/tile.jpg' | static }}" alt="Ejemplo"></div>
-    <div class="sqr-item"><img src="{{ 'image/tile.jpg' | static }}" alt="Ejemplo"></div>
+{% for _ in range(15) %}
+	<figure class="v">
+		<img src="{{ 'image/tile.jpg' | static }}" alt="Ejemplo">
+		<figcaption>
+			<h2>Club De Jazz - 2019</h2>
+			poster
+		</figcaption>
+	</figure>
+{% endfor %}
 {% endblock %}

BIN
root/android-chrome-192x192.png


BIN
root/android-chrome-512x512.png


BIN
root/apple-touch-icon.png


BIN
root/favicon-16x16.png


BIN
root/favicon-32x32.png


BIN
root/favicon.ico


BIN
root/mstile-150x150.png


+ 8 - 3
run_server.sh

@@ -1,4 +1,9 @@
 #!/bin/bash
-python3 sitegen.py --with_index
-cd build
-python3 -m http.server 8001
+python3 sitegen.py --with-index --watch &
+gen=$!
+
+python3 -m http.server -d build 8001 &
+srv=$!
+
+trap "kill $gen; kill $srv" EXIT
+wait

+ 126 - 35
sitegen.py

@@ -1,22 +1,67 @@
 import argparse
+from functools import lru_cache
 from jinja2 import Environment, FileSystemLoader, contextfilter
 from pathlib import Path
-from os import walk, makedirs
-from os.path import join, exists, splitext, split
-from shutil import rmtree, copy, copytree
-from functools import lru_cache
+from os import walk, makedirs, listdir, symlink, readlink
+from os.path import join, exists, splitext, split, islink, isdir
+from shutil import rmtree, copy2, copystat, Error
+from time import sleep
+from traceback import print_exc
 
+#TODO: load from config file (and watch it too)
 LANGUAGES = 'languages'
-
-#TODO: load from config file
+ROOT = 'root'
+STATIC = 'static'
 DEFAULT_LANG = 'en'
 OTHER_LANGS = set(['es'])
+WATCH_INTERVAL = 1 # in secs
+
+# Utils
+
+def copytree(src, dst, symlinks=False):
+    names = listdir(src)
+    makedirs(dst, exist_ok=True)
+    # `exist_ok=True`, very important for hot reloading
+    errors = []
+    for name in names:
+        srcname = join(src, name)
+        dstname = join(dst, name)
+        try:
+            if symlinks and islink(srcname):
+                linkto = readlink(srcname)
+                symlink(linkto, dstname)
+            elif isdir(srcname):
+                copytree(srcname, dstname, symlinks)
+            else:
+                copy2(srcname, dstname)
+            # XXX What about devices, sockets etc.?
+        except OSError as why:
+            errors.append((srcname, dstname, str(why)))
+        # catch the Error from the recursive copytree so that we can
+        # continue with other files
+        except Error as err:
+            errors.extend(err.args[0])
+    try:
+        copystat(src, dst)
+    except OSError as why:
+        # can't copy file access times on Windows
+        if why.winerror is None:
+            errors.extend((src, dst, str(why)))
+    if errors:
+        raise Error(errors)
+
+def mtimes(target_dir):
+    ''''get modification time of files in `target_dir`'''
+    return { f: f.stat().st_mtime for f in Path(target_dir).rglob('*') }
 
+def save_dic(lang, dic):
+    with open(join(LANGUAGES, lang + '.txt'), 'w') as f:
+        for key, value in dic.items():
+            f.write(f"{key}:{value}\n")
 
 @lru_cache(None)
 def get_lang_dic(lang):
-    if not exists(LANGUAGES):
-        makedirs(LANGUAGES)
+    makedirs(LANGUAGES, exist_ok=True)
 
     lang_file = join(LANGUAGES, lang + '.txt')
 
@@ -30,12 +75,7 @@ def get_lang_dic(lang):
                 dic[key] = value
         return dic
 
-
-def save_dic(lang, dic):
-    with open(join(LANGUAGES, lang + '.txt'), 'w') as f:
-        for key, value in dic.items():
-            f.write(f"{key}:{value}\n")
-
+# Filters
 
 def add_lang_prefix(lang, path):
     if lang == DEFAULT_LANG:
@@ -46,7 +86,6 @@ def add_lang_prefix(lang, path):
 
     return f"/{lang}{path}"
 
-
 @contextfilter
 def lang(ctx, value):
     lang = ctx.environment.globals['lang']
@@ -65,7 +104,6 @@ def lang(ctx, value):
             print(f"Not translated phrase: `{value}`")
             return value
 
-
 @contextfilter
 def lang_url(ctx, lang):
     name, _ = splitext(ctx.name)
@@ -77,16 +115,15 @@ def lang_url(ctx, lang):
 
     return add_lang_prefix(lang, f"/{name}")
 
-
 @contextfilter
 def cur_lang(ctx, path):
     lang = ctx.environment.globals['lang']
     return add_lang_prefix(lang, path)
 
-
 def static(value):
     return f"/static/{value}"
 
+# Compiler
 
 def compile(env, path, target):
     lang = env.globals['lang']
@@ -112,9 +149,8 @@ def compile(env, path, target):
 
             dst = join(target, dst)
             dst_folder, _ = split(dst)
-
-            if not exists(dst_folder):
-                makedirs(dst_folder)
+            
+            makedirs(dst_folder, exist_ok=True)
 
             template = env.get_template(join(cur_base, doc))
             output = template.render()
@@ -122,11 +158,12 @@ def compile(env, path, target):
             with open(dst, 'w') as f:
                 f.write(output)
 
-
-def run(args):
+def init_gen(args):
     # Load layout
     file_loader = FileSystemLoader(args.source)
-    env = Environment(loader=file_loader)
+    env = args.env = Environment(loader=file_loader)
+    
+    # Init gobals
     env.globals['lang'] = DEFAULT_LANG
 
     # Add filters
@@ -135,32 +172,86 @@ def run(args):
     env.filters['cur_lang'] = cur_lang
     env.filters['static'] = static
 
-    path = Path(args.source)
-
+    # Clean target
     if exists(args.target):
         rmtree(args.target)
+    makedirs(args.target, exist_ok=True)
 
-    if exists('root'):
-        copytree('root', args.target)
-    else:
-        makedirs(args.target)
-
-    compile(env, path, args.target)
+def gen_layout(args):
+    env = args.env
+    path = Path(args.source)
 
-    copytree('static', join(args.target, 'static'))
+    # Reset gobals
+    env.globals['lang'] = DEFAULT_LANG
 
+    # compile
+    compile(env, path, args.target)
     for other_lang in OTHER_LANGS:
         env.globals['lang'] = other_lang
         compile(env, path, args.target)
 
+def gen_root(args):
+    copytree(ROOT, args.target)
+
+def gen_static(args):
+    copytree(STATIC, join(args.target, 'static'))
+
+def save_generate(generator, args, msg):
+    print(f'* {msg}')
+    try:
+        generator(args)
+        print('  done!')
+    except:
+        print_exc()
+        return False
+    return True
+
+# Runtime
+
+def run(args):
+    print('initial compilation')
+    init_gen(args)
+    save_generate(gen_layout, args, 'generating layout')
+    save_generate(gen_root, args, 'copying root files')
+    save_generate(gen_static, args, 'copying static dir')
+
+    if not args.watch:
+        return
+    
+    print(f'watching {args.source}/*:{LANGUAGES}/*:{STATIC}/*:{ROOT}/*')
+    while True:
+        # take modification times
+        source_mt = mtimes(args.source)
+        source_mt.update(mtimes(LANGUAGES))
+        static_mt = mtimes(STATIC)
+        root_mt = mtimes(ROOT)
+
+        sleep(WATCH_INTERVAL)
+
+        # compare modification times and recompile if different
+        new_source_mt = mtimes(args.source)
+        new_source_mt.update(mtimes(LANGUAGES))
+        if source_mt != new_source_mt:
+            source_mt = new_source_mt
+            save_generate(gen_layout, args, 'recompiling layout')
+        
+        new_root_mt = mtimes(ROOT)
+        if root_mt != new_root_mt:
+            root_mt = new_root_mt
+            save_generate(gen_root, args, 'copying root files')
+        
+        new_static_mt = mtimes(STATIC)
+        if static_mt != new_static_mt:
+            static_mt = new_static_mt
+            save_generate(gen_static, args, 'copying static files')
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser("Site generator")
 
-    parser.add_argument('--with_index', action='store_true', default=False)
+    parser.add_argument('--watch', action='store_true', default=False)
+    parser.add_argument('--with-index', action='store_true', default=False)
     parser.add_argument('--source', default='layout')
     parser.add_argument('--target', default='build')
 
     args = parser.parse_args()
     run(args)
-

+ 52 - 19
static/css/main.css

@@ -3,6 +3,7 @@
 	--columns: 5;
 	--tile-margin: .9vw;
 	--body-margin: 3.6vw;
+	--theme-color: #ff2d00;
 	/* --tile-margin: 12.75px; */
 	/* --body-margin: 66px; */
 	--header-inner-space: calc(8 * var(--tile-margin));
@@ -56,8 +57,8 @@ h1 {
 	#menu { visibility: hidden; }
 }
 #logo {
-	width: 9vw;
-	margin: 0 21.5px .2em 15px;
+	width: 10vw;
+	margin: 0 .7vw -.5vw .5vw;
 }
 /* @media (max-width: 460px) {
 	#logo { width: 130px; }
@@ -103,20 +104,14 @@ h2 {
 	text-decoration: none;
 	text-transform: uppercase;
 }
-h3 {
-	font-size: 20px;
-	font-family: 'nexa-light';
+figure figcaption {
+	font-size: .9em;
 }
-a.current, a[href]:hover {
+a.selected, a[href]:hover {
 	/* cursor: default; */
 	/* display: inline-block; */
-	color: #ff2d00;
-}
-
-a.selected {
-	color: #ff2d00;
+	color: var(--theme-color);
 }
-
 div {
 	display: flex;
 }
@@ -230,7 +225,7 @@ a.first {
 a.last {
 	margin-right: var(--tile-margin);
 }
-.main-content {
+main {
 	display: flex;
 	flex-direction: row;
 	flex-wrap: wrap;
@@ -240,16 +235,54 @@ a.last {
 	/* margin-top: calc(var(--header-space) + var(--body-margin)); */
 	position: static;
 }
-.sqr-item {
+figure {
+	position: relative;
 	display: flex;
 	width: calc(100% / var(--columns) - 2 * var(--tile-margin));
 	margin: var(--tile-margin);
+	overflow: hidden;
 }
-.sqr-item img {
+figure img {
+	display: flex;
 	width: 100%;
 }
-.sqr-item svg {
-	width: 100%;
-	height: 100%;
-	background-color: #bbb9ba;
+figure figcaption {
+	display: flex;
+	flex-direction: column;
+	position: absolute;
+	padding: calc(2 * var(--tile-margin));
+	background-color: var(--theme-color);
+	bottom: 0;
+	width: calc(100% - var(--tile-margin) * 4);
+	opacity: 0;
+	height: 0;
+}
+figure:hover figcaption {
+	opacity: 1;
+	transition: .3s ease;
+}
+figure.v:hover figcaption {
+	height: 21%;
+}
+figure.h:hover figcaption,
+figure.s:hover figcaption {
+	height: 30%;
+}
+footer {
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	justify-content: space-between;
+	margin: 0 var(--tile-margin) calc(2*var(--tile-margin)) var(--tile-margin);
+	font-size: .7em;
+	font-weight: 900;
+}
+footer .copyright {
+	align-items: center;
+}
+footer a.first {
+	margin-left: 0;
 }
+footer a.last {
+	margin-right: 0;
+}

+ 8 - 6
static/js/eye.js

@@ -1,16 +1,17 @@
 // nilox (c)
 
 window.onload = function () {
-    var eye = document.getElementById('logo');
-
-    var eyeDoc = eye.contentDocument;
+    const body = document.getElementById('body');
+    const lang = document.getElementById('lang-chooser');
+    const eye = document.getElementById('logo');
+    const eyeDoc = eye.contentDocument;
     const logo = eyeDoc.getElementById('alucho-logo');
     const iris = eyeDoc.getElementById('iris');
     const pupil = eyeDoc.getElementById('pupil');
 
     // #iris cx attribute animation settings (relative to #alucho-logo i.e. absolute inside the svg)
-    const istart = 56;
-    const iend = 116;
+    const istart = 66;
+    const iend = 126;
     const ilength = iend - istart;
 
     // #pupil cx attribute animation settings (relative to #iris cx)
@@ -20,7 +21,8 @@ window.onload = function () {
     const oY = logo.getAttribute('height') / 2.;
 
     window.onmousemove = function (ev) {
-        const r = ev.clientX / window.innerWidth;
+        const eyeOffset = body.getBoundingClientRect().x + lang.clientWidth + eye.clientWidth / 2;
+        const r = (ev.clientX - eyeOffset) / (window.innerWidth - eyeOffset) / 2 + .5;
 
         const iX = istart + ilength * r;
         iris.setAttribute('cx', iX);

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 44 - 44
static/svg/alucho-logo.svg


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 52
static/svg/alucho-logo.svg.bak