Webification Repo Template Development - PuzzleServer/mainpuzzleserver GitHub Wiki

Back to the Webification hub

To help with creating new events, here are some full-featured templates designed specifically to work with our website. All of the places that need to be changed for each specific puzzle are marked with CHANGE_ME:

<!DOCTYPE html>
<html lang="en-us">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- CHANGE_ME: title of your puzzle, followed by - Microsoft Intern Puzzleday 2024 -->
    <title>Example Puzzle - Microsoft Intern Puzzleday 2024</title>
    <link rel="stylesheet" href="../../resources/puzzle-base-styles.css">
    <link rel="stylesheet" href="../../resources/puzzle-print-styles.css" media="print">
    <script type="text/javascript" src="../../resources/resize.js"></script>
    <!-- CHANGE_ME: To use the puzzlejs library, include the following three lines -->
    <!-- <link rel="stylesheet" href="../../resources/puzzle.css"/> -->
    <!-- <link rel="stylesheet" href="../../resources/puzzle-basic-colors.css"/> -->
    <!-- <script type="text/javascript" src="../../resources/puzzle.js"></script> -->
    <!-- Class names used by puzzle.js that should not be manually used or overridden:
        .across                     .black                      .black-cell                     .blue
        .blue-laser                 .border-bottom              .border-top                     .border-left
        .border-right               .bottom-clue                .cell                           .class-fill
        .clipboard-button           .clue                       .commands                       .copy-only
        .corner-focus               .crossword-clues            .darkgray                       .down
        .edge-base                  .extract                    .extract-code                   .given
        .given-fill                 .given-text                 .gray                           .green
        .green-laser                .hovered                    .indigo                         .indigo-laser
        .inner-cell                 .interesting                .left-clue                      .lightgray
        .marked                     .no-copy                    .no-input                       .nopointer
        .orange                     .orange-laser               .outer-cell                     .path
        .puzzle-about               .puzzle-about-back          .puzzle-about-button            .puzzle-about-close
        .puzzle-about-credits       .puzzle-about-savedstate    .puzzle-about-scroller          .puzzle-commands
        .puzzle-entry               .puzzle-redo-button         .puzzle-reset-button            .puzzle-undo-button
        .red                        .red-laser                  .reticle-back                   .reticle-front
        .right-clue                 .small-text                 .strikethrough                  .text
        .top-clue                   .unselectable               .violet                         .violet-laser
        .white                      .white-laser                .x-mode                         .yellow
        .yellow-laser -->
    <!-- CHANGE_ME: add necessary page-specific styles for this puzzle only -->
    <style>
        .typable {
            display: inline-block;
            box-sizing: border-box;
            width: 12px;
            border-bottom: 2px solid black;
            color: red;
            text-transform: uppercase;
        }

        .spoiler {
            background-color: black;
            color: black;
            font-weight: bold;
        }

        .example-help {
            display: block;
            margin: 0 auto;
        }
    </style>
    <!-- CHANGE_ME: add necessary page-specific scripts for this puzzle only -->
    <script>
        window.onload = function example_events() {
            for (ced of document.getElementsByClassName("typable")) {
                ced.addEventListener("keypress", example_onKeyPress);
            }
        }

        function example_onKeyPress() {
            window.getSelection().selectAllChildren(this);
        }

        function example_unSpoiler() {
            for (elem of document.getElementsByClassName("spoiler")) {
                elem.classList.remove("spoiler");
                elem.classList.add("answer-blue");
            }
        }
    </script>
