PHPIndex

This page lists files in the current directory. You can view content, get download/execute commands for Wget, Curl, or PowerShell, or filter the list using wildcards (e.g., `*.sh`).

index.html
wget 'https://sme10.lists2.roe3.org/mdrone/ccsgen/index.html'
View Content
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <title>Christmas Cross-Stitch Message Generator</title>
    <link rel="stylesheet" href="./style.css">

  </head>
    
  <body>
  <main>
  <header>
    <h1>Christmas Cross-Stitch Message Generator</h1>
    <div class="controls">
      <textarea id="message" placeholder="Write your Christmas message…" size="40" ></textarea>
      <button type="button" id="btn-generate">Generate</button>
      <button type="button" id="btn-clear">Clear</button>
    </div>
  </header>

  <div id="stitches" class="stitches"></div>

</main>

    <script  src="./script.js"></script>
<br><br><br><br>
<p style="color:#ffffff;">(Hint: To save the result, take a screenshot and use a graphics program to crop the image)</p>

  </body>
  
</html>
script.js
wget 'https://sme10.lists2.roe3.org/mdrone/ccsgen/script.js'
View Content
console.clear();
const FIRST_WORD = "Merry Christmas";
const CHAR_BITMAPS = {
  "A": [
    "0220",
    "2002",
    "2002",
    "2222",
    "2002",
    "2002"
  ],
  "B": [
    "2220",
    "2002",
    "2220",
    "2002",
    "2002",
    "2220"
  ],
  "C": [
    "0220",
    "2002",
    "2000",
    "2000",
    "2002",
    "0220"
  ],
  "D": [
    "2220",
    "2002",
    "2002",
    "2002",
    "2002",
    "2220"
  ],
  "E": [
    "2222",
    "2000",
    "2220",
    "2000",
    "2000",
    "2222"
  ],
  "F": [
    "2222",
    "2000",
    "2220",
    "2000",
    "2000",
    "2000"
  ],
  "G": [
    "0220",
    "2002",
    "2000",
    "2022",
    "2002",
    "0220"
  ],
  "H": [
    "2002",
    "2002",
    "2222",
    "2002",
    "2002",
    "2002"
  ],
  "I": [
    "222",
    "020",
    "020",
    "020",
    "020",
    "222"
  ],
  "J": [
    "2222",
    "0020",
    "0020",
    "0020",
    "2020",
    "0200"
  ],
  "K": [
    "2002",
    "2020",
    "2200",
    "2020",
    "2002",
    "2002"
  ],
  "L": [
    "200",
    "200",
    "200",
    "200",
    "200",
    "222"
  ],
  "M": [
    "20002",
    "22022",
    "20202",
    "20002",
    "20002",
    "20002"
  ],
  "N": [
    "2002",
    "2202",
    "2022",
    "2002",
    "2002",
    "2002"
  ],
  "O": [
    "0220",
    "2002",
    "2002",
    "2002",
    "2002",
    "0220"
  ],
  "P": [
    "2220",
    "2002",
    "2220",
    "2000",
    "2000",
    "2000"
  ],
  "Q": [
    "0220",
    "2002",
    "2002",
    "2002",
    "2022",
    "0220"
  ],
  "R": [
    "2220",
    "2002",
    "2220",
    "2002",
    "2002",
    "2002"
  ],
  "S": [
    "0220",
    "2002",
    "0220",
    "0002",
    "2002",
    "0220"
  ],
  "T": [
    "222",
    "020",
    "020",
    "020",
    "020",
    "020"
  ],
  "U": [
    "2002",
    "2002",
    "2002",
    "2002",
    "2002",
    "0220"
  ],
  "V": [
    "2002",
    "2002",
    "2002",
    "2002",
    "0220",
    "0200"
  ],
  "W": [
    "20002",
    "20002",
    "20002",
    "20202",
    "22022",
    "20002"
  ],
  "X": [
    "2002",
    "2002",
    "0220",
    "2002",
    "2002",
    "2002"
  ],
  "Y": [
    "2002",
    "2002",
    "0222",
    "0002",
    "2002",
    "0220"
  ],
  "Z": [
    "2222",
    "0002",
    "0020",
    "0200",
    "2000",
    "2222"
  ],
  "0": [
    "0220",
    "2002",
    "2002",
    "2002",
    "2002",
    "0220"
  ],
  "1": [
    "220",
    "020",
    "020",
    "020",
    "020",
    "222"
  ],
  "2": [
    "0220",
    "2002",
    "0020",
    "0200",
    "2000",
    "2222"
  ],
  "3": [
    "2220",
    "0002",
    "0220",
    "0002",
    "0002",
    "2220"
  ],
  "4": [
    "2002",
    "2002",
    "2222",
    "0002",
    "0002",
    "0002"
  ],
  "5": [
    "2222",
    "2000",
    "2220",
    "0002",
    "0002",
    "2220"
  ],
  "6": [
    "0220",
    "2000",
    "2220",
    "2002",
    "2002",
    "0220"
  ],
  "7": [
    "2222",
    "0002",
    "0020",
    "0200",
    "0200",
    "0200"
  ],
  "8": [
    "0220",
    "2002",
    "0220",
    "2002",
    "2002",
    "0220"
  ],
  "9": [
    "0220",
    "2002",
    "2002",
    "0222",
    "0002",
    "0220"
  ],
  " ": [
    "0000",
    "0000",
    "0000",
    "0000",
    "0000",
    "0000"
  ],
  "'": [
    "2",
    "2",
    "0",
    "0",
    "0",
    "0"
  ],
  ",": [
    "0",
    "0",
    "0",
    "0",
    "2",
    "2"
  ],
  ".": [
    "0",
    "0",
    "0",
    "0",
    "0",
    "2"
  ],
  "!": [
    "2",
    "2",
    "2",
    "2",
    "0",
    "2"
  ],
  "?": [
    "0220",
    "2002",
    "0002",
    "0020",
    "0000",
    "0020"
  ],
  "¿": [
    "0200",
    "0000",
    "0200",
    "2000",
    "2002",
    "0220"
  ],
	"@": [
  "02220",
  "20002",
  "20222",
  "20202",
  "20022",
  "02220"
],
"#": [
  "02020",
  "22222",
  "02020",
  "22222",
  "02020",
  "02020"
],
"-": [
  "000",
  "000",
  "222",
  "000",
  "000",
  "000"
]
};
const CHRISTMAS_SYMBOLS = {
  
  "STAR": [
    "20202",
    "02220",
    "22222",
    "02220",
    "20202"
  ],
  "TREE": [
    "00200",
    "02220",
    "22222",
    "00200",
    "00200"
  ],
  "BELL": [
    "00200",
    "02220",
    "02220",
    "22222",
    "00200"
  ],
  
  "CANDY": [
    "00200",
    "02220",
    "22222",
    "02220",
    "00200"
  ]
};

