Applied

Engineering

Results

samueltorres.dev

Mexico City

Mexico City, Mexico Available immediately

Availability

Remote and on-site projects MXN · USD

Documentation

How samueltorres.dev is built and maintained — structure, code, contact form, SEO, server and deployment, explained step by step.

Last updated: September 25, 2026.

Overview

samueltorres.dev is Samuel Torres Pichardo's portfolio. It is a static, bilingual (Spanish / English) site built on top of the Ashley HTML template. There is no framework and no build step for the pages themselves: every page is a hand-written .html file that the server delivers as-is.

The only moving parts are:

  • SCSS, compiled locally into a single css/style.css (with Prepros).
  • JavaScript in the browser: page transitions, animations, sliders, the language switch and the contact form.
  • PHP on the server, used only by mail.php (contact form) and, optionally, by the in-house analytics.
ItemValue
Domainhttps://samueltorres.dev (without www; www redirects here)
Spanish home/ (the index.html file)
English home/home-1.html
HostingShared cPanel hosting, Apache + PHP 8.3, free AutoSSL certificate
Web rootpublic_html/
How files get thereManual upload through the cPanel File Manager
About this page: it is an internal reference. It carries noindex, nofollow, is left out of sitemap.xml and is not linked from any menu, so search engines and regular visitors never land on it.

Project structure

Everything lives in one folder. The HTML pages sit at the root; assets and server code live in subfolders. The Uploaded? column says whether the item belongs on the server.

PathWhat it containsUploaded?
*.html (root)All the pages of the site, in English and SpanishYes
css/style.css (compiled) and the plugin stylesheets in css/plugins/Yes
js/The site scripts and the libraries in js/plugins/Yes
img/Photos, project screenshots, icons, the social-share cover (og-cover.jpg)Yes
docs/certificates/Certificate PDFs/images opened from the certifications pageYes (only this subfolder of docs/)
mail.phpContact-form backendYes
.htaccessApache rules: HTTPS, redirects, headers, cachingYes, carefully
robots.txt, sitemap.xmlInstructions and page list for search enginesYes
favicon*, apple-touch-icon.png, site.webmanifestBrowser-tab and home-screen iconsYes
api/, admin/, cron/In-house analytics (see its section)Only if analytics is turned on
config/Database credentials (db.php)Only with analytics, never to git
scss/Source of the stylesheetNo
sql/, tools/Database schema, admin creation script, sitemap generatorNo
*.bak, *.bak2, *.zipLocal backupsNever (anyone could download them by URL)
*.md, contexto-completo.txt, DEPLOY-PENDIENTE.txtInternal notesNo

Pages and languages

Every page exists twice, once per language, as two separate files. There is no translation engine: each file already contains its final text.

Naming convention
  • English is the base name: contact.html, cv.html, case-study-bea.html…
  • Spanish adds -es: contact-es.html, cv-es.html, case-study-bea-es.html…
  • The one exception is the home page: the Spanish home is index.html (served at /) and its English twin is home-1.html. The old home-1-es.html is retired and redirects to /.
  • 404.html is the only page without a twin.
Page groups
GroupFiles (English name)
Homeindex.html (ES) / home-1.html (EN)
Projectsportfolio-1.html, project-*.html (6), case-study-*.html (13)
Servicesservices.html, service.html, service-ai, -cloud, -linux, -security
Profilecv.html, certifications.html
Contact and legalcontact.html, privacy.html
Internaldocumentation.html (this page), 404.html
ES · EN language switch

initLangSwitch() in js/main.js adds the ES · EN button to the top bar of every page. It works out the twin page from the file name (applying the naming convention and the home exception above) and simply links to it. It remembers the choice in localStorage but never redirects on its own, so whoever shares a Spanish link always gets the Spanish page. On 404.html the button is disabled.

Adding a new page: create both files (name.html and name-es.html), set <html lang="en"> / lang="es", add the canonical and the three hreflang tags (en, es, x-default) to both, and regenerate the sitemap (see SEO). The language switch will find the twin automatically.

HTML markup

Every page follows the same skeleton. Knowing what each block does makes it easy to edit any page without breaking the template:

<!DOCTYPE html>
<html lang="en"> <!-- or "es" on the Spanish twin -->

<head>
    <!-- meta, description, canonical, hreflang, favicons, CSS -->
</head>