</head>
<body>
<div class="page-div">
    <div class="header-div">
        <h1>
            <!-- CHANGE_ME: title, same as page title above but without - Microsoft Intern Puzzleday 2024 -->
            Example Puzzle
        </h1>
        <div class="byline-div">
            <!-- CHANGE_ME: author, just your name, no By: or anything like that -->
            Philip Z Loh
        </div>
        <!-- CHANGE_ME: if your puzzle has Data Confirmation, include this line -->
        <!-- <div class="has-dc" title="Data Confirmation checks are available for this puzzle."></div> -->
    </div>
    <div class="content-div">
        <!-- CHANGE_ME: puzzle content goes here -->
        <h4>Interactivity</h4>
        <p>
            Puzzles will look like this! <b>Many of them are interactive</b> - if it looks like you can click around /
            tab around / start typing, you're probably right! You won't need to View Source or go hacking around the
            code though; that's not fun.
        </p>
        <h4>Incorrect Answers</h4>
        <p><b>Go ahead and submit your most creative incorrect answer</b> above to
            get a feel for what an incorrect answer's response looks like.</p>
        <h4>Partial Answers</h4>
        <p>
            If you make progress on the puzzle and get something that looks like a phrase, type it into the answer
            submission page! <b>Partial answers will be confirmed</b> to tell you you're on the right track.</p>
        <ul>
            <li>
                z<div contenteditable="true" class="typable"></div>ro
            </li>
            <li>o<div contenteditable="true" class="typable"></div>e
            </li>
            <li>
                <div contenteditable="true" class="typable"></div>wo
            </li>
            <li>thr<div contenteditable="true" class="typable"></div>e</li>
            <li>fou<div contenteditable="true" class="typable"></div>
            </li>
            <li>fiv<div contenteditable="true" class="typable"></div>
            </li>
            <li>
                <div contenteditable="true" class="typable"></div>ix
            </li>
            <li>
                <div contenteditable="true" class="typable"></div>even
            </li>
            <li>
                <div contenteditable="true" class="typable"></div>ight
            </li>
            <li>nin<div contenteditable="true" class="typable"></div>
            </li>
            <li>t<div contenteditable="true" class="typable"></div>n</li>
            <li>ele<div contenteditable="true" class="typable"></div>en</li>
            <li>twelv<div contenteditable="true" class="typable"></div>
            </li>
            <li>thirte<div contenteditable="true" class="typable"></div>n</li>
            <li>fourt<div contenteditable="true" class="typable"></div>en</li>
            <li>fifte<div contenteditable="true" class="typable"></div>n</li>
            <li>sixt<div contenteditable="true" class="typable"></div>en</li>
            <li>sev<div contenteditable="true" class="typable"></div>nteen</li>
            <li>eightee<div contenteditable="true" class="typable"></div>
            </li>
            <li>ni<div contenteditable="true" class="typable"></div>eteen</li>
        </ul>
        <p>
            Click on this chunk to see what partial answer you can submit for Example Puzzle:
            <span class="spoiler" onclick="example_unSpoiler()">ENTERESSEEEVEEEEEENN</span>.
        </p>
        <h4>Correct Answers</h4>
        <p>
            <b>Typing the correct answer in</b> solves the puzzle! Example Puzzle is worth 0
            points.
        </p>
        <h4>Asking for Help</h4>
        <p>If you're stuck and not having fun, <b>consider asking for help</b>. Use the link on that specific puzzle's
            submission page. Go ahead and try clicking on the link for Example Puzzle, just to see what happens and to
            make sure you have your email setup working.</p>
        <img class="example-help" src="ask-for-help.png" title="Screenshot showing where to click for help" style="max-width: 100%;">
        <!-- END OF PUZZLE CONTENT -->
    </div>
</div>
</body>
</html>

By default, PuzzleJS is NOT included in a puzzle unless it's actually needed since anything added to a puzzle makes it take longer to load. There's also a corresponding answer template, and it also has the option to include PuzzleJS since PuzzleJS can be used in a non-editable state to easily show the solution to a puzzle:

<!DOCTYPE html>
<html lang="en-us">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- CHANGE_ME: check mark, followed by the title of your puzzle, followed by - Microsoft Intern Puzzleday 2024 -->
    <title>βœ” Example Puzzle - Microsoft Intern Puzzleday 2024</title>
    <link rel="stylesheet" href="../../resources/puzzle-base-styles.css">
    <link rel="stylesheet" href="../../resources/puzzle-print-styles.css" media="print">
    <!-- CHANGE_ME: To use the puzzlejs library, include the following three lines -->
    <!-- <link rel="stylesheet" href="../../resources/puzzle.css"/> -->
    <!-- <link rel="stylesheet" href="../../resources/puzzle-basic-colors.css"/> -->
    <!-- <script type="text/javascript" src="../../resources/puzzle.js"></script> -->
    <!-- Class names used by puzzle.js that should not be manually used or overridden:
        .across                     .black                      .black-cell                     .blue
        .blue-laser                 .border-bottom              .border-top                     .border-left
        .border-right               .bottom-clue                .cell                           .class-fill
        .clipboard-button           .clue                       .commands                       .copy-only
        .corner-focus               .crossword-clues            .darkgray                       .down
        .edge-base                  .extract                    .extract-code                   .given
        .given-fill                 .given-text                 .gray                           .green
        .green-laser                .hovered                    .indigo                         .indigo-laser
        .inner-cell                 .interesting                .left-clue                      .lightgray
        .marked                     .no-copy                    .no-input                       .nopointer
        .orange                     .orange-laser               .outer-cell                     .path
        .puzzle-about               .puzzle-about-back          .puzzle-about-button            .puzzle-about-close
        .puzzle-about-credits       .puzzle-about-savedstate    .puzzle-about-scroller          .puzzle-commands
        .puzzle-entry               .puzzle-redo-button         .puzzle-reset-button            .puzzle-undo-button
        .red                        .red-laser                  .reticle-back                   .reticle-front
        .right-clue                 .small-text                 .strikethrough                  .text
        .top-clue                   .unselectable               .violet                         .violet-laser
        .white                      .white-laser                .x-mode                         .yellow
        .yellow-laser -->
    <!-- CHANGE_ME: add necessary page-specific styles for this puzzle only -->
    <style>
        
    </style>
    <!-- CHANGE_ME: add necessary page-specific scripts for this puzzle only -->
    <script>
        
    </script>
</head>
<body>
<div class="page-div">
    <div class="header-div">
        <h1 class="solution-h1">
            <!-- CHANGE_ME: title, same as page title above but without checkmark or - Microsoft Intern Puzzleday 2024 -->
            Example Puzzle
        </h1>
        <div class="byline-div">
            <!-- CHANGE_ME: author, just your name, no By: or anything like that -->
            Philip Z Loh
        </div>
        <div class="answer-div">
            <!-- CHANGE_ME: primary answer, no need to put it in uppercase -->
            The One Canonical Answer
        </div>
    </div>
    <div class="content-div">
        <!-- CHANGE_ME: solution content goes here -->
        <p>
            This is a puzzle. <span class="answer-red">Puzzle text puzzle text</span>. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
            eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
            exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit
            in voluptate velit esse cillum dolore eu fugiat nulla pariatur. <span class="answer-blue">Excepteur sint occaecat cupidatat non
            proident</span>, sunt in culpa qui officia deserunt mollit anim id est laborum.
        </p>

        <ol>
            <li>🍎</li>
            <li>Bravo</li>
            <li>Gamma</li>
            <li>〇</li>
            <li>5️⃣</li>
            <li>6</li>
            <li>δΈƒ</li>
            <li>μ—¬λŸ</li>
            <li>???</li>
        </ol>

        <table>
            <tr>
                <th>🍎</th>
                <th>Bravo</th>
                <th>Gamma</th>
            </tr>
            <tr>
                <td>〇</td>
                <td>5️⃣</td>
                <td>6</td>
            </tr>
            <tr>
                <td>δΈƒ</td>
                <td>μ—¬λŸ</td>
                <td>???</td>
            </tr>
        </table>
        <!-- END OF SOLUTION CONTENT -->
    </div>
</div>
</body>
</html>

The referenced CSS files are here. Note that the one thing not here is the Data Confirmation icon the CSS files use, since that may need to change year-to-year for style reasons:

/* Globals and Constants */
:root {
  --ink-black: rgb(51, 51, 51);
  --ruby-red: rgb(206, 10, 10);
  --evening-blue: rgb(10, 10, 206);
  --content-margin: max(calc(100vw - 600px) / 2 - 400px, 40px);
  --dc-width: 50px;
}

html {
  margin: 0px;
  padding: 0px;
}

body {
  min-width: 600px;
  width: 100%;
  margin: 0px;
  padding: 0px;
  color: var(--ink-black);
  font-size: 1.2rem;
  font-family: Tahoma, Verdana, sans-serif;
}

.page-div {
  padding: 0px;
  margin: 20px;
}

.header-div {
  margin: 30px 0px;
  position: relative;
}

.has-dc {
  background-image: url("../../images/comic-dc.png");
  background-repeat: no-repeat;
  background-size: 100%;
  position: absolute;
  top: 60px;
  right: calc(var(--content-margin) + 20px);
  width: var(--dc-width);
  height: var(--dc-width);
}