// config
const SETTINGS = {
  GRID_COLS: 149,
  GRID_ROWS: 50,              // initial value - will expand if needed
  MSG_CHAR_HEIGHT: 7,
  MSG_SPACING: 1,
  MSG_SPACE_WIDTH: 1,
  VERTICAL_MESSAGE_PADDING: 6,    // empty rows between strips and message
  STRIP_PADDING_ROWS: 2,
  STRIP_HORIZONTAL_PADDING: 1,
  STRIP_BETWEEN_SYMBOLS: 1,
  STRIP_DASHED: true,
  STRIP_COLOR: "checkbox-white",
  COLOR_CLASSES: ["checkbox-red", "checkbox-white"],
	ALLOWED_MESSAGE_CHARS: "A-Z0-9 ,.'¡!?¿@#\\-"
};

// selectors
const grid = document.getElementById("stitches");
const btnGenerate = document.getElementById("btn-generate");
const btnClear = document.getElementById("btn-clear");
const messageInput = document.getElementById("message");

// set CSS variable for grid columns
grid.style.setProperty("--grid-cols", SETTINGS.GRID_COLS);

// create checkbox grid
let checkboxes = [];
function createGrid(rows) {
  grid.innerHTML = "";
  checkboxes = [];
  for (let i = 0; i < SETTINGS.GRID_COLS * rows; i++) {
    const cb = document.createElement("input");
    cb.type = "checkbox";
    grid.appendChild(cb);
    checkboxes.push(cb);
  }
  grid.style.setProperty("--grid-rows", rows);
}
createGrid(SETTINGS.GRID_ROWS);

// function - draw a symbol at a given position
// ------------------------------
function drawSymbolAt(pattern, startX, startY) {
  const height = pattern.length;
  const width = pattern[0].length;
  for (let y = 0; y < height; y++) {
    const row = pattern[y] || "";
    for (let x = 0; x < width; x++) {
      if (row[x] === "2") {
        const gx = startX + x;
        const gy = startY + y;
        if (gx >= 0 && gx < SETTINGS.GRID_COLS && gy >= 0 && gy < SETTINGS.GRID_ROWS) {
          const idx = gy * SETTINGS.GRID_COLS + gx;
          checkboxes[idx].checked = true;
          checkboxes[idx].className = SETTINGS.COLOR_CLASSES[(x + y) % SETTINGS.COLOR_CLASSES.length];
        }
      }
    }
  }
}