<body>

    <!-- wrapper -->
    <div class="mil-wrapper" id="top">

        <!-- cursor -->
        <div class="mil-ball"> ... </div>

        <!-- preloader -->
        <div class="mil-preloader"> ... </div>

        <!-- scrollbar progress -->
        <div class="mil-progress-track"> ... </div>

        <!-- menu -->
        <div class="mil-menu-frame"> ... </div>

        <!-- curtain -->
        <div class="mil-curtain"></div>

        <!-- frame -->
        <div class="mil-frame"> ... </div>

        <!-- content -->
        <div class="mil-content">
            <div id="swupMain" class="mil-main-transition">

                <!-- banner --> ...
                <!-- page sections --> ...
                <!-- footer --> ...
                <!-- hidden elements --> ...

            </div>
        </div>

    </div>
    <!-- wrapper end -->

    <!-- scripts: plugins, then js/main.js, then page scripts -->

</body>
</html>
BlockWhat it does
.mil-ballThe custom circular cursor that follows the mouse (desktop only). Its "More" / "Choose" labels appear over links and sliders.
.mil-preloaderIntro animation shown while the page loads (see Preloader).
.mil-progress-trackThe thin bar that shows how far down the page you have scrolled.
.mil-menu-frameThe full-screen menu that opens from the hamburger button. The links live in #swupMenu.
.mil-curtainThe layer that covers the screen during page transitions.
.mil-frameFixed top bar (logo, language switch, menu button) and bottom bar (current page, "Back to top").
#swupMainThe only part that changes between pages. Swup swaps just this container, so everything outside it stays in place.
.mil-hidden-elementsDecorative SVGs (arrows, lines, dodecahedron) that main.js copies into the page where they are needed.
Useful classes
  • mil-up: the element fades in and rises as it enters the screen (GSAP ScrollTrigger).
  • mil-mb-30, mil-mb-60, mil-p-120-90…: spacing helpers (bottom margin, section padding) in pixels.
  • mil-dark-bg, mil-muted, mil-light-soft, mil-thin: background and text tones.

CSS and SCSS

Pages load four plugin stylesheets plus the site's own stylesheet, always in this order:

<!-- bootstrap grid css -->
<link rel="stylesheet" href="/css/plugins/bootstrap-grid.css">
<!-- font awesome css -->
<link rel="stylesheet" href="/css/plugins/font-awesome.min.css">
<!-- swiper css -->
<link rel="stylesheet" href="/css/plugins/swiper.min.css">
<!-- fancybox css -->
<link rel="stylesheet" href="/css/plugins/fancybox.min.css">
<!-- ashley scss -->
<link rel="stylesheet" href="/css/style.css">
Plugins
The site's own styles (SCSS)

css/style.css is generated; do not edit it by hand. Edit the files in scss/ and compile with Prepros (the prepros.config file in the root already has the configuration). scss/style.scss is the entry point that imports the rest:

FileWhat it contains
_variables.scssColours, fonts (it imports Outfit) and shared values. Change a colour here and it changes everywhere.
_common.scssBase: typography, links, buttons, forms, breadcrumbs, backgrounds, spacing.
_components.scssTemplate pieces: preloader, cursor, menu, banner, services, footer, portfolio, accordion, 404, transitions, the language switch…
_recognition.scssThe recognition block on the home page.
_certifications.scssThe certifications page.
_project-preview.scssThe project grid and its previews.
_documentation.scssThis page: side index, code blocks, copy button.

JavaScript

Every page loads the same libraries and then js/main.js. After that come only the scripts that page needs.