h1 {
  padding: 0px;
  margin: 0px;
  font-size: 48px;
  font-weight: bold;
  letter-spacing: 1px;
  text-align: center;
}

h1.solution-h1::before {
  content: "βœ”";
  color: var(--ruby-red);
}

.byline-div {
  font-size: 18px;
  font-weight: bold;
  letter-spacing: 1px;
  text-align: center;
  margin: 10px 0px;
}

.byline-div::before {
  content: "by ";
}

.answer-div {
  color: var(--ruby-red);
  font-size: 18px;
  font-weight: bold;
  text-transform: uppercase;
  text-align: center;
}

.answer-div::before {
  color: var(--ink-black);
  font-size: 18px;
  font-weight: bold;
  text-transform: none;
  content: "Answer: ";
}

.content-div {
  padding: 20px var(--content-margin);
  margin: 0px;
  border: 6px solid #000000;
}

.answer-red {
  color: var(--ruby-red);
  text-transform: uppercase;
  font-weight: bold;
}

.answer-blue {
  color: var(--evening-blue);
  text-transform: uppercase;
  font-weight: bold;
}

@media screen {
  .print-only {
    display: none;
  }
}

And here is the base print style file:

@page {
  margin: 0in;
}

body {
  font-size: 0.8rem;
}

.page-div {
  margin: 0px;
}

.header-div {
  position: absolute;
  display: grid;
  grid-template-columns: 540px 200px;
}

.has-dc {
  -webkit-print-color-adjust: exact;
  background-size: cover;
  position: absolute;
  top: 10px;
  left: 750px;
  width: 25px;
  height: 25px;
}

h1 {
  font-size: 28px;
  margin-left: 0.5in;
  margin-top: 10px;
  text-align: left;
  text-transform: uppercase;
  grid-column: 1;
}

.byline-div {
  font-size: 14px;
  line-height: 48px;
  margin: 0px;
  text-align: right;
  grid-column: 2;
}

.answer-div {
  grid-column: 1;
  margin-left: -20px;
}

.content-div {
  padding: 1in 0.5in 0in 0.5in;
  border: none;
}

.online-only {
  display: none;
}

Finally, here's the resizing script:

window.addEventListener("message", (e) => {
    if ((e.origin.includes("puzzlehunt.azurewebsites.net")) || (e.origin.includes("localhost"))) {
        e.source.postMessage({ width: document.body.scrollWidth, height: document.body.scrollHeight }, e.origin);
    }
});

document.addEventListener("DOMContentLoaded", () => {
    const params = new URLSearchParams(window.location.search);
    if (params.has("embed") && (params.get("embed").length > 0) && (params.get("embed") === "true")) {
        const contents = document.querySelector(".content-div");
        if ((contents !== null) && (contents !== undefined)) {
            contents.style.padding = "0px";
            contents.style.borderWidth = "0px";
        }
        const page = document.querySelector(".page-div");
        if ((page !== null) && (page !== undefined)) {
            page.style.margin = "0px";
        }
        const header = document.querySelector(".header-div");
        if ((header !== null) && (header !== undefined)) {
            header.parentElement.removeChild(header);
        }
        document.body.style.overflowY = "hidden";
    }
    else {
        document.body.style.overflowY = "scroll";
    }
    if (params.has("print") && (params.get("print").length > 0) && (params.get("print") === "true")) {
        window.onafterprint = window.close;
        window.print();
    }
});

A less-featured version of this file is also part of the website in case you don't want to provide your own in the Shared Resources. This one does have a few extra features though. First, when the puzzle loads, if it sees that it's being embedded, it removes the title, author, outline, and extra space around the puzzle since the embedding website takes care of all of that. It also makes sure no extra scrollbars appear on the puzzle's iframe. Finally, it also listens for when the website tells the puzzle to print (as opposed to the user telling it to) and automatically pops the print dialog then closes the new tab when the print is done. This will happen when the user clicks the link on the puzzle to print it or hits ctrl+P but not if the browser's ... menu is used.

For help with how to set up the file structure for webifying multiple puzzles and upload them, see the admin wiki for event customization.

⚠️ **GitHub.com Fallback** ⚠️