// function - draw a full stitch line
function drawStitchLine(y, dashed = false, colorClass = SETTINGS.STRIP_COLOR) {
  if (y < 0 || y >= SETTINGS.GRID_ROWS) return;
  const step = dashed ? 2 : 1;
  for (let x = 0; x < SETTINGS.GRID_COLS; x += step) {
    const idx = y * SETTINGS.GRID_COLS + x;
    checkboxes[idx].checked = true;
    checkboxes[idx].className = colorClass;
  }
}

// function - draw top/bottom strips
function drawSymbolStrip(options = {}) {
  const {
    paddingRows = SETTINGS.STRIP_PADDING_ROWS,
    horizontalPadding = SETTINGS.STRIP_HORIZONTAL_PADDING,
    betweenSymbols = SETTINGS.STRIP_BETWEEN_SYMBOLS,
    dashed = SETTINGS.STRIP_DASHED,
    stitchColor = SETTINGS.STRIP_COLOR
  } = options;

  const keys = Object.keys(CHRISTMAS_SYMBOLS);
  if (!keys.length) return { topReserved: 0, bottomReserved: 0 };

  // pick two distinct symbols
  let a = Math.floor(Math.random() * keys.length);
  let b;
  do { b = Math.floor(Math.random() * keys.length); } while (b === a && keys.length > 1);

  const symbolA = CHRISTMAS_SYMBOLS[keys[a]];
  const symbolB = CHRISTMAS_SYMBOLS[keys[b]] || symbolA;
  const stripHeight = Math.max(symbolA.length, symbolB.length);

  // Draw a single strip at given stitchTopRow
  function drawStripAt(stitchTopRow) {
    const symbolsStartRow = stitchTopRow + 1 + paddingRows;
    const symbolsBottomRow = symbolsStartRow + stripHeight - 1;
    const stitchBottomRow = symbolsBottomRow + paddingRows + 1;

    drawStitchLine(stitchTopRow, dashed, stitchColor);

    // compute symbols layout
    const patterns = [];
    for (let i = 0; i < Math.ceil(SETTINGS.GRID_COLS / 2); i++) patterns.push(i % 2 === 0 ? symbolA : symbolB);

    // place as many as fit
    const placed = [];
    let totalWidth = 0;
    for (let i = 0; i < patterns.length; i++) {
      const w = patterns[i][0].length;
      const newTotal = placed.length === 0 ? totalWidth + w : totalWidth + betweenSymbols + w;
      if (newTotal + horizontalPadding * 2 > SETTINGS.GRID_COLS) break;
      placed.push(patterns[i]);
      totalWidth = newTotal;
    }

    const startX = Math.floor((SETTINGS.GRID_COLS - totalWidth) / 2);
    let cursorX = startX;

    for (let pat of placed) {
      drawSymbolAt(pat, cursorX, symbolsStartRow);
      cursorX += pat[0].length + betweenSymbols;
    }

    drawStitchLine(stitchBottomRow, dashed, stitchColor);

    return { top: stitchTopRow, bottom: stitchBottomRow };
  }

  // top strip (stitchTopRow = paddingRows)
  const topReservedRange = drawStripAt(paddingRows);

  // bottom strip (calculate based on expanded GRID_ROWS)
  const bottomStitchBottom = SETTINGS.GRID_ROWS - 1 - paddingRows;
  const bottomStitchTop = bottomStitchBottom - (stripHeight + paddingRows * 2 + 1);
  const bottomReservedRange = drawStripAt(bottomStitchTop);

  const topReservedRows = topReservedRange.bottom + 1;
  const bottomReservedRows = SETTINGS.GRID_ROWS - bottomReservedRange.top;

  return { topReserved: topReservedRows, bottomReserved: bottomReservedRows };
}