<!-- jQuery js -->
<script src="/js/plugins/jquery.min.js"></script>
<!-- swup js -->
<script src="/js/plugins/swup.min.js"></script>
<!-- swiper js -->
<script src="/js/plugins/swiper.min.js"></script>
<!-- fancybox js -->
<script src="/js/plugins/fancybox.min.js"></script>
<!-- gsap js -->
<script src="/js/plugins/gsap.min.js"></script>
<!-- scroll smoother -->
<script src="/js/plugins/smooth-scroll.js"></script>
<!-- scroll trigger js -->
<script src="/js/plugins/ScrollTrigger.min.js"></script>
<!-- scroll to js -->
<script src="/js/plugins/ScrollTo.min.js"></script>
<!-- ashley js -->
<script src="/js/main.js"></script>
<!-- page scripts (only where needed) -->
<script src="/js/contact-form.js"></script>
Libraries
LibraryWhat it is used for
jQueryBase for main.js and Fancybox.
SwupSmooth transitions between pages without a full reload.
SwiperSliders (projects, testimonials, galleries).
FancyboxLightbox for images and certificates.
GSAP + ScrollTrigger + ScrollToAnimations: preloader, reveal on scroll, progress bar, smooth jumps to anchors.
Smooth scrollSofter mouse-wheel scrolling.
Site scripts
FileLoaded onWhat it does
main.jsEvery pageSwup, language switch, preloader, cursor, menu, progress bar, scroll animations, sliders and Fancybox.
contact-form.jsContactSends the form to mail.php without reloading and shows the result message.
certifications.jsHome, certificationsConfigures the Fancybox lightbox for certificates.
recognition.jsHomeThe interactive recognition block.
documentation.jsThis pageHighlights the current section in the index and powers the "Copy" buttons.
track.jsNone yetIn-house analytics, ready but not switched on (see its section).
i18n.js, translations.jsNoneOld translation engine, no longer used. Kept on disk for reference only.
Important when writing JavaScript: with Swup the page is not really reloaded, only #swupMain is swapped. A page-specific script therefore has to start again on the swup:contentReplaced event and clean up (listeners, timers, ScrollTriggers) on swup:willReplaceContent. The "REINIT" section at the end of main.js and the files recognition.js and documentation.js show how.

Preloader

Every page starts with a short black-screen animation in two stages, run by GSAP from the "preloader" section of js/main.js:

  1. Three words (.mil-animation-1) appear and fade one after another.
  2. The domain (.mil-animation-2): a colour block sweeps across and reveals samueltorres.dev.
  3. The screen fades out, gets the mil-hidden class and the page becomes usable.
<div class="mil-preloader">
    <div class="mil-preloader-animation">
        <div class="mil-pos-abs mil-animation-1">
            <p class="mil-h3 mil-muted mil-thin">Applied</p>
            <p class="mil-h3 mil-muted">Engineering</p>
            <p class="mil-h3 mil-muted mil-thin">Results</p>
        </div>
        <div class="mil-pos-abs mil-animation-2">
            <div class="mil-reveal-frame">
                <p class="mil-reveal-box"></p>
                <p class="mil-h3 mil-muted mil-thin">samueltorres.dev</p>
            </div>
        </div>
    </div>
</div>
To change the text: it is plain HTML, so edit it directly. There are no separate letters to rebuild. The markup is copied into each of the 64 pages, so a change has to be made in every .html file (a search-and-replace across the folder does it) and every one of them has to be uploaded again.

Contact form

The form on contact.html / contact-es.html sends an email to Samuel's inbox. It involves three pieces:

  1. HTML: <form id="mil-contact-form" action="/mail.php" method="POST"> with the fields name, email, message and a hidden anti-bot field, website. The data-msg-* attributes hold the messages shown to the visitor in each language.
  2. js/contact-form.js: checks that nothing is empty, disables the button ("Sending…"), sends the data with fetch plus the X-Requested-With: XMLHttpRequest header, and then shows the success or error message.
  3. mail.php: validates everything again on the server and sends the email with PHP's mail() function.
Checks in mail.php, in order
#CheckIf it fails
1The request is a POST405 method_not_allowed
2It carries the X-Requested-With header403 forbidden
3It comes from samueltorres.dev or www.samueltorres.dev (Origin/Referer, list in ALLOWED_ORIGINS)403 forbidden
4That IP has not sent 3 requests in the last 15 minutes429 rate_limited
5The hidden website field is empty (a bot would fill it in)Replies "success" but sends nothing
6Name, email and message are filled in400 missing_fields
7The email address is valid400 invalid_email
8The server manages to send the email500 mail_failed

When everything passes, the answer is {"success": true}. The email goes out as [email protected] with the visitor's address in Reply-To, so clicking "Reply" in Gmail answers the visitor directly. The recipient is written into the server code and never comes from the browser, which means nobody can use the form to send email to anyone else.

If emails do not arrive:
  1. Check the Spam folder.
  2. In cPanel, open Email → Email Deliverability and make sure SPF and DKIM for samueltorres.dev show as valid. Without them Gmail usually rejects or hides mail from no-reply@.
  3. Use Email → Track Delivery to see whether the server sent it and what the receiving server answered.
  4. If the visitor sees "Something went wrong", PHP could not send the email. Look in the error_log file in public_html.
  5. While testing, remember the limit of 3 attempts every 15 minutes per IP, which counts failed attempts too.

In-house analytics

The site includes its own visit counter, with no Google Analytics or any other third party. It is built but not switched on: no page loads track.js yet. It has four parts:

PartWhat it does
js/track.jsSends a single request per page view (page, title, referrer, language) to /api/collect.php. No cookies or localStorage, and it does nothing if the browser has "Do Not Track" on.
api/collect.phpOnly accepts requests from the site's own domain and saves the visit in MySQL (sessions and visits tables).
admin/Password-protected panel: overview, visits, most-viewed pages and sessions.
cron/purge.phpDeletes data older than 90 days. Runs from the hosting's cron, never from the browser.

Privacy: the IP address is never stored. The server keeps only a one-way fingerprint (SHA-256 of IP + browser + the day's date) that changes every day, so it groups the pages seen in one visit but cannot identify anyone or follow them from one day to the next. Full steps to switch it on (database, config/db.php, first admin, cron) are in README-analytics.md.

SEO

  • canonical: each page declares its own final URL, so Google does not treat variants as duplicates.
  • hreflang: each page points to its English twin, its Spanish twin, and an x-default. This tells Google which version to show to each visitor.
  • Open Graph: the title, description and image (img/og-cover.jpg) used when a link is shared on LinkedIn, WhatsApp and similar apps. Currently present on the home pages, the portfolio, the case studies and the certifications page.
  • sitemap.xml: the list of the 60 public URLs with their language pairs. Do not edit it by hand. Regenerate it with php tools/build-sitemap.php from the project root. The script skips 404.html, documentation*.html and the retired home-1-es.html.
  • robots.txt: allows the whole site, blocks /admin/, /api/, /config/, /sql/, /docs/, /cron/ and /tools/, and points to the sitemap.

Server (.htaccess)

The .htaccess file in the root tells Apache how to serve the site:

RuleEffect
HTTPSEvery http:// request is redirected (301) to https://.
No wwwwww.samueltorres.dev is redirected (301) to samueltorres.dev.
Old home/home-1-es.html is redirected (301) to /.
Error pageAny missing URL shows 404.html.
No folder listingOptions -Indexes: nobody can browse a folder's contents.
Security headersnosniff, X-Frame-Options: SAMEORIGIN (the site cannot be embedded on another site) and Referrer-Policy.
CachingHTML is never cached (changes show up immediately). Images, CSS and JS are cached for 1 year.
Compressiongzip / Brotli when the server supports it.
Watch out for the 1-year cache: if you change css/style.css or a .js file and upload it, visitors who already had the old one may keep it. To force the update, change the reference in the HTML (for example style.css?v=2). To check your own changes, reload with Ctrl+F5.
Shared folder: the hosting account's public_html also contains other sites in subfolders, and they inherit this .htaccess. That is why every rule is wrapped in <IfModule> and targets specific paths. After changing it, test the whole site and keep the previous copy to hand in case you need to roll back.

Deployment

Changes are published by hand, uploading files through the cPanel File Manager into public_html, keeping the same folder structure as the project.

  1. Make and review the changes locally (open the pages in the browser).
  2. If you touched SCSS, compile with Prepros so css/style.css gets updated.
  3. If you added or removed pages, regenerate sitemap.xml.
  4. Note the changed files in DEPLOY-PENDIENTE.txt (a local list in the form file → upload to public_html/folder/, never uploaded or committed to git).
  5. Upload each file to its folder in public_html, replacing the old one.
  6. Check the live site: the edited pages, the ES · EN switch and, if you touched mail.php, send a test from the form.
Never upload: *.bak, *.bak2, *.zip, config/ (except with analytics), sql/, tools/, scss/, *.md, contexto-completo.txt or anything in docs/ other than docs/certificates/. The server delivers them as plain text or as downloads to anyone who guesses the URL. If one is already on the server, delete it.

Fonts

  • Outfit (weights 100–900) from Google Fonts. It is imported in scss/_variables.scss and used across the whole site.
  • Code blocks on this page use the system monospace font (Courier New).

Icons

  • Font Awesome 5: social-network icons, envelope, arrows. Usage: <i class="fab fa-linkedin"></i> (brands) or <i class="fas fa-chevron-down"></i> (solid).
  • Inline SVGs from the template: the cursor arrow, the decorative lines and the arrows on buttons (in .mil-hidden-elements).
  • Site icons: favicon.ico, favicon-16x16.png, favicon-32x32.png, apple-touch-icon.png and img/icon-192.png / icon-512.png (declared in site.webmanifest).

Versión en español

Professional contact:

Mexico City

Mexico City, Mexico Available immediately

Availability

Remote and on-site projects MXN · USD

© 2026 Eng. Samuel Torres. Applied Engineering.