// function - draw message on grid
function drawMessage(msg) {
  // clear all checkboxes
  checkboxes.forEach(cb => {
    cb.checked = false;
    cb.className = "";
  });


	// normalize message
	msg = (msg || "")
  .toUpperCase()
  .replace(new RegExp(`[^${SETTINGS.ALLOWED_MESSAGE_CHARS}\n]`, "g"), "");
  if (!msg) return;

  // width of a character ---
  function charWidthOf(ch) {
    if (ch === " ") return SETTINGS.MSG_SPACE_WIDTH;
    const pattern = CHAR_BITMAPS[ch];
    return pattern && pattern[0] ? pattern[0].length : SETTINGS.MSG_SPACE_WIDTH;
  }

  // Ssplit into lines
  const rawLines = msg.split(/\r?\n/);

  // build lines array
  const lines = [];
  for (const rawLine of rawLines) {
    const words = rawLine.split(" ").filter(Boolean);
    let currentLine = [];
    let currentWidth = 0;

    for (const word of words) {
      let wordWidth = 0;
      for (const ch of word) wordWidth += charWidthOf(ch) + SETTINGS.MSG_SPACING;
      wordWidth -= SETTINGS.MSG_SPACING; // remove extra spacing

      const extraSpace = currentLine.length > 0 ? SETTINGS.MSG_SPACE_WIDTH + SETTINGS.MSG_SPACING : 0;

      if (currentWidth + extraSpace + wordWidth > SETTINGS.GRID_COLS) {
        if (currentLine.length > 0) lines.push(currentLine);
        currentLine = [];
        currentWidth = 0;
      }

      if (currentLine.length > 0) {
        currentLine.push(" ");
        currentWidth += extraSpace;
      }

      for (const ch of word) {
        currentLine.push(ch);
        currentWidth += charWidthOf(ch) + SETTINGS.MSG_SPACING;
      }
      currentWidth -= SETTINGS.MSG_SPACING;
    }

    if (currentLine.length > 0) lines.push(currentLine);
  }

  // estimate message height in rows
  const totalMessageHeight = lines.length * SETTINGS.MSG_CHAR_HEIGHT;
  const totalVerticalPadding = SETTINGS.VERTICAL_MESSAGE_PADDING * 2;
  const messageBlockHeight = totalMessageHeight + totalVerticalPadding;

  // determine required grid height
  const stripMaxHeight = Math.max(...Object.values(CHRISTMAS_SYMBOLS).map(p => p.length));
  const stripFullHeight = stripMaxHeight + SETTINGS.STRIP_PADDING_ROWS * 2 + 2; // top stitch + padding + symbols + padding + bottom stitch
  const requiredHeight = messageBlockHeight + 2 * stripFullHeight;

  // expand grid if necesarry
  if (requiredHeight > SETTINGS.GRID_ROWS) {
    const extraRows = requiredHeight - SETTINGS.GRID_ROWS;
    SETTINGS.GRID_ROWS = requiredHeight;
    for (let i = 0; i < extraRows * SETTINGS.GRID_COLS; i++) {
      const cb = document.createElement("input");
      cb.type = "checkbox";
      grid.appendChild(cb);
      checkboxes.push(cb);
    }
  }

  // draw top & bottom strips
  const reserved = drawSymbolStrip({
    paddingRows: SETTINGS.STRIP_PADDING_ROWS,
    horizontalPadding: SETTINGS.STRIP_HORIZONTAL_PADDING,
    betweenSymbols: SETTINGS.STRIP_BETWEEN_SYMBOLS,
    dashed: SETTINGS.STRIP_DASHED,
    stitchColor: SETTINGS.STRIP_COLOR
  });

  const { topReserved, bottomReserved } = reserved;

  // calculate vertical start for message (centered between strips + vertical padding)
  const availableHeight = SETTINGS.GRID_ROWS - (topReserved + bottomReserved);
  const startY = topReserved + SETTINGS.VERTICAL_MESSAGE_PADDING + Math.floor((availableHeight - messageBlockHeight) / 2);

  // render each line
  lines.forEach((line, lineIndex) => {
    // calculate line width
    let lineWidth = 0;
    for (let t = 0; t < line.length; t++) {
      lineWidth += line[t] === " " ? SETTINGS.MSG_SPACE_WIDTH : charWidthOf(line[t]);
      if (t < line.length - 1) lineWidth += SETTINGS.MSG_SPACING;
    }

    // horizontal centering
    let cursorX = Math.floor((SETTINGS.GRID_COLS - lineWidth) / 2);
    const yBase = startY + lineIndex * SETTINGS.MSG_CHAR_HEIGHT;

    for (let ti = 0; ti < line.length; ti++) {
      const token = line[ti];

      if (token === " ") {
        cursorX += SETTINGS.MSG_SPACE_WIDTH;
      } else {
        const pattern = CHAR_BITMAPS[token] || [];
        const w = charWidthOf(token);
        for (let y = 0; y < SETTINGS.MSG_CHAR_HEIGHT; y++) {
          const row = pattern[y] || "";
          for (let x = 0; x < w; x++) {
            if (row[x] === "2") {
              const gx = cursorX + x;
              const gy = yBase + y;
              if (gx >= 0 && gx < SETTINGS.GRID_COLS && gy >= 0 && gy < SETTINGS.GRID_ROWS) {
                const idx = gy * SETTINGS.GRID_COLS + gx;
                checkboxes[idx].checked = true;
                checkboxes[idx].className = ""; // plain color
              }
            }
          }
        }
        cursorX += w;
      }
      if (ti < line.length - 1) cursorX += SETTINGS.MSG_SPACING;
    }
  });
}

// clear grid
function clearGrid() {
  messageInput.value = "";
  drawMessage(""); // draws strips only
}

// events
btnGenerate.addEventListener("click", () => drawMessage(messageInput.value));
btnClear.addEventListener("click", clearGrid);

// show first message
drawMessage(FIRST_WORD);
style.css
wget 'https://sme10.lists2.roe3.org/mdrone/ccsgen/style.css'
View Content
@import url(https://fonts.bunny.net/css?family=annie-use-your-telescope:400);
@layers base, demo;

@layer demo{
  :root{
    --clr-bg: rgb(3, 46, 21);
    --clr-txt: red;
  }
  body {
    
    background-color: var(--clr-bg);
    color: var(--clr-txt);
  }
  main{
    font-family: 'Annie Use Your Telescope', handwriting;
      display: grid;
      place-items: center;
      grid-template-rows: auto 1fr;
      width: min(100%, 900px);
      gap: 2rem;
    }
    header{
			width: 100%;
      display: grid;
      gap: .5rem;
      & h1{
        margin: 0;
        text-wrap: balance;
        line-height: 1.2;
        font-size: clamp(1.2rem, 3.5vw + .04rem, 1.6rem);
      }
    }
    .stitches {
      width: 100%;
      align-self: start;
      display: grid;
      grid-template-columns: repeat(var(--grid-cols,100), 1fr);
		  grid-template-rows: repeat(var(--grid-rows), 1fr);
      gap: 1px;
      background: rgb(255 255 255 / .15);
      /*border: 1px solid  rgb(0 0 0 / .15);*/
    }
  
  input[type="checkbox"] {
      --clr-checked: gold;
      appearance: none;
      aspect-ratio:1;
      margin: 0;
      padding: 0;
      border-radius: 0;
      border: transparent;
      position: relative;
      background-color: var(--clr-bg);
      /* Diagonal bars using ::before and ::after */
      &:checked::before,
      &:checked::after {
        content: "";
        position: absolute;
        inset:0;
        margin: auto;
        width: 2px;
        height: 100%;
        rotate: 45deg;
        background: var(--clr-checked);
        box-shadow: 1px 0 1px 1px rgb(0 0 0 / .25);
      }
      /* Rotate the second bar to form the other diagonal */
      &:checked::after {
        transform: rotate(90deg);
      }
      &.checkbox-red {
        --clr-checked: red;
      }
      &.checkbox-white {
        --clr-checked: white;
      }
    }
  
    
  .controls{
    display: grid;
    grid-template-columns: 3fr 1fr;
    gap: .5rem;
    & :where(textarea,button){
      font-size: 1.2rem;
      font-family: inherit;
      color: white;
      border-radius: 3px;
      border: none;
      transition-property: offset,background-color,color;
      transition-timing-function: ease-in-out;
      transition-duration: 150ms;
      &:focus-visible{
        outline: 1px dotted white;
        outline-offset: 2px;
      }
    }
    & button{
      background: red;
      &:hover{
        background-color: rgb(130, 24, 26);
      }
    }
    & textarea {
      grid-row: span 2;
      height: 90px;
      line-height: 1.2;
      padding: 1rem;
      background-color: rgb(255 255 255 / .2);
      resize: none;
      &::placeholder{
        color: #CCC; 
      font-style: italic; 
      }
    }
  }
}

@layer base{
  *,::before,::after{
    box-sizing: border-box;
  }
  body {
    padding: 1rem;
    display: grid;
    place-items: center;
    font-family: system-ui;
    font-size: 1rem;
    line-height: 1.4;
  }